Compare commits

..

No commits in common. "main" and "0.1.0" have entirely different histories.
main ... 0.1.0

180 changed files with 5637 additions and 74444 deletions

View File

@ -1,14 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.cs]
csharp_space_after_cast = true
charset = utf-8-bom
max_line_length = 100

6
.gitmodules vendored
View File

@ -7,9 +7,3 @@
[submodule "lib/RefreshCS"]
path = lib/RefreshCS
url = https://gitea.moonside.games/MoonsideGames/RefreshCS.git
[submodule "lib/WellspringCS"]
path = lib/WellspringCS
url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
[submodule "lib/dav1dfile"]
path = lib/dav1dfile
url = https://github.com/MoonsideGames/dav1dfile.git

2862
Doxyfile

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);lib\**\*</DefaultItemExcludes>
</PropertyGroup>
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);lib\**\*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<Compile Include="lib\FAudio\csharp\FAudio.cs" />
<Compile Include="lib\RefreshCS\src\Refresh.cs" />
<Compile Include="lib\SDL2-CS\src\SDL2.cs" />
<Compile Include="lib\WellspringCS\WellspringCS.cs" />
<Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
<ProjectReference Include=".\lib\RefreshCS\RefreshCS.csproj" />
<ProjectReference Include=".\lib\FAudio\csharp\FAudio-CS.Core.csproj" />
</ItemGroup>
<ItemGroup>
<ItemGroup>
<None Include="MoonWorks.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_transform.vert.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh">
<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName>
</EmbeddedResource>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -5,18 +5,10 @@
<dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/>
<dllmap dll="Refresh" os="windows" target="Refresh.dll"/>
<dllmap dll="Refresh" os="osx" target="libRefresh.1.dylib"/>
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/>
<dllmap dll="Refresh" os="osx" target="libRefresh.0.dylib"/>
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.0"/>
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
<dllmap dll="Wellspring" os="osx" target="libWellspring.1.dylib"/>
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.1"/>
<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
<dllmap dll="dav1dfile" os="osx" target="libdav1dfile.1.dylib"/>
<dllmap dll="dav1dfile" os="linux,freebsd,netbsd,openbsd" target="libdav1dfile.so.1"/>
</configuration>

View File

@ -4,6 +4,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoonWorks", "MoonWorks.csproj", "{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}"
ProjectSection(ProjectDependencies) = postProject
{608AA31D-F163-4096-B4EF-B9C7D21D52BB} = {608AA31D-F163-4096-B4EF-B9C7D21D52BB}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS.Core", "lib\SDL2-CS\SDL2-CS.Core.csproj", "{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudio\csharp\FAudio-CS.Core.csproj", "{608AA31D-F163-4096-B4EF-B9C7D21D52BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\RefreshCS\RefreshCS.csproj", "{AD7C94E4-0AFA-44CA-889C-110142369893}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,6 +24,18 @@ Global
{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x64.Build.0 = Debug|x64
{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.ActiveCfg = Release|x64
{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.Build.0 = Release|x64
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Debug|x64.ActiveCfg = Debug|x64
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Debug|x64.Build.0 = Debug|x64
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Release|x64.ActiveCfg = Release|x64
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Release|x64.Build.0 = Release|x64
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Debug|x64.ActiveCfg = Debug|x64
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Debug|x64.Build.0 = Debug|x64
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Release|x64.ActiveCfg = Release|x64
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Release|x64.Build.0 = Release|x64
{AD7C94E4-0AFA-44CA-889C-110142369893}.Debug|x64.ActiveCfg = Debug|x64
{AD7C94E4-0AFA-44CA-889C-110142369893}.Debug|x64.Build.0 = Debug|x64
{AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.ActiveCfg = Release|x64
{AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -10,25 +10,11 @@ MoonWorks *does not* include things like a built-in physics engine, a GUI editor
MoonWorks uses strictly Free Open Source Software. It will never have any kind of dependency on proprietary products.
## Documentation
API Reference: https://moonside.games/docs/moonworksapi/
High-level documentation is provided here: https://moonside.games/docs/moonworks/
The source is documented in doc comments that your preferred IDE can read.
Join our Discord! https://discord.gg/ujhwdkHmhN
## Dependencies
* [SDL2](https://github.com/flibitijibibo/SDL2-CS) - Window management, Input
* [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics
* [FAudio](https://github.com/FNA-XNA/FAudio) - Audio
* [Wellspring](https://gitea.moonside.games/MoonsideGames/Wellspring) - Font Rendering
* [dav1dfile](https://github.com/MoonsideGames/dav1dfile) - Compressed Video
Prebuilt dependencies can be obtained here: https://moonside.games/files/moonlibs.tar.bz2
## License

@ -1 +1 @@
Subproject commit 60480416bda930bf7544e6abe31b937f0daa0256
Subproject commit 5e7991da958b1b22658aa358b5f049d42317125d

@ -1 +1 @@
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28
Subproject commit 64a6e2ac6508879cfc66a50d553be93ab116a2bb

@ -1 +1 @@
Subproject commit e4afbb848586fca530b6538320f799f81a18b941
Subproject commit 2b8d237fd4585d14ea837764ac247d4cd200158f

@ -1 +0,0 @@
Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89

@ -1 +0,0 @@
Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
);
}
}
}

View File

@ -1,354 +1,274 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
namespace MoonWorks.Audio
{
/// <summary>
/// AudioDevice manages all audio-related concerns.
/// </summary>
public class AudioDevice : IDisposable
{
public IntPtr Handle { get; }
public byte[] Handle3D { get; }
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
public class AudioDevice : IDisposable
{
public IntPtr Handle { get; }
public byte[] Handle3D { get; }
public IntPtr MasteringVoice { get; }
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
public IntPtr ReverbVoice { get; }
private IntPtr trueMasteringVoice;
public float CurveDistanceScalar = 1f;
public float DopplerScale = 1f;
public float SpeedOfSound = 343.5f;
// 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;
internal FAudio.FAudioVoiceSends ReverbSends;
public float CurveDistanceScalar = 1f;
public float DopplerScale = 1f;
public float SpeedOfSound = 343.5f;
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
private readonly HashSet<GCHandle> resourceHandles = new HashSet<GCHandle>();
private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
private bool IsDisposed;
private AudioTweenManager AudioTweenManager;
public unsafe AudioDevice()
{
FAudio.FAudioCreate(out var handle, 0, 0);
Handle = handle;
private SourceVoicePool VoicePool;
private List<SourceVoice> VoicesToReturn = new List<SourceVoice>();
/* Find a suitable device */
private const int Step = 200;
private TimeSpan UpdateInterval;
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
private long previousTickTime;
private Thread Thread;
private AutoResetEvent WakeSignal;
internal readonly object StateLock = new object();
FAudio.FAudio_GetDeviceCount(Handle, out var devices);
private bool Running;
public bool IsDisposed { get; private set; }
if (devices == 0)
{
Logger.LogError("No audio devices found!");
Handle = IntPtr.Zero;
FAudio.FAudio_Release(Handle);
return;
}
internal unsafe AudioDevice()
{
UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
FAudio.FAudioDeviceDetails deviceDetails;
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
Handle = handle;
uint i = 0;
for (i = 0; i < devices; i++)
{
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
{
DeviceDetails = deviceDetails;
break;
}
}
/* Find a suitable device */
if (i == devices)
{
i = 0; /* whatever we'll just use the first one I guess */
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
DeviceDetails = deviceDetails;
}
FAudio.FAudio_GetDeviceCount(Handle, out var devices);
/* Init Mastering Voice */
IntPtr masteringVoice;
if (devices == 0)
{
Logger.LogError("No audio devices found!");
FAudio.FAudio_Release(Handle);
Handle = IntPtr.Zero;
return;
}
if (FAudio.FAudio_CreateMasteringVoice(
Handle,
out masteringVoice,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
i,
IntPtr.Zero
) != 0)
{
Logger.LogError("No mastering voice found!");
Handle = IntPtr.Zero;
FAudio.FAudio_Release(Handle);
return;
}
FAudio.FAudioDeviceDetails deviceDetails;
MasteringVoice = masteringVoice;
uint i = 0;
for (i = 0; i < devices; i++)
{
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
{
DeviceDetails = deviceDetails;
break;
}
}
/* Init 3D Audio */
if (i == devices)
{
i = 0; /* whatever we'll just use the first one I guess */
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
DeviceDetails = deviceDetails;
}
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
FAudio.F3DAudioInitialize(
DeviceDetails.OutputFormat.dwChannelMask,
SpeedOfSound,
Handle3D
);
/* Init Mastering Voice */
var result = FAudio.FAudio_CreateMasteringVoice(
Handle,
out trueMasteringVoice,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
i,
IntPtr.Zero
);
/* Init reverb */
if (result != 0)
{
Logger.LogError("Failed to create a mastering voice!");
Logger.LogError("Audio device creation failed!");
return;
}
IntPtr reverbVoice;
fauxMasteringVoice = SubmixVoice.CreateFauxMasteringVoice(this);
IntPtr reverb;
FAudio.FAudioCreateReverb(out reverb, 0);
/* Init 3D Audio */
IntPtr chainPtr;
chainPtr = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioEffectChain>()
);
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
FAudio.F3DAudioInitialize(
DeviceDetails.OutputFormat.dwChannelMask,
SpeedOfSound,
Handle3D
);
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
reverbChain->EffectCount = 1;
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
);
AudioTweenManager = new AudioTweenManager();
VoicePool = new SourceVoicePool(this);
FAudio.FAudioEffectDescriptor* reverbDescriptor =
(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
WakeSignal = new AutoResetEvent(true);
reverbDescriptor->InitialState = 1;
reverbDescriptor->OutputChannels = (uint) (
(DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
);
reverbDescriptor->pEffect = reverb;
Thread = new Thread(ThreadMain);
Thread.IsBackground = true;
Thread.Start();
FAudio.FAudio_CreateSubmixVoice(
Handle,
out reverbVoice,
1, /* omnidirectional reverb */
DeviceDetails.OutputFormat.Format.nSamplesPerSec,
0,
0,
IntPtr.Zero,
chainPtr
);
FAudio.FAPOBase_Release(reverb);
Running = true;
Marshal.FreeHGlobal(reverbChain->pEffectDescriptors);
Marshal.FreeHGlobal(chainPtr);
TickStopwatch.Start();
previousTickTime = 0;
}
ReverbVoice = reverbVoice;
private void ThreadMain()
{
while (Running)
{
lock (StateLock)
{
try
{
ThreadMainTick();
}
catch (Exception e)
{
Logger.LogError(e.ToString());
}
}
/* Init reverb params */
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
WakeSignal.WaitOne(UpdateInterval);
}
}
IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioFXReverbParameters>()
);
private void ThreadMainTick()
{
long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime;
previousTickTime = TickStopwatch.Elapsed.Ticks;
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
reverbParams->WetDryMix = 100.0f;
reverbParams->ReflectionsDelay = 7;
reverbParams->ReverbDelay = 11;
reverbParams->RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
reverbParams->PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams->PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
reverbParams->PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams->PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
reverbParams->EarlyDiffusion = 15;
reverbParams->LateDiffusion = 15;
reverbParams->LowEQGain = 8;
reverbParams->LowEQCutoff = 4;
reverbParams->HighEQGain = 8;
reverbParams->HighEQCutoff = 6;
reverbParams->RoomFilterFreq = 5000f;
reverbParams->RoomFilterMain = -10f;
reverbParams->RoomFilterHF = -1f;
reverbParams->ReflectionsGain = -26.0200005f;
reverbParams->ReverbGain = 10.0f;
reverbParams->DecayTime = 1.49000001f;
reverbParams->Density = 100.0f;
reverbParams->RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
FAudio.FAudioVoice_SetEffectParameters(
ReverbVoice,
0,
reverbParamsPtr,
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
0
);
Marshal.FreeHGlobal(reverbParamsPtr);
AudioTweenManager.Update(elapsedSeconds);
/* Init reverb sends */
foreach (var voice in updatingSourceVoices)
{
voice.Update();
}
ReverbSends = new FAudio.FAudioVoiceSends
{
SendCount = 2,
pSends = Marshal.AllocHGlobal(
2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()
)
};
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
sendDesc[0].Flags = 0;
sendDesc[0].pOutputVoice = MasteringVoice;
sendDesc[1].Flags = 0;
sendDesc[1].pOutputVoice = ReverbVoice;
}
foreach (var voice in VoicesToReturn)
{
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Remove(updatingSourceVoice);
}
public void Update()
{
for (var i = streamingSounds.Count - 1; i >= 0; i--)
{
var weakReference = streamingSounds[i];
if (weakReference.TryGetTarget(out var streamingSound))
{
streamingSound.Update();
}
else
{
streamingSounds.RemoveAt(i);
}
}
}
voice.Reset();
VoicePool.Return(voice);
}
internal void AddDynamicSoundInstance(StreamingSound instance)
{
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
}
VoicesToReturn.Clear();
}
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
{
lock (resources)
{
resources.Add(resourceReference);
}
}
/// <summary>
/// Triggers all pending operations with the given syncGroup value.
/// </summary>
public void TriggerSyncGroup(uint syncGroup)
{
FAudio.FAudio_CommitChanges(Handle, syncGroup);
}
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
{
lock (resources)
{
resources.Remove(resourceReference);
}
}
/// <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);
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
foreach (var weakReference in streamingSounds)
{
if (weakReference.TryGetTarget(out var streamingSound))
{
streamingSound.Dispose();
}
}
streamingSounds.Clear();
}
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Add(updatingSourceVoice);
}
FAudio.FAudio_Release(Handle);
return voice;
}
}
IsDisposed = true;
}
}
/// <summary>
/// Returns the source voice to the voice pool.
/// </summary>
/// <param name="voice"></param>
internal void Return(SourceVoice voice)
{
lock (StateLock)
{
VoicesToReturn.Add(voice);
}
}
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
~AudioDevice()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
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)
{
lock (StateLock)
{
resourceHandles.Remove(resourceReference);
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
{
updatingSourceVoices.Remove(updatableVoice);
}
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
Running = false;
if (disposing)
{
Thread.Join();
// dispose all source voices first
foreach (var handle in resourceHandles)
{
if (handle.Target is SourceVoice voice)
{
voice.Dispose();
}
}
// dispose all submix voices except the faux mastering voice
foreach (var handle in resourceHandles)
{
if (handle.Target is SubmixVoice voice && voice != fauxMasteringVoice)
{
voice.Dispose();
}
}
// dispose the faux mastering voice
fauxMasteringVoice.Dispose();
// dispose the true mastering voice
FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
// destroy all other audio resources
foreach (var handle in resourceHandles)
{
if (handle.Target is AudioResource resource)
{
resource.Dispose();
}
}
resourceHandles.Clear();
}
FAudio.FAudio_Release(Handle);
IsDisposed = true;
}
}
~AudioDevice()
{
// 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);
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -1,15 +1,12 @@
using System;
using System;
using System.Runtime.InteropServices;
using MoonWorks.Math.Float;
using MoonWorks.Math;
namespace MoonWorks.Audio
{
/// <summary>
/// An emitter for 3D spatial audio.
/// </summary>
public class AudioEmitter : AudioResource
{
internal FAudio.F3DAUDIO_EMITTER emitterData;
public class AudioEmitter : AudioResource
{
internal FAudio.F3DAUDIO_EMITTER emitterData;
public float DopplerScale
{
@ -110,17 +107,17 @@ namespace MoonWorks.Audio
GCHandleType.Pinned
);
public AudioEmitter(AudioDevice device) : base(device)
{
emitterData = new FAudio.F3DAUDIO_EMITTER();
public AudioEmitter(AudioDevice device) : base(device)
{
emitterData = new FAudio.F3DAUDIO_EMITTER();
DopplerScale = 1f;
Forward = Vector3.Forward;
Position = Vector3.Zero;
Up = Vector3.Up;
Velocity = Vector3.Zero;
DopplerScale = 1f;
Forward = Vector3.Forward;
Position = Vector3.Zero;
Up = Vector3.Up;
Velocity = Vector3.Zero;
/* Unexposed variables, defaults based on XNA behavior */
/* Unexposed variables, defaults based on XNA behavior */
emitterData.pCone = IntPtr.Zero;
emitterData.ChannelCount = 1;
emitterData.ChannelRadius = 1.0f;
@ -131,6 +128,8 @@ namespace MoonWorks.Audio
emitterData.pLPFReverbCurve = IntPtr.Zero;
emitterData.pReverbCurve = IntPtr.Zero;
emitterData.CurveDistanceScaler = 1.0f;
}
}
}
protected override void Destroy() { }
}
}

View File

@ -1,14 +1,11 @@
using System;
using MoonWorks.Math.Float;
using System;
using MoonWorks.Math;
namespace MoonWorks.Audio
{
/// <summary>
/// A listener for 3D spatial audio. Usually attached to a camera.
/// </summary>
public class AudioListener : AudioResource
{
internal FAudio.F3DAUDIO_LISTENER listenerData;
public class AudioListener : AudioResource
{
internal FAudio.F3DAUDIO_LISTENER listenerData;
public Vector3 Forward
{
@ -83,16 +80,18 @@ namespace MoonWorks.Audio
}
}
public AudioListener(AudioDevice device) : base(device)
{
listenerData = new FAudio.F3DAUDIO_LISTENER();
Forward = Vector3.Forward;
Position = Vector3.Zero;
Up = Vector3.Up;
Velocity = Vector3.Zero;
public AudioListener(AudioDevice device) : base(device)
{
listenerData = new FAudio.F3DAUDIO_LISTENER();
Forward = Vector3.Forward;
Position = Vector3.Zero;
Up = Vector3.Up;
Velocity = Vector3.Zero;
/* Unexposed variables, defaults based on XNA behavior */
/* Unexposed variables, defaults based on XNA behavior */
listenerData.pCone = IntPtr.Zero;
}
}
}
protected override void Destroy() { }
}
}

View File

@ -1,53 +1,52 @@
using System;
using System.Runtime.InteropServices;
using System;
namespace MoonWorks.Audio
{
public abstract class AudioResource : IDisposable
{
public AudioDevice Device { get; }
public abstract class AudioResource : IDisposable
{
public AudioDevice Device { get; }
public bool IsDisposed { get; private set; }
public bool IsDisposed { get; private set; }
private GCHandle SelfReference;
private WeakReference<AudioResource> selfReference;
protected AudioResource(AudioDevice device)
{
Device = device;
public AudioResource(AudioDevice device)
{
Device = device;
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
Device.AddResourceReference(SelfReference);
}
selfReference = new WeakReference<AudioResource>(this);
Device.AddResourceReference(selfReference);
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Device.RemoveResourceReference(SelfReference);
SelfReference.Free();
}
protected abstract void Destroy();
IsDisposed = true;
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
Destroy();
~AudioResource()
{
#if DEBUG
// If you see this log message, you leaked an audio resource without disposing it!
// We can't clean it up for you because this can cause catastrophic issues.
// You should really fix this when it happens.
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
#endif
}
if (selfReference != null)
{
Device.RemoveResourceReference(selfReference);
selfReference = null;
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
IsDisposed = true;
}
}
~AudioResource()
{
// 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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,10 +0,0 @@
namespace MoonWorks.Audio
{
public enum FilterType
{
None,
LowPass,
BandPass,
HighPass
}
}

View File

@ -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
};
}
}
}

View File

@ -1,7 +0,0 @@
namespace MoonWorks.Audio
{
public interface IPoolable<T>
{
static abstract T Create(AudioDevice device, Format format);
}
}

View File

@ -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));
}
}
}

View File

@ -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
);
}
}
}
}

352
src/Audio/SoundInstance.cs Normal file
View File

@ -0,0 +1,352 @@
using System;
using System.Runtime.InteropServices;
using MoonWorks.Math;
namespace MoonWorks.Audio
{
public abstract class SoundInstance : AudioResource
{
internal IntPtr Handle { get; }
internal FAudio.FAudioWaveFormatEx Format { get; }
public bool Loop { get; }
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
protected bool is3D;
public abstract 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 = 1;
public float Pitch
{
get => _pitch;
set
{
_pitch = 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 float _lowPassFilter;
public float LowPassFilter
{
get => _lowPassFilter;
set
{
_lowPassFilter = value;
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
Frequency = _lowPassFilter,
OneOverQ = 1f
};
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref p,
0
);
}
}
private float _highPassFilter;
public float HighPassFilter
{
get => _highPassFilter;
set
{
_highPassFilter = value;
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioHighPassFilter,
Frequency = _highPassFilter,
OneOverQ = 1f
};
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref p,
0
);
}
}
private float _bandPassFilter;
public float BandPassFilter
{
get => _bandPassFilter;
set
{
_bandPassFilter = value;
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioBandPassFilter,
Frequency = _bandPassFilter,
OneOverQ = 1f
};
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref p,
0
);
}
}
public SoundInstance(
AudioDevice device,
ushort channels,
uint samplesPerSecond,
bool is3D,
bool loop
) : base(device)
{
var blockAlign = (ushort)(4 * channels);
var format = new FAudio.FAudioWaveFormatEx
{
wFormatTag = 3,
wBitsPerSample = 32,
nChannels = channels,
nBlockAlign = blockAlign,
nSamplesPerSec = samplesPerSecond,
nAvgBytesPerSec = blockAlign * samplesPerSecond
};
Format = format;
FAudio.FAudio_CreateSourceVoice(
Device.Handle,
out var 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;
}
Handle = handle;
this.is3D = is3D;
InitDSPSettings(Format.nChannels);
FAudio.FAudioVoice_SetOutputVoices(
handle,
ref Device.ReverbSends
);
Loop = loop;
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(bool immediate);
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()
{
Stop(true);
FAudio.FAudioVoice_DestroyVoice(Handle);
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -1,9 +1,9 @@
namespace MoonWorks.Audio
namespace MoonWorks.Audio
{
public enum SoundState
{
Playing,
Paused,
Stopped
}
public enum SoundState
{
Playing,
Paused,
Stopped
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

82
src/Audio/StaticSound.cs Normal file
View File

@ -0,0 +1,82 @@
using System;
using System.Runtime.InteropServices;
namespace MoonWorks.Audio
{
public class StaticSound : AudioResource
{
internal FAudio.FAudioBuffer Handle;
public ushort Channels { get; }
public uint SamplesPerSecond { get; }
public uint LoopStart { get; set; } = 0;
public uint LoopLength { get; set; } = 0;
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
);
}
public StaticSound(
AudioDevice device,
ushort channels,
uint samplesPerSecond,
float[] buffer,
uint bufferOffset, /* in floats */
uint bufferLength /* in floats */
) : base(device)
{
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;
}
public StaticSoundInstance CreateInstance(bool loop = false)
{
return new StaticSoundInstance(Device, this, false, loop);
}
protected override void Destroy()
{
Marshal.FreeHGlobal(Handle.pAudioData);
}
}
}

View File

@ -0,0 +1,96 @@
using System;
namespace MoonWorks.Audio
{
public class StaticSoundInstance : SoundInstance
{
public StaticSound Parent { get; }
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)
{
Stop(true);
}
return _state;
}
protected set
{
_state = value;
}
}
internal StaticSoundInstance(
AudioDevice device,
StaticSound parent,
bool is3D,
bool loop
) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop)
{
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(bool immediate = true)
{
if (immediate)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
State = SoundState.Stopped;
}
else
{
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
}
}
}
}

178
src/Audio/StreamingSound.cs Normal file
View File

@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MoonWorks.Audio
{
/// <summary>
/// For streaming long playback.
/// Can be extended to support custom decoders.
/// </summary>
public abstract class StreamingSound : SoundInstance
{
private readonly List<IntPtr> queuedBuffers = new List<IntPtr>();
private readonly List<uint> queuedSizes = new List<uint>();
private const int MINIMUM_BUFFER_CHECK = 3;
public int PendingBufferCount => queuedBuffers.Count;
public StreamingSound(
AudioDevice device,
ushort channels,
uint samplesPerSecond,
bool is3D,
bool loop
) : base(device, channels, samplesPerSecond, is3D, loop) { }
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(bool immediate = true)
{
if (immediate)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
ClearBuffers();
}
State = SoundState.Stopped;
}
internal void Update()
{
if (State != SoundState.Playing)
{
return;
}
FAudio.FAudioSourceVoice_GetState(
Handle,
out var state,
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
);
while (PendingBufferCount > state.BuffersQueued)
lock (queuedBuffers)
{
Marshal.FreeHGlobal(queuedBuffers[0]);
queuedBuffers.RemoveAt(0);
}
QueueBuffers();
}
protected void QueueBuffers()
{
for (
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
i > 0;
i -= 1
)
{
AddBuffer();
}
}
protected void ClearBuffers()
{
lock (queuedBuffers)
{
foreach (IntPtr buf in queuedBuffers)
{
Marshal.FreeHGlobal(buf);
}
queuedBuffers.Clear();
queuedSizes.Clear();
}
}
protected void AddBuffer()
{
AddBuffer(
out var buffer,
out var bufferOffset,
out var bufferLength,
out var reachedEnd
);
var lengthInBytes = bufferLength * sizeof(float);
IntPtr next = Marshal.AllocHGlobal((int) lengthInBytes);
Marshal.Copy(buffer, (int) bufferOffset, next, (int) bufferLength);
lock (queuedBuffers)
{
queuedBuffers.Add(next);
if (State != SoundState.Stopped)
{
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
{
AudioBytes = lengthInBytes,
pAudioData = next,
PlayLength = (
lengthInBytes /
Format.nChannels /
(uint)(Format.wBitsPerSample / 8)
)
};
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
Handle,
ref buf,
IntPtr.Zero
);
}
else
{
queuedSizes.Add(lengthInBytes);
}
}
/* We have reached the end of the file, what do we do? */
if (reachedEnd)
{
if (Loop)
{
SeekStart();
}
else
{
Stop(false);
}
}
}
protected abstract void AddBuffer(
out float[] buffer,
out uint bufferOffset, /* in floats */
out uint bufferLength, /* in floats */
out bool reachedEnd
);
protected abstract void SeekStart();
protected override void Destroy()
{
Stop(true);
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using System.IO;
namespace MoonWorks.Audio
{
public class StreamingSoundOgg : StreamingSound
{
// FIXME: what should this value be?
public const int BUFFER_SIZE = 1024 * 128;
internal IntPtr FileHandle { get; }
internal FAudio.stb_vorbis_info Info { get; }
private readonly float[] buffer;
public override SoundState State { get; protected set; }
public static StreamingSoundOgg Load(
AudioDevice device,
string filePath,
bool is3D = false,
bool loop = false
) {
var fileHandle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
if (error != 0)
{
Logger.LogError("Error opening OGG file!");
throw new AudioLoadException("Error opening OGG file!");
}
var info = FAudio.stb_vorbis_get_info(fileHandle);
return new StreamingSoundOgg(
device,
fileHandle,
info,
is3D,
loop
);
}
internal StreamingSoundOgg(
AudioDevice device,
IntPtr fileHandle,
FAudio.stb_vorbis_info info,
bool is3D,
bool loop
) : base(device, (ushort) info.channels, info.sample_rate, is3D, loop)
{
FileHandle = fileHandle;
Info = info;
buffer = new float[BUFFER_SIZE];
device.AddDynamicSoundInstance(this);
}
protected override void AddBuffer(
out float[] buffer,
out uint bufferOffset,
out uint bufferLength,
out bool reachedEnd
) {
buffer = this.buffer;
/* NOTE: this function returns samples per channel, not total samples */
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
FileHandle,
Info.channels,
buffer,
buffer.Length
);
var sampleCount = samples * Info.channels;
bufferOffset = 0;
bufferLength = (uint) sampleCount;
reachedEnd = sampleCount < buffer.Length;
}
protected override void SeekStart()
{
FAudio.stb_vorbis_seek_start(FileHandle);
}
protected override void Destroy()
{
FAudio.stb_vorbis_close(FileHandle);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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];
}
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace MoonWorks
{
public class AudioLoadException : Exception
{
public AudioLoadException(string message) : base(message)
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,426 +1,164 @@
using SDL2;
using System.Collections.Generic;
using SDL2;
using MoonWorks.Audio;
using MoonWorks.Graphics;
using MoonWorks.Input;
using MoonWorks.Window;
using System.Text;
using System;
using System.Diagnostics;
namespace MoonWorks
{
/// <summary>
/// This class is your entry point into controlling your game. <br/>
/// It manages the main game loop and subsystems. <br/>
/// You should inherit this class and implement Update and Draw methods. <br/>
/// Then instantiate your Game subclass from your Program.Main method and call the Run method.
/// </summary>
public abstract class Game
{
public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
public TimeSpan Timestep { get; private set; }
public abstract class Game
{
public const double MAX_DELTA_TIME = 0.1;
private bool quit = false;
private Stopwatch gameTimer;
private long previousTicks = 0;
TimeSpan accumulatedUpdateTime = TimeSpan.Zero;
TimeSpan accumulatedDrawTime = TimeSpan.Zero;
// must be a power of 2 so we can do a bitmask optimization when checking worst case
private const int PREVIOUS_SLEEP_TIME_COUNT = 128;
private const int SLEEP_TIME_MASK = PREVIOUS_SLEEP_TIME_COUNT - 1;
private TimeSpan[] previousSleepTimes = new TimeSpan[PREVIOUS_SLEEP_TIME_COUNT];
private int sleepTimeIndex = 0;
private TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1);
private bool quit = false;
private double timestep;
ulong currentTime = SDL.SDL_GetPerformanceCounter();
double accumulator = 0;
bool debugMode;
private bool FramerateCapped = false;
private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero;
public OSWindow Window { get; }
public GraphicsDevice GraphicsDevice { get; }
public AudioDevice AudioDevice { get; }
public Inputs Inputs { get; }
public GraphicsDevice GraphicsDevice { get; }
public AudioDevice AudioDevice { get; }
public Inputs Inputs { get; }
private Dictionary<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
{
{ PresentMode.Immediate, RefreshCS.Refresh.PresentMode.Immediate },
{ PresentMode.Mailbox, RefreshCS.Refresh.PresentMode.Mailbox },
{ PresentMode.FIFO, RefreshCS.Refresh.PresentMode.FIFO },
{ PresentMode.FIFORelaxed, RefreshCS.Refresh.PresentMode.FIFORelaxed }
};
/// <summary>
/// This Window is automatically created when your Game is instantiated.
/// </summary>
public Window MainWindow { get; }
public Game(
WindowCreateInfo windowCreateInfo,
PresentMode presentMode,
int targetTimestep = 60,
bool debugMode = false
) {
timestep = 1.0 / targetTimestep;
/// <summary>
/// Instantiates your Game.
/// </summary>
/// <param name="windowCreateInfo">The parameters that will be used to create the MainWindow.</param>
/// <param name="frameLimiterSettings">The frame limiter settings.</param>
/// <param name="targetTimestep">How often Game.Update will run in terms of ticks per second.</param>
/// <param name="debugMode">If true, enables extra debug checks. Should be turned off for release builds.</param>
public Game(
WindowCreateInfo windowCreateInfo,
FrameLimiterSettings frameLimiterSettings,
int targetTimestep = 60,
bool debugMode = false
)
{
Logger.LogInfo("Initializing frame limiter...");
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
gameTimer = Stopwatch.StartNew();
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
{
System.Console.WriteLine("Failed to initialize SDL!");
return;
}
SetFrameLimiter(frameLimiterSettings);
Logger.Initialize();
for (int i = 0; i < previousSleepTimes.Length; i += 1)
{
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
}
Inputs = new Inputs();
Logger.LogInfo("Initializing SDL...");
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
{
Logger.LogError("Failed to initialize SDL!");
return;
}
Window = new OSWindow(windowCreateInfo);
Logger.Initialize();
GraphicsDevice = new GraphicsDevice(
Window.Handle,
moonWorksToRefreshPresentMode[presentMode],
debugMode
);
Logger.LogInfo("Initializing input...");
Inputs = new Inputs();
AudioDevice = new AudioDevice();
Logger.LogInfo("Initializing graphics device...");
GraphicsDevice = new GraphicsDevice(
Backend.Vulkan,
debugMode
);
this.debugMode = debugMode;
}
Logger.LogInfo("Initializing main window...");
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
public void Run()
{
while (!quit)
{
var newTime = SDL.SDL_GetPerformanceCounter();
double frameTime = (newTime - currentTime) / (double)SDL.SDL_GetPerformanceFrequency();
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
{
throw new System.SystemException("Could not claim window!");
}
if (frameTime > MAX_DELTA_TIME)
{
frameTime = MAX_DELTA_TIME;
}
Logger.LogInfo("Initializing audio thread...");
AudioDevice = new AudioDevice();
}
currentTime = newTime;
/// <summary>
/// Initiates the main game loop. Call this once from your Program.Main method.
/// </summary>
public void Run()
{
MainWindow.Show();
accumulator += frameTime;
while (!quit)
{
Tick();
}
if (!quit)
{
while (accumulator >= timestep)
{
Inputs.Mouse.Wheel = 0;
Logger.LogInfo("Starting shutdown sequence...");
HandleSDLEvents();
Logger.LogInfo("Cleaning up game...");
Destroy();
Inputs.Update();
AudioDevice.Update();
Logger.LogInfo("Unclaiming window...");
GraphicsDevice.UnclaimWindow(MainWindow);
Update(timestep);
Logger.LogInfo("Disposing window...");
MainWindow.Dispose();
accumulator -= timestep;
}
Logger.LogInfo("Disposing graphics device...");
GraphicsDevice.Dispose();
double alpha = accumulator / timestep;
Logger.LogInfo("Closing audio thread...");
AudioDevice.Dispose();
Draw(timestep, alpha);
}
}
}
SDL.SDL_Quit();
}
private void HandleSDLEvents()
{
while (SDL.SDL_PollEvent(out var _event) == 1)
{
switch (_event.type)
{
case SDL.SDL_EventType.SDL_QUIT:
quit = true;
break;
/// <summary>
/// Updates the frame limiter settings.
/// </summary>
public void SetFrameLimiter(FrameLimiterSettings settings)
{
FramerateCapped = settings.Mode == FrameLimiterMode.Capped;
case SDL.SDL_EventType.SDL_TEXTINPUT:
HandleTextInput(_event);
break;
if (FramerateCapped)
{
FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap);
}
else
{
FramerateCapTimeSpan = TimeSpan.Zero;
}
}
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
Inputs.Mouse.Wheel += _event.wheel.y;
break;
}
}
}
/// <summary>
/// Starts the game shutdown process.
/// </summary>
public void Quit()
{
quit = true;
}
protected abstract void Update(double dt);
/// <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 Draw(double dt, double alpha);
/// <summary>
/// If the frame limiter mode is Capped, this will run at most Cap times per second. <br />
/// Otherwise it will run as many times as possible.
/// </summary>
/// <param name="alpha">A value from 0-1 describing how "in-between" update ticks it is called. Useful for interpolation.</param>
protected abstract void Draw(double alpha);
/// <summary>
/// You can optionally override this to perform cleanup tasks before the game quits.
/// </summary>
protected virtual void Destroy() {}
/// <summary>
/// Called when a file is dropped on the game window.
/// </summary>
protected virtual void DropFile(string filePath) {}
/// <summary>
/// Required to distinguish between multiple files dropped at once
/// vs multiple files dropped one at a time.
/// Called once for every multi-file drop.
/// </summary>
protected virtual void DropBegin() {}
protected virtual void DropComplete() {}
private void Tick()
{
AdvanceElapsedTime();
if (FramerateCapped)
{
/* We want to wait until the framerate cap,
* but we don't want to oversleep. Requesting repeated 1ms sleeps and
* seeing how long we actually slept for lets us estimate the worst case
* sleep precision so we don't oversleep the next frame.
*/
while (accumulatedDrawTime + worstCaseSleepPrecision < FramerateCapTimeSpan)
{
System.Threading.Thread.Sleep(1);
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
}
/* Now that we have slept into the sleep precision threshold, we need to wait
* for just a little bit longer until the target elapsed time has been reached.
* SpinWait(1) works by pausing the thread for very short intervals, so it is
* an efficient and time-accurate way to wait out the rest of the time.
*/
while (accumulatedDrawTime < FramerateCapTimeSpan)
{
System.Threading.Thread.SpinWait(1);
AdvanceElapsedTime();
}
}
// Now that we are going to perform an update, let's handle SDL events.
HandleSDLEvents();
// Do not let any step take longer than our maximum.
if (accumulatedUpdateTime > MAX_DELTA_TIME)
{
accumulatedUpdateTime = MAX_DELTA_TIME;
}
if (!quit)
{
while (accumulatedUpdateTime >= Timestep)
{
Inputs.Update();
Update(Timestep);
AudioDevice.WakeThread();
accumulatedUpdateTime -= Timestep;
}
var alpha = accumulatedUpdateTime / Timestep;
Draw(alpha);
accumulatedDrawTime -= FramerateCapTimeSpan;
}
}
private void HandleSDLEvents()
{
while (SDL.SDL_PollEvent(out var _event) == 1)
{
switch (_event.type)
{
case SDL.SDL_EventType.SDL_QUIT:
quit = true;
break;
case SDL.SDL_EventType.SDL_TEXTINPUT:
HandleTextInput(_event);
break;
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
Inputs.Mouse.WheelRaw += _event.wheel.y;
break;
case SDL.SDL_EventType.SDL_DROPBEGIN:
DropBegin();
break;
case SDL.SDL_EventType.SDL_DROPCOMPLETE:
DropComplete();
break;
case SDL.SDL_EventType.SDL_DROPFILE:
HandleFileDrop(_event);
break;
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED:
HandleControllerAdded(_event);
break;
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED:
HandleControllerRemoved(_event);
break;
case SDL.SDL_EventType.SDL_WINDOWEVENT:
HandleWindowEvent(_event);
break;
}
}
}
private void HandleWindowEvent(SDL.SDL_Event evt)
{
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
{
var window = Window.Lookup(evt.window.windowID);
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();
}
}
private void HandleTextInput(SDL.SDL_Event evt)
{
// Based on the SDL2# LPUtf8StrMarshaler
unsafe
{
int bytes = MeasureStringLength(evt.text.text);
if (bytes > 0)
{
/* UTF8 will never encode more characters
private void HandleTextInput(SDL2.SDL.SDL_Event evt)
{
// Based on the SDL2# LPUtf8StrMarshaler
unsafe
{
int bytes = MeasureStringLength(evt.text.text);
if (bytes > 0)
{
/* UTF8 will never encode more characters
* than bytes in a string, so bytes is a
* suitable upper estimate of size needed
*/
char* charsBuffer = stackalloc char[bytes];
int chars = Encoding.UTF8.GetChars(
evt.text.text,
bytes,
charsBuffer,
bytes
);
char* charsBuffer = stackalloc char[bytes];
int chars = Encoding.UTF8.GetChars(
evt.text.text,
bytes,
charsBuffer,
bytes
);
for (int i = 0; i < chars; i += 1)
{
Inputs.OnTextInput(charsBuffer[i]);
}
}
}
}
private void HandleFileDrop(SDL.SDL_Event evt)
{
// Need to do it this way because SDL2 expects you to free the filename string.
string filePath = SDL.UTF8_ToManaged(evt.drop.file, true);
DropFile(filePath);
}
private void HandleControllerAdded(SDL.SDL_Event evt)
{
var index = evt.cdevice.which;
if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
{
Logger.LogInfo("New controller detected!");
Inputs.AddGamepad(index);
}
}
private void HandleControllerRemoved(SDL.SDL_Event evt)
{
Logger.LogInfo("Controller removal detected!");
Inputs.RemoveGamepad(evt.cdevice.which);
}
public static void ShowRuntimeError(string title, string message)
{
SDL.SDL_ShowSimpleMessageBox(
SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
title ?? "",
message ?? "",
IntPtr.Zero
);
}
private TimeSpan AdvanceElapsedTime()
{
long currentTicks = gameTimer.Elapsed.Ticks;
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
accumulatedUpdateTime += timeAdvanced;
accumulatedDrawTime += timeAdvanced;
previousTicks = currentTicks;
return timeAdvanced;
}
/* To calculate the sleep precision of the OS, we take the worst case
* time spent sleeping over the results of previous requests to sleep 1ms.
*/
private void UpdateEstimatedSleepPrecision(TimeSpan timeSpentSleeping)
{
/* It is unlikely that the scheduler will actually be more imprecise than
* 4ms and we don't want to get wrecked by a single long sleep so we cap this
* value at 4ms for sanity.
*/
var upperTimeBound = TimeSpan.FromMilliseconds(4);
if (timeSpentSleeping > upperTimeBound)
{
timeSpentSleeping = upperTimeBound;
}
/* We know the previous worst case - it's saved in worstCaseSleepPrecision.
* We also know the current index. So the only way the worst case changes
* is if we either 1) just got a new worst case, or 2) the worst case was
* the oldest entry on the list.
*/
if (timeSpentSleeping >= worstCaseSleepPrecision)
{
worstCaseSleepPrecision = timeSpentSleeping;
}
else if (previousSleepTimes[sleepTimeIndex] == worstCaseSleepPrecision)
{
var maxSleepTime = TimeSpan.MinValue;
for (int i = 0; i < previousSleepTimes.Length; i++)
{
if (previousSleepTimes[i] > maxSleepTime)
{
maxSleepTime = previousSleepTimes[i];
}
}
worstCaseSleepPrecision = maxSleepTime;
}
previousSleepTimes[sleepTimeIndex] = timeSpentSleeping;
sleepTimeIndex = (sleepTimeIndex + 1) & SLEEP_TIME_MASK;
}
for (int i = 0; i < chars; i += 1)
{
Inputs.OnTextInput(charsBuffer[i]);
}
}
}
}
private unsafe static int MeasureStringLength(byte* ptr)
{
int bytes;
for (bytes = 0; *ptr != 0; ptr += 1, bytes += 1) ;
for (bytes = 0; *ptr != 0; ptr += 1, bytes += 1);
return bytes;
}
}
}
}

View File

@ -1,17 +1,14 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
/// <summary>
/// A buffer-offset pair to be used when binding vertex buffers.
/// </summary>
public struct BufferBinding
{
public Buffer Buffer;
public ulong Offset;
public struct BufferBinding
{
public Buffer Buffer;
public ulong Offset;
public BufferBinding(Buffer buffer, ulong offset)
{
Buffer = buffer;
Offset = offset;
}
}
public BufferBinding(Buffer buffer, ulong offset)
{
Buffer = buffer;
Offset = offset;
}
}
}

View File

@ -1,17 +1,14 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// A texture-sampler pair to be used when binding samplers.
/// </summary>
public struct TextureSamplerBinding
{
public Texture Texture;
public Sampler Sampler;
public struct TextureSamplerBinding
{
public Texture Texture;
public Sampler Sampler;
public TextureSamplerBinding(Texture texture, Sampler sampler)
{
Texture = texture;
Sampler = sampler;
}
}
public TextureSamplerBinding(Texture texture, Sampler sampler)
{
Texture = texture;
Sampler = sampler;
}
}
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -19,8 +19,6 @@ using System;
using System.Diagnostics;
using System.Text;
using MoonWorks.Math;
using MoonWorks.Math.Float;
using MoonWorks.Graphics.PackedVector;
#endregion
namespace MoonWorks.Graphics
@ -43,7 +41,7 @@ namespace MoonWorks.Graphics
{
unchecked
{
return (byte) (this.packedValue);
return (byte)(this.packedValue);
}
}
set
@ -61,12 +59,12 @@ namespace MoonWorks.Graphics
{
unchecked
{
return (byte) (this.packedValue >> 8);
return (byte)(this.packedValue >> 8);
}
}
set
{
this.packedValue = (this.packedValue & 0xffff00ff) | ((uint) value << 8);
this.packedValue = (this.packedValue & 0xffff00ff) | ((uint)value << 8);
}
}
@ -1540,27 +1538,27 @@ namespace MoonWorks.Graphics
RosyBrown = new Color(0xff8f8fbc);
RoyalBlue = new Color(0xffe16941);
SaddleBrown = new Color(0xff13458b);
Salmon = new Color(0xff7280fa);
Salmon= new Color(0xff7280fa);
SandyBrown = new Color(0xff60a4f4);
SeaGreen = new Color(0xff578b2e);
SeaShell = new Color(0xffeef5ff);
Sienna = new Color(0xff2d52a0);
Silver = new Color(0xffc0c0c0);
SkyBlue = new Color(0xffebce87);
SlateBlue = new Color(0xffcd5a6a);
SlateGray = new Color(0xff908070);
Snow = new Color(0xfffafaff);
SpringGreen = new Color(0xff7fff00);
SteelBlue = new Color(0xffb48246);
Tan = new Color(0xff8cb4d2);
Teal = new Color(0xff808000);
Thistle = new Color(0xffd8bfd8);
Tomato = new Color(0xff4763ff);
Turquoise = new Color(0xffd0e040);
Violet = new Color(0xffee82ee);
Wheat = new Color(0xffb3def5);
White = new Color(uint.MaxValue);
WhiteSmoke = new Color(0xfff5f5f5);
SlateBlue= new Color(0xffcd5a6a);
SlateGray= new Color(0xff908070);
Snow= new Color(0xfffafaff);
SpringGreen= new Color(0xff7fff00);
SteelBlue= new Color(0xffb48246);
Tan= new Color(0xff8cb4d2);
Teal= new Color(0xff808000);
Thistle= new Color(0xffd8bfd8);
Tomato= new Color(0xff4763ff);
Turquoise= new Color(0xffd0e040);
Violet= new Color(0xffee82ee);
Wheat= new Color(0xffb3def5);
White= new Color(uint.MaxValue);
WhiteSmoke= new Color(0xfff5f5f5);
Yellow = new Color(0xff00ffff);
YellowGreen = new Color(0xff32cd9a);
}
@ -1625,7 +1623,7 @@ namespace MoonWorks.Graphics
R = (byte) MathHelper.Clamp(r, Byte.MinValue, Byte.MaxValue);
G = (byte) MathHelper.Clamp(g, Byte.MinValue, Byte.MaxValue);
B = (byte) MathHelper.Clamp(b, Byte.MinValue, Byte.MaxValue);
A = (byte) 255;
A = (byte)255;
}
/// <summary>
@ -1759,67 +1757,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
#region Public Static Operators and Override Methods
@ -1832,10 +1769,10 @@ namespace MoonWorks.Graphics
/// <returns><c>True</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(Color a, Color b)
{
return (a.A == b.A &&
return ( a.A == b.A &&
a.R == b.R &&
a.G == b.G &&
a.B == b.B);
a.B == b.B );
}
/// <summary>

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,17 +0,0 @@
namespace MoonWorks.Graphics.Font
{
public enum HorizontalAlignment
{
Left,
Center,
Right
}
public enum VerticalAlignment
{
Baseline,
Top,
Middle,
Bottom
}
}

View File

@ -1,121 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using WellspringCS;
namespace MoonWorks.Graphics.Font
{
public unsafe class Font : GraphicsResource
{
public Texture Texture { get; }
public float PixelsPerEm { get; }
public float DistanceRange { get; }
internal IntPtr Handle { get; }
private byte* StringBytes;
private int StringBytesLength;
/// <summary>
/// Loads a TTF or OTF font from a path for use in MSDF rendering.
/// Note that there must be an msdf-atlas-gen JSON and image file alongside.
/// </summary>
/// <returns></returns>
public unsafe static Font Load(
GraphicsDevice graphicsDevice,
CommandBuffer commandBuffer,
string fontPath
) {
var fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read);
var fontFileByteBuffer = NativeMemory.Alloc((nuint) fontFileStream.Length);
var fontFileByteSpan = new Span<byte>(fontFileByteBuffer, (int) fontFileStream.Length);
fontFileStream.ReadExactly(fontFileByteSpan);
fontFileStream.Close();
var atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read);
var atlasFileByteBuffer = NativeMemory.Alloc((nuint) atlasFileStream.Length);
var atlasFileByteSpan = new Span<byte>(atlasFileByteBuffer, (int) atlasFileStream.Length);
atlasFileStream.ReadExactly(atlasFileByteSpan);
atlasFileStream.Close();
var handle = Wellspring.Wellspring_CreateFont(
(IntPtr) fontFileByteBuffer,
(uint) fontFileByteSpan.Length,
(IntPtr) atlasFileByteBuffer,
(uint) atlasFileByteSpan.Length,
out float pixelsPerEm,
out float distanceRange
);
var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png"));
NativeMemory.Free(fontFileByteBuffer);
NativeMemory.Free(atlasFileByteBuffer);
return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
}
private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
{
Handle = handle;
Texture = texture;
PixelsPerEm = pixelsPerEm;
DistanceRange = distanceRange;
StringBytesLength = 32;
StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
}
public unsafe bool TextBounds(
string text,
int pixelSize,
HorizontalAlignment horizontalAlignment,
VerticalAlignment verticalAlignment,
out Wellspring.Rectangle rectangle
) {
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
if (StringBytesLength < byteCount)
{
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
}
fixed (char* chars = text)
{
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
var result = Wellspring.Wellspring_TextBounds(
Handle,
pixelSize,
(Wellspring.HorizontalAlignment) horizontalAlignment,
(Wellspring.VerticalAlignment) verticalAlignment,
(IntPtr) StringBytes,
(uint) byteCount,
out rectangle
);
if (result == 0)
{
Logger.LogWarn("Could not decode string: " + text);
return false;
}
}
return true;
}
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Texture.Dispose();
}
Wellspring.Wellspring_DestroyFont(Handle);
}
base.Dispose(disposing);
}
}
}

View File

@ -1,20 +0,0 @@
using System.Runtime.InteropServices;
using MoonWorks.Math.Float;
namespace MoonWorks.Graphics.Font
{
[StructLayout(LayoutKind.Sequential)]
public struct Vertex : IVertexType
{
public Vector3 Position;
public Vector2 TexCoord;
public Color Color;
public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[]
{
VertexElementFormat.Vector3,
VertexElementFormat.Vector2,
VertexElementFormat.Color
};
}
}

View File

@ -1,151 +0,0 @@
using System;
using System.Runtime.InteropServices;
using WellspringCS;
namespace MoonWorks.Graphics.Font
{
public unsafe class TextBatch : GraphicsResource
{
public const int INITIAL_CHAR_COUNT = 64;
public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4;
public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6;
private GraphicsDevice GraphicsDevice { get; }
public IntPtr Handle { get; }
public Buffer VertexBuffer { get; protected set; } = null;
public Buffer IndexBuffer { get; protected set; } = null;
public uint PrimitiveCount { get; protected set; }
public Font CurrentFont { get; private set; }
private byte* StringBytes;
private int StringBytesLength;
public TextBatch(GraphicsDevice device) : base(device)
{
GraphicsDevice = device;
Handle = Wellspring.Wellspring_CreateTextBatch();
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(Font font)
{
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
CurrentFont = font;
PrimitiveCount = 0;
}
// Add text with size and color to the batch
public unsafe bool Add(
string text,
int pixelSize,
Color color,
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
) {
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
if (StringBytesLength < byteCount)
{
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
}
fixed (char* chars = text)
{
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
var result = Wellspring.Wellspring_AddToTextBatch(
Handle,
pixelSize,
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
(Wellspring.HorizontalAlignment) horizontalAlignment,
(Wellspring.VerticalAlignment) verticalAlignment,
(IntPtr) StringBytes,
(uint) byteCount
);
if (result == 0)
{
Logger.LogWarn("Could not decode string: " + text);
return false;
}
}
return true;
}
// Call this after you have made all the Add calls you want, but before beginning a render pass.
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
{
Wellspring.Wellspring_GetBufferData(
Handle,
out uint vertexCount,
out IntPtr vertexDataPointer,
out uint vertexDataLengthInBytes,
out IntPtr indexDataPointer,
out uint indexDataLengthInBytes
);
if (VertexBuffer.Size < vertexDataLengthInBytes)
{
VertexBuffer.Dispose();
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
}
if (IndexBuffer.Size < indexDataLengthInBytes)
{
IndexBuffer.Dispose();
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
}
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
{
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
}
PrimitiveCount = vertexCount / 2;
}
// Call this AFTER binding your text pipeline!
public void Render(CommandBuffer commandBuffer, Math.Float.Matrix4x4 transformMatrix)
{
commandBuffer.BindFragmentSamplers(new TextureSamplerBinding(
CurrentFont.Texture,
GraphicsDevice.LinearSampler
));
commandBuffer.BindVertexBuffers(VertexBuffer);
commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
commandBuffer.DrawIndexedPrimitives(
0,
0,
PrimitiveCount,
commandBuffer.PushVertexShaderUniforms(transformMatrix),
commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange)
);
}
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
VertexBuffer.Dispose();
IndexBuffer.Dispose();
}
NativeMemory.Free(StringBytes);
Wellspring.Wellspring_DestroyTextBatch(Handle);
}
base.Dispose(disposing);
}
}
}

View File

@ -1,484 +1,138 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using MoonWorks.Video;
using RefreshCS;
using WellspringCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// GraphicsDevice manages all graphics-related concerns.
/// </summary>
public class GraphicsDevice : IDisposable
{
public IntPtr Handle { get; }
public Backend Backend { get; }
private uint windowFlags;
public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags;
// Built-in video pipeline
internal GraphicsPipeline VideoPipeline { get; }
// Built-in text shader info
public GraphicsShaderInfo TextVertexShaderInfo { get; }
public GraphicsShaderInfo TextFragmentShaderInfo { get; }
public VertexInputState TextVertexInputState { get; }
// Built-in samplers
public Sampler PointSampler { get; }
public Sampler LinearSampler { get; }
public bool IsDisposed { get; private set; }
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
private FencePool FencePool;
private CommandBufferPool CommandBufferPool;
internal GraphicsDevice(
Backend preferredBackend,
bool debugMode
) {
Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags);
if (Backend == Backend.Invalid)
{
throw new System.Exception("Could not set graphics backend!");
}
Handle = Refresh.Refresh_CreateDevice(
Conversions.BoolToByte(debugMode)
);
// TODO: check for CreateDevice fail
// Check for replacement stock shaders
string basePath = System.AppContext.BaseDirectory;
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
ShaderModule videoVertShader;
ShaderModule videoFragShader;
ShaderModule textVertShader;
ShaderModule textFragShader;
if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
{
videoVertShader = new ShaderModule(this, videoVertPath);
videoFragShader = new ShaderModule(this, videoFragPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh");
videoVertShader = new ShaderModule(this, vertStream);
videoFragShader = new ShaderModule(this, fragStream);
}
if (File.Exists(textVertPath) && File.Exists(textFragPath))
{
textVertShader = new ShaderModule(this, textVertPath);
textFragShader = new ShaderModule(this, textFragPath);
}
else
{
// use defaults
var assembly = typeof(GraphicsDevice).Assembly;
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh");
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh");
textVertShader = new ShaderModule(this, vertStream);
textFragShader = new ShaderModule(this, fragStream);
}
VideoPipeline = new GraphicsPipeline(
this,
new GraphicsPipelineCreateInfo
{
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
new ColorAttachmentDescription(
TextureFormat.R8G8B8A8,
ColorAttachmentBlendState.None
)
),
DepthStencilState = DepthStencilState.Disable,
VertexShaderInfo = GraphicsShaderInfo.Create(
videoVertShader,
"main",
0
),
FragmentShaderInfo = GraphicsShaderInfo.Create(
videoFragShader,
"main",
3
),
VertexInputState = VertexInputState.Empty,
RasterizerState = RasterizerState.CCW_CullNone,
PrimitiveType = PrimitiveType.TriangleList,
MultisampleState = MultisampleState.None
}
);
TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0);
TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1);
TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
FencePool = new FencePool(this);
CommandBufferPool = new CommandBufferPool(this);
}
/// <summary>
/// Prepares a window so that frames can be presented to it.
/// </summary>
/// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param>
/// <returns>True if successfully claimed.</returns>
public bool ClaimWindow(Window window, PresentMode presentMode)
{
if (window.Claimed)
{
Logger.LogError("Window already claimed!");
return false;
}
var success = Conversions.ByteToBool(
Refresh.Refresh_ClaimWindow(
Handle,
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()
{
var commandBuffer = CommandBufferPool.Obtain();
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
#if DEBUG
commandBuffer.ResetStateTracking();
#endif
return commandBuffer;
}
/// <summary>
/// Submits a command buffer to the GPU for processing.
/// </summary>
public void Submit(CommandBuffer commandBuffer)
{
#if DEBUG
if (commandBuffer.Submitted)
{
throw new System.InvalidOperationException("Command buffer already submitted!");
}
#endif
Refresh.Refresh_Submit(
Handle,
commandBuffer.Handle
);
CommandBufferPool.Return(commandBuffer);
#if DEBUG
commandBuffer.Submitted = true;
#endif
}
/// <summary>
/// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission.
/// </summary>
/// <returns></returns>
public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
{
var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence(
Handle,
commandBuffer.Handle
);
var fence = FencePool.Obtain();
fence.SetHandle(fenceHandle);
return fence;
}
/// <summary>
/// Wait for the graphics device to become idle.
/// </summary>
public void Wait()
{
Refresh.Refresh_Wait(Handle);
}
/// <summary>
/// Waits for the given fence to become signaled.
/// </summary>
public unsafe void WaitForFences(Fence fence)
{
var handlePtr = stackalloc nint[1];
handlePtr[0] = fence.Handle;
Refresh.Refresh_WaitForFences(
Handle,
1,
1,
(nint) handlePtr
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(
Fence fenceOne,
Fence fenceTwo,
bool waitAll
) {
var handlePtr = stackalloc nint[2];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
2,
(nint) handlePtr
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(
Fence fenceOne,
Fence fenceTwo,
Fence fenceThree,
bool waitAll
) {
var handlePtr = stackalloc nint[3];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle;
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
3,
(nint) handlePtr
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(
Fence fenceOne,
Fence fenceTwo,
Fence fenceThree,
Fence fenceFour,
bool waitAll
) {
var handlePtr = stackalloc nint[4];
handlePtr[0] = fenceOne.Handle;
handlePtr[1] = fenceTwo.Handle;
handlePtr[2] = fenceThree.Handle;
handlePtr[3] = fenceFour.Handle;
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
4,
(nint) handlePtr
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(Fence[] fences, bool waitAll)
{
var handlePtr = stackalloc nint[fences.Length];
for (var i = 0; i < fences.Length; i += 1)
{
handlePtr[i] = fences[i].Handle;
}
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToByte(waitAll),
4,
(nint) handlePtr
);
}
/// <summary>
/// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing.
/// </summary>
/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
public bool QueryFence(Fence fence)
{
var result = Refresh.Refresh_QueryFence(Handle, fence.Handle);
if (result < 0)
{
throw new InvalidOperationException("The graphics device has been lost.");
}
return result != 0;
}
/// <summary>
/// Release reference to an acquired fence, enabling it to be reused.
/// </summary>
public void ReleaseFence(Fence fence)
{
Refresh.Refresh_ReleaseFence(Handle, fence.Handle);
fence.Handle = IntPtr.Zero;
FencePool.Return(fence);
}
private TextureFormat GetSwapchainFormat(Window window)
{
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
}
internal void AddResourceReference(GCHandle resourceReference)
{
lock (resources)
{
resources.Add(resourceReference);
}
}
internal void RemoveResourceReference(GCHandle resourceReference)
{
lock (resources)
{
resources.Remove(resourceReference);
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
lock (resources)
{
// Dispose video players first to avoid race condition on threaded decoding
foreach (var resource in resources)
{
if (resource.Target is VideoPlayer player)
{
player.Dispose();
}
}
// Dispose everything else
foreach (var resource in resources)
{
if (resource.Target is IDisposable disposable)
{
disposable.Dispose();
}
}
resources.Clear();
}
}
Refresh.Refresh_DestroyDevice(Handle);
IsDisposed = true;
}
}
~GraphicsDevice()
{
// 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);
}
}
public class GraphicsDevice : IDisposable
{
public IntPtr Handle { get; }
public bool IsDisposed { get; private set; }
private readonly Queue<CommandBuffer> commandBufferPool;
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
public GraphicsDevice(
IntPtr deviceWindowHandle,
Refresh.PresentMode presentMode,
bool debugMode,
int initialCommandBufferPoolSize = 4
) {
var presentationParameters = new Refresh.PresentationParameters
{
deviceWindowHandle = deviceWindowHandle,
presentMode = presentMode
};
Handle = Refresh.Refresh_CreateDevice(
presentationParameters,
Conversions.BoolToByte(debugMode)
);
commandBufferPool = new Queue<CommandBuffer>(initialCommandBufferPoolSize);
for (var i = 0; i < initialCommandBufferPoolSize; i += 1)
{
commandBufferPool.Enqueue(new CommandBuffer(this));
}
}
public CommandBuffer AcquireCommandBuffer()
{
var commandBufferHandle = Refresh.Refresh_AcquireCommandBuffer(Handle, 0);
if (commandBufferPool.Count == 0)
{
commandBufferPool.Enqueue(new CommandBuffer(this));
}
var commandBuffer = commandBufferPool.Dequeue();
commandBuffer.Handle = commandBufferHandle;
return commandBuffer;
}
public unsafe void Submit(params CommandBuffer[] commandBuffers)
{
var commandBufferPtrs = stackalloc IntPtr[commandBuffers.Length];
for (var i = 0; i < commandBuffers.Length; i += 1)
{
commandBufferPtrs[i] = commandBuffers[i].Handle;
}
Refresh.Refresh_Submit(
Handle,
(uint) commandBuffers.Length,
(IntPtr) commandBufferPtrs
);
// return to pool
for (var i = 0; i < commandBuffers.Length; i += 1)
{
commandBuffers[i].Handle = IntPtr.Zero;
commandBufferPool.Enqueue(commandBuffers[i]);
}
}
public void Wait()
{
Refresh.Refresh_Wait(Handle);
}
internal void AddResourceReference(WeakReference<GraphicsResource> resourceReference)
{
lock (resources)
{
resources.Add(resourceReference);
}
}
internal void RemoveResourceReference(WeakReference<GraphicsResource> resourceReference)
{
lock (resources)
{
resources.Remove(resourceReference);
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
lock (resources)
{
foreach (var resource in resources)
{
if (resource.TryGetTarget(out var target))
{
target.Dispose();
}
}
resources.Clear();
}
Refresh.Refresh_DestroyDevice(Handle);
}
IsDisposed = true;
}
}
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
~GraphicsDevice()
{
// 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);
}
}
}

View File

@ -1,55 +1,52 @@
using System;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
// TODO: give this a Name property for debugging use
public abstract class GraphicsResource : IDisposable
{
public GraphicsDevice Device { get; }
public abstract class GraphicsResource : IDisposable
{
public GraphicsDevice Device { get; }
public IntPtr Handle { get; protected set; }
private GCHandle SelfReference;
public bool IsDisposed { get; private set; }
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
public bool IsDisposed { get; private set; }
private WeakReference<GraphicsResource> selfReference;
protected GraphicsResource(GraphicsDevice device)
{
Device = device;
public GraphicsResource(GraphicsDevice device)
{
Device = device;
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
Device.AddResourceReference(SelfReference);
}
selfReference = new WeakReference<GraphicsResource>(this);
Device.AddResourceReference(selfReference);
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Device.RemoveResourceReference(SelfReference);
SelfReference.Free();
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
QueueDestroyFunction(Device.Handle, Handle);
IsDisposed = true;
}
}
if (selfReference != null)
{
Device.RemoveResourceReference(selfReference);
selfReference = null;
}
~GraphicsResource()
{
#if DEBUG
// If you see this log message, you leaked a graphics resource without disposing it!
// We'll try to clean it up for you but you really should fix this.
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
#endif
IsDisposed = true;
}
}
Dispose(false);
}
~GraphicsResource()
{
// 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);
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -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; }
}
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values, ranging from 0 to 1, using

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing four 8-bit unsigned integer values, ranging from 0 to 255.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -16,10 +16,10 @@
#region Using Statements
using System;
using MoonWorks.Math.Float;
using MoonWorks.Math;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct HalfSingle : IPackedVector<ushort>, IEquatable<HalfSingle>, IPackedVector
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -19,7 +19,7 @@ using System;
using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
internal static class HalfTypeHelper
{
@ -104,7 +104,7 @@ namespace MoonWorks.Graphics.PackedVector
internal static float Convert(ushort value)
{
uint rst;
uint mantissa = (uint) (value & 1023);
uint mantissa = (uint)(value & 1023);
uint exp = 0xfffffff2;
if ((value & -33792) == 0)

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -16,10 +16,10 @@
#region Using Statements
using System;
using MoonWorks.Math.Float;
using MoonWorks.Math;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct HalfVector2 : IPackedVector<uint>, IPackedVector, IEquatable<HalfVector2>
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -16,10 +16,10 @@
#region Using Statements
using System;
using MoonWorks.Math.Float;
using MoonWorks.Math;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing four 16-bit floating-point values.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -14,9 +14,9 @@
#endregion
using MoonWorks.Math.Float;
using MoonWorks.Math;
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.packedvector.ipackedvector.aspx
public interface IPackedVector

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct NormalizedByte2 : IPackedVector<ushort>, IEquatable<NormalizedByte2>
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct NormalizedByte4 : IPackedVector<uint>, IEquatable<NormalizedByte4>
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct NormalizedShort2 : IPackedVector<uint>, IEquatable<NormalizedShort2>
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct NormalizedShort4 : IPackedVector<ulong>, IEquatable<NormalizedShort4>
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
public struct Short2 : IPackedVector<uint>, IEquatable<Short2>
{

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -17,10 +17,9 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics.PackedVector
namespace MoonWorks.Graphics
{
/// <summary>
/// Packed vector type containing four 16-bit signed integer values.
@ -193,7 +192,7 @@ namespace MoonWorks.Graphics.PackedVector
static ulong Pack(float x, float y, float z, float w)
{
return (ulong) (
((long) System.Math.Round(MathHelper.Clamp(x, -32768, 32767)) & 0xFFFF) |
((long) System.Math.Round(MathHelper.Clamp(x, -32768, 32767)) & 0xFFFF ) |
(((long) System.Math.Round(MathHelper.Clamp(y, -32768, 32767)) << 16) & 0xFFFF0000) |
(((long) System.Math.Round(MathHelper.Clamp(z, -32768, 32767)) << 32) & 0xFFFF00000000) |
((long) System.Math.Round(MathHelper.Clamp(w, -32768, 32767)) << 48)

View File

@ -1,307 +1,302 @@
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
* reference the RefreshCS namespace when using MoonWorks.Graphics
*/
namespace MoonWorks.Graphics
{
public enum PrimitiveType
{
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip
}
public enum PresentMode
{
Immediate,
Mailbox,
FIFO,
FIFORelaxed
}
/// <summary>
/// Describes the operation that a render pass will use when loading a render target.
/// </summary>
public enum LoadOp
{
Load,
Clear,
DontCare
}
public enum PrimitiveType
{
PointList,
LineList,
LineStrip,
TriangleList,
TriangleStrip
}
/// <summary>
/// Describes the operation that a render pass will use when storing a render target.
/// </summary>
public enum StoreOp
{
Store,
DontCare
}
/// <summary>
/// Describes the operation that a render pass will use when loading a render target.
/// </summary>
public enum LoadOp
{
Load,
Clear,
DontCare
}
[Flags]
public enum ClearOptionsFlags : uint
{
Color = 1,
Depth = 2,
Stencil = 4,
DepthStencil = Depth | Stencil,
All = Color | Depth | Stencil
}
/// <summary>
/// Describes the operation that a render pass will use when storing a render target.
/// </summary>
public enum StoreOp
{
Store,
DontCare
}
public enum IndexElementSize
{
Sixteen,
ThirtyTwo
}
[Flags]
public enum ClearOptionsFlags : uint
{
Color = 1,
Depth = 2,
Stencil = 4,
DepthStencil = Depth | Stencil,
All = Color | Depth | Stencil
}
public enum TextureFormat
{
R8G8B8A8,
B8G8R8A8,
R5G6B5,
A1R5G5B5,
B4G4R4A4,
A2R10G10B10,
R16G16,
R16G16B16A16,
R8,
BC1,
BC2,
BC3,
BC7,
R8G8_SNORM,
R8G8B8A8_SNORM,
R16_SFLOAT,
R16G16_SFLOAT,
R16G16B16A16_SFLOAT,
R32_SFLOAT,
R32G32_SFLOAT,
R32G32B32A32_SFLOAT,
public enum IndexElementSize
{
Sixteen,
ThirtyTwo
}
R8_UINT,
R8G8_UINT,
R8G8B8A8_UINT,
R16_UINT,
R16G16_UINT,
R16G16B16A16_UINT,
D16,
D32,
D16S8,
D32S8
}
public enum TextureFormat
{
R8G8B8A8,
R5G6B5,
A1R5G5B5,
B4G4R4A4,
BC1,
BC2,
BC3,
R8G8_SNORM,
R8G8B8A8_SNORM,
A2R10G10B10,
R16G16,
R16G16B16A16,
R8,
R32_SFLOAT,
R32G32_SFLOAT,
R32G32B32A32_SFLOAT,
R16_SFLOAT,
R16G16_SFLOAT,
R16G16B16A16_SFLOAT,
D16,
D32,
D16S8,
D32S8
}
[Flags]
public enum TextureUsageFlags : uint
{
Sampler = 1,
ColorTarget = 2,
DepthStencilTarget = 4,
Compute = 8
}
[Flags]
public enum TextureUsageFlags : uint
{
Sampler = 1,
ColorTarget = 2,
DepthStencilTarget = 4
}
public enum SampleCount
{
One,
Two,
Four,
Eight
}
public enum SampleCount
{
One,
Two,
Four,
Eight,
Sixteen,
ThirtyTwo,
SixtyFour
}
public enum CubeMapFace : uint
{
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
}
public enum CubeMapFace : uint
{
PositiveX,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
}
[Flags]
public enum BufferUsageFlags : uint
{
Vertex = 1,
Index = 2,
Compute = 4,
Indirect = 8
}
[Flags]
public enum BufferUsageFlags : uint
{
Vertex = 1,
Index = 2,
Compute = 4
}
public enum VertexElementFormat
{
UInt,
Float,
Vector2,
Vector3,
Vector4,
Color,
Byte4,
Short2,
Short4,
NormalizedShort2,
NormalizedShort4,
HalfVector2,
HalfVector4
}
public enum VertexElementFormat
{
Single,
Vector2,
Vector3,
Vector4,
Color,
Byte4,
Short2,
Short4,
NormalizedShort2,
NormalizedShort4,
HalfVector2,
HalfVector4
}
public enum VertexInputRate
{
Vertex,
Instance
}
public enum VertexInputRate
{
Vertex,
Instance
}
public enum FillMode
{
Fill,
Line
}
public enum FillMode
{
Fill,
Line,
Point
}
public enum CullMode
{
None,
Front,
Back
}
public enum CullMode
{
None,
Front,
Back,
FrontAndBack
}
public enum FrontFace
{
CounterClockwise,
Clockwise
}
public enum FrontFace
{
CounterClockwise,
Clockwise
}
public enum CompareOp
{
Never,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
}
public enum CompareOp
{
Never,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
}
public enum StencilOp
{
Keep,
Zero,
Replace,
IncrementAndClamp,
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
}
public enum StencilOp
{
Keep,
Zero,
Replace,
IncrementAndClamp,
DecrementAndClamp,
Invert,
IncrementAndWrap,
DecrementAndWrap
}
public enum BlendOp
{
Add,
Subtract,
ReverseSubtract,
Min,
Max
}
public enum BlendOp
{
Add,
Subtract,
ReverseSubtract,
Min,
Max
}
public enum BlendFactor
{
Zero,
One,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
ConstantColor,
OneMinusConstantColor,
SourceAlphaSaturate
}
public enum LogicOp
{
Clear,
And,
AndReverse,
Copy,
AndInverted,
NoOp,
Xor,
Or,
Nor,
Equivalent,
Invert,
OrReverse,
CopyInverted,
OrInverted,
Nand,
Set
}
[Flags]
public enum ColorComponentFlags : uint
{
R = 1,
G = 2,
B = 4,
A = 8,
public enum BlendFactor
{
Zero,
One,
SourceColor,
OneMinusSourceColor,
DestinationColor,
OneMinusDestinationColor,
SourceAlpha,
OneMinusSourceAlpha,
DestinationAlpha,
OneMinusDestinationAlpha,
ConstantColor,
OneMinusConstantColor,
ConstantAlpha,
OneMinusConstantAlpha,
SourceAlphaSaturate,
SourceOneColor,
OneMinusSourceOneColor,
SourceOneAlpha,
OneMinusSourceOneAlpha
}
RG = R | G,
RB = R | B,
RA = R | A,
GB = G | B,
GA = G | A,
BA = B | A,
[Flags]
public enum ColorComponentFlags : uint
{
R = 1,
G = 2,
B = 4,
A = 8,
RGB = R | G | B,
RGA = R | G | A,
GBA = G | B | A,
RG = R | G,
RB = R | B,
RA = R | A,
GB = G | B,
GA = G | A,
BA = B | A,
RGBA = R | G | B | A,
None = 0
}
RGB = R | G | B,
RGA = R | G | A,
GBA = G | B | A,
public enum Filter
{
Nearest,
Linear
}
RGBA = R | G | B | A,
None = 0
}
public enum SamplerMipmapMode
{
Nearest,
Linear
}
public enum ShaderStageType
{
Vertex,
Fragment
}
public enum SamplerAddressMode
{
Repeat,
MirroredRepeat,
ClampToEdge,
ClampToBorder
}
public enum Filter
{
Nearest,
Linear,
Cubic
}
public enum BorderColor
{
FloatTransparentBlack,
IntTransparentBlack,
FloatOpaqueBlack,
IntOpaqueBlack,
FloatOpaqueWhite,
IntOpaqueWhite
}
public enum SamplerMipmapMode
{
Nearest,
Linear
}
public enum Backend
{
DontCare,
Vulkan,
PS5,
Invalid
}
public enum SamplerAddressMode
{
Repeat,
MirroredRepeat,
ClampToEdge,
ClampToBorder
}
public enum BorderColor
{
FloatTransparentBlack,
IntTransparentBlack,
FloatOpaqueBlack,
IntOpaqueBlack,
FloatOpaqueWhite,
IntOpaqueWhite
}
}

View File

@ -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);
}
}

View File

@ -6,352 +6,115 @@ using System.Runtime.InteropServices;
*/
namespace MoonWorks.Graphics
{
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilValue
{
public float Depth;
public uint Stencil;
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilValue
{
public float Depth;
public uint Stencil;
public DepthStencilValue(float depth, uint stencil)
{
Depth = depth;
Stencil = stencil;
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.DepthStencilValue ToRefresh()
{
return new Refresh.DepthStencilValue
{
depth = Depth,
stencil = Stencil
};
}
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.DepthStencilValue ToRefresh()
{
return new Refresh.DepthStencilValue
{
depth = Depth,
stencil = Stencil
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int X;
public int Y;
public int W;
public int H;
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int X;
public int Y;
public int W;
public int H;
// FIXME: can we do an unsafe cast somehow?
public Refresh.Rect ToRefresh()
{
return new Refresh.Rect
{
x = X,
y = Y,
w = W,
h = H
};
}
}
public Rect(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
[StructLayout(LayoutKind.Sequential)]
public struct Viewport
{
public float X;
public float Y;
public float W;
public float H;
public float MinDepth;
public float MaxDepth;
}
public Rect(int w, int h)
{
X = 0;
Y = 0;
W = w;
H = h;
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexBinding
{
public uint Binding;
public uint Stride;
public VertexInputRate InputRate;
}
// FIXME: can we do an unsafe cast somehow?
public Refresh.Rect ToRefresh()
{
return new Refresh.Rect
{
x = X,
y = Y,
w = W,
h = H
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexAttribute
{
public uint Location;
public uint Binding;
public VertexElementFormat Format;
public uint Offset;
}
[StructLayout(LayoutKind.Sequential)]
public struct Viewport
{
public float X;
public float Y;
public float W;
public float H;
public float MinDepth;
public float MaxDepth;
[StructLayout(LayoutKind.Sequential)]
public struct ColorTargetDescription
{
public TextureFormat Format;
public SampleCount MultisampleCount;
public LoadOp LoadOp;
public StoreOp StoreOp;
}
public Viewport(float w, float h)
{
X = 0;
Y = 0;
W = w;
H = h;
MinDepth = 0;
MaxDepth = 1;
}
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilTargetDescription
{
public TextureFormat Format;
public LoadOp LoadOp;
public StoreOp StoreOp;
public LoadOp StencilLoadOp;
public StoreOp StencilStoreOp;
}
public Viewport(float x, float y, float w, float h)
{
X = x;
Y = y;
W = w;
H = h;
MinDepth = 0;
MaxDepth = 1;
}
[StructLayout(LayoutKind.Sequential)]
public struct StencilOpState
{
public StencilOp FailOp;
public StencilOp PassOp;
public StencilOp DepthFailOp;
public CompareOp CompareOp;
public uint CompareMask;
public uint WriteMask;
public uint Reference;
public Viewport(float x, float y, float w, float h, float minDepth, float maxDepth)
{
X = x;
Y = y;
W = w;
H = h;
MinDepth = minDepth;
MaxDepth = maxDepth;
}
public Refresh.Viewport ToRefresh()
{
return new Refresh.Viewport
{
x = X,
y = Y,
w = W,
h = H,
minDepth = MinDepth,
maxDepth = MaxDepth
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexBinding
{
public uint Binding;
public uint Stride;
public VertexInputRate InputRate;
public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
{
return new VertexBinding
{
Binding = binding,
InputRate = inputRate,
Stride = (uint) Marshal.SizeOf<T>()
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct VertexAttribute
{
public uint Location;
public uint Binding;
public VertexElementFormat Format;
public uint Offset;
}
[StructLayout(LayoutKind.Sequential)]
public struct StencilOpState
{
public StencilOp FailOp;
public StencilOp PassOp;
public StencilOp DepthFailOp;
public CompareOp CompareOp;
public uint CompareMask;
public uint WriteMask;
public uint Reference;
// FIXME: can we do an explicit cast here?
public Refresh.StencilOpState ToRefresh()
{
return new Refresh.StencilOpState
{
failOp = (Refresh.StencilOp) FailOp,
passOp = (Refresh.StencilOp) PassOp,
depthFailOp = (Refresh.StencilOp) DepthFailOp,
compareOp = (Refresh.CompareOp) CompareOp,
compareMask = CompareMask,
writeMask = WriteMask,
reference = Reference
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentInfo
{
public Texture Texture;
public uint Depth;
public uint Layer;
public uint Level;
public Color ClearColor;
public LoadOp LoadOp;
public StoreOp StoreOp;
public ColorAttachmentInfo(
Texture texture,
Color clearColor,
StoreOp storeOp = StoreOp.Store
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
ClearColor = clearColor;
LoadOp = LoadOp.Clear;
StoreOp = storeOp;
}
public ColorAttachmentInfo(
Texture texture,
LoadOp loadOp = LoadOp.DontCare,
StoreOp storeOp = StoreOp.Store
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
ClearColor = Color.White;
LoadOp = loadOp;
StoreOp = storeOp;
}
public Refresh.ColorAttachmentInfo ToRefresh()
{
return new Refresh.ColorAttachmentInfo
{
texture = Texture.Handle,
depth = Depth,
layer = Layer,
level = Level,
clearColor = new Refresh.Vec4
{
x = ClearColor.R / 255f,
y = ClearColor.G / 255f,
z = ClearColor.B / 255f,
w = ClearColor.A / 255f
},
loadOp = (Refresh.LoadOp) LoadOp,
storeOp = (Refresh.StoreOp) StoreOp
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilAttachmentInfo
{
public Texture Texture;
public uint Depth;
public uint Layer;
public uint Level;
public DepthStencilValue DepthStencilClearValue;
public LoadOp LoadOp;
public StoreOp StoreOp;
public LoadOp StencilLoadOp;
public StoreOp StencilStoreOp;
public DepthStencilAttachmentInfo(
Texture texture,
DepthStencilValue clearValue,
StoreOp depthStoreOp = StoreOp.Store,
StoreOp stencilStoreOp = StoreOp.Store
)
{
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
DepthStencilClearValue = clearValue;
LoadOp = LoadOp.Clear;
StoreOp = depthStoreOp;
StencilLoadOp = LoadOp.Clear;
StencilStoreOp = stencilStoreOp;
}
public DepthStencilAttachmentInfo(
Texture texture,
LoadOp loadOp = LoadOp.DontCare,
StoreOp storeOp = StoreOp.Store,
LoadOp stencilLoadOp = LoadOp.DontCare,
StoreOp stencilStoreOp = StoreOp.Store
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
DepthStencilClearValue = new DepthStencilValue();
LoadOp = loadOp;
StoreOp = storeOp;
StencilLoadOp = stencilLoadOp;
StencilStoreOp = stencilStoreOp;
}
public DepthStencilAttachmentInfo(
Texture texture,
DepthStencilValue depthStencilValue,
LoadOp loadOp,
StoreOp storeOp,
LoadOp stencilLoadOp,
StoreOp stencilStoreOp
) {
Texture = texture;
Depth = 0;
Layer = 0;
Level = 0;
DepthStencilClearValue = depthStencilValue;
LoadOp = loadOp;
StoreOp = storeOp;
StencilLoadOp = stencilLoadOp;
StencilStoreOp = stencilStoreOp;
}
public Refresh.DepthStencilAttachmentInfo ToRefresh()
{
return new Refresh.DepthStencilAttachmentInfo
{
texture = Texture.Handle,
depth = Depth,
layer = Layer,
level = Level,
depthStencilClearValue = DepthStencilClearValue.ToRefresh(),
loadOp = (Refresh.LoadOp) LoadOp,
storeOp = (Refresh.StoreOp) StoreOp,
stencilLoadOp = (Refresh.LoadOp) StencilLoadOp,
stencilStoreOp = (Refresh.StoreOp) StencilStoreOp
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentDescription
{
public TextureFormat Format;
public ColorAttachmentBlendState BlendState;
public ColorAttachmentDescription(
TextureFormat format,
ColorAttachmentBlendState blendState
) {
Format = format;
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;
}
}
// FIXME: can we do an explicit cast here?
public Refresh.StencilOpState ToRefresh()
{
return new Refresh.StencilOpState
{
failOp = (Refresh.StencilOp)FailOp,
passOp = (Refresh.StencilOp)PassOp,
depthFailOp = (Refresh.StencilOp)DepthFailOp,
compareOp = (Refresh.CompareOp)CompareOp,
compareMask = CompareMask,
writeMask = WriteMask,
reference = Reference
};
}
}
}

View File

@ -1,141 +1,143 @@
using System;
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// Buffers are generic data containers that can be used by the GPU.
/// </summary>
public class Buffer : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
/// <summary>
/// Buffers are generic data containers that can be used by the GPU.
/// </summary>
public class Buffer : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
/// <summary>
/// Size in bytes.
/// </summary>
public uint Size { get; }
/// <summary>
/// Creates a buffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
public Buffer(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateBuffer(
device.Handle,
(Refresh.BufferUsageFlags) usageFlags,
sizeInBytes
);
}
/// <summary>
/// Creates a buffer of appropriate size given a type and element count.
/// </summary>
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
/// <param name="device">The GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
/// <returns></returns>
public unsafe static Buffer Create<T>(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint elementCount
) where T : unmanaged
{
return new Buffer(
device,
usageFlags,
(uint) Marshal.SizeOf<T>() * elementCount
);
}
/// <summary>
/// Asynchronously copies data into the buffer.
/// </summary>
/// <param name="data">An array of data to copy into the buffer.</param>
/// <param name="offsetInElements">Specifies where to start copying out of the array.</param>
/// <param name="lengthInElements">Specifies how many elements to copy.</param>
public unsafe void SetData<T>(
T[] data,
uint offsetInElements,
uint lengthInElements
) where T : unmanaged
{
var elementSize = Marshal.SizeOf<T>();
/// <summary>
/// Creates a buffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
public Buffer(
GraphicsDevice device,
BufferUsageFlags usageFlags,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateBuffer(
device.Handle,
(Refresh.BufferUsageFlags) usageFlags,
sizeInBytes
);
Size = sizeInBytes;
}
fixed (T* ptr = &data[0])
{
Refresh.Refresh_SetBufferData(
Device.Handle,
Handle,
(uint) (offsetInElements * elementSize),
(IntPtr) ptr,
(uint) (lengthInElements * elementSize)
);
}
}
/// <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.
/// </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>(
Span<T> data,
uint dataLengthInBytes
) where T : unmanaged
{
#if DEBUG
if (dataLengthInBytes > Size)
{
Logger.LogWarn("Requested too many bytes from buffer!");
}
/// <summary>
/// Asynchronously copies data into the buffer.
/// This variant of this method copies the entire array.
/// </summary>
/// <param name="data">An array of data to copy.</param>
public unsafe void SetData<T>(
T[] data
) where T : unmanaged
{
fixed (T* ptr = &data[0])
{
Refresh.Refresh_SetBufferData(
Device.Handle,
Handle,
0,
(IntPtr)ptr,
(uint) (data.Length * Marshal.SizeOf<T>())
);
}
}
if (dataLengthInBytes > data.Length * Marshal.SizeOf<T>())
{
Logger.LogWarn("Data length is larger than the provided Span!");
}
#endif
/// <summary>
/// Asynchronously copies data into the buffer.
/// </summary>
/// <param name="data">A pointer to an array.</param>
/// <param name="offsetInBytes">Specifies where to start copying the data, in bytes.</param>
/// <param name="dataLengthInBytes">Specifies how many bytes of data to copy.</param>
public void SetData(
IntPtr data,
uint offsetInBytes,
uint dataLengthInBytes
) {
Refresh.Refresh_SetBufferData(
Device.Handle,
Handle,
offsetInBytes,
data,
dataLengthInBytes
);
}
fixed (T* ptr = data)
{
Refresh.Refresh_GetBufferData(
Device.Handle,
Handle,
(IntPtr) ptr,
dataLengthInBytes
);
}
}
/// <summary>
/// Asynchronously copies data into the buffer.
/// </summary>
/// <param name="data">A pointer to an array.</param>
/// <param name="offsetInBytes">Specifies where to start copying the data, in bytes.</param>
/// <param name="dataLengthInBytes">Specifies how many bytes of data to copy.</param>
public unsafe void SetData<T>(
T* data,
uint offsetInElements,
uint lengthInElements
) where T : unmanaged {
var elementSize = Marshal.SizeOf<T>();
Refresh.Refresh_SetBufferData(
Device.Handle,
Handle,
(uint) (offsetInElements * elementSize),
(IntPtr) data,
(uint) (lengthInElements * elementSize)
);
}
/// <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);
}
}
/// <summary>
/// Reads data out of a buffer and into an array.
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
/// </summary>
/// <param name="data">The array 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
{
fixed (T* ptr = &data[0])
{
Refresh.Refresh_GetBufferData(
Device.Handle,
Handle,
(IntPtr)ptr,
dataLengthInBytes
);
}
}
}
}

View File

@ -1,41 +1,59 @@
using RefreshCS;
using System;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Compute pipelines perform arbitrary parallel processing on input data.
/// </summary>
public class ComputePipeline : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
public class ComputePipeline : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
public ComputeShaderInfo ComputeShaderInfo { get; }
public ShaderStageState ComputeShaderState { get; }
public unsafe ComputePipeline(
GraphicsDevice device,
ComputeShaderInfo computeShaderInfo
) : base(device)
{
var refreshComputeShaderInfo = new Refresh.ComputeShaderInfo
{
entryPointName = computeShaderInfo.EntryPointName,
shaderModule = computeShaderInfo.ShaderModule.Handle,
uniformBufferSize = computeShaderInfo.UniformBufferSize,
bufferBindingCount = computeShaderInfo.BufferBindingCount,
imageBindingCount = computeShaderInfo.ImageBindingCount
};
public unsafe ComputePipeline(
GraphicsDevice device,
ShaderStageState computeShaderState,
uint bufferBindingCount,
uint imageBindingCount
) : base(device) {
var computePipelineLayoutCreateInfo = new Refresh.ComputePipelineLayoutCreateInfo
{
bufferBindingCount = bufferBindingCount,
imageBindingCount = imageBindingCount
};
Handle = Refresh.Refresh_CreateComputePipeline(
device.Handle,
refreshComputeShaderInfo
);
if (Handle == IntPtr.Zero)
{
throw new Exception("Could not create compute pipeline!");
}
var computePipelineCreateInfo = new Refresh.ComputePipelineCreateInfo
{
pipelineLayoutCreateInfo = computePipelineLayoutCreateInfo,
computeShaderState = new Refresh.ShaderStageState
{
entryPointName = computeShaderState.EntryPointName,
shaderModule = computeShaderState.ShaderModule.Handle,
uniformBufferSize = computeShaderState.UniformBufferSize
}
};
ComputeShaderInfo = computeShaderInfo;
}
}
Handle = Refresh.Refresh_CreateComputePipeline(
device.Handle,
computePipelineCreateInfo
);
ComputeShaderState = computeShaderState;
}
public unsafe uint PushComputeShaderUniforms<T>(
params T[] uniforms
) where T : unmanaged
{
fixed (T* ptr = &uniforms[0])
{
return Refresh.Refresh_PushComputeShaderUniforms(
Device.Handle,
Handle,
(IntPtr) ptr,
(uint) (uniforms.Length * Marshal.SizeOf<T>())
);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A framebuffer is a collection of render targets that is rendered to during a render pass.
/// </summary>
public class Framebuffer : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyFramebuffer;
public RenderTarget DepthStencilTarget { get; }
private RenderTarget[] colorTargets { get; }
public IEnumerable<RenderTarget> ColorTargets => colorTargets;
public RenderPass RenderPass { get; }
/// <summary>
/// Creates a framebuffer.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the framebuffer.</param>
/// <param name="height">The height of the framebuffer.</param>
/// <param name="renderPass">The reference render pass for the framebuffer.</param>
/// <param name="depthStencilTarget">The depth stencil target. Can be null.</param>
/// <param name="colorTargets">Anywhere from 0-4 color targets can be provided.</param>
public unsafe Framebuffer(
GraphicsDevice device,
uint width,
uint height,
RenderPass renderPass,
RenderTarget depthStencilTarget,
params RenderTarget[] colorTargets
) : base(device)
{
IntPtr[] colorTargetHandles = new IntPtr[colorTargets.Length];
for (var i = 0; i < colorTargets.Length; i += 1)
{
colorTargetHandles[i] = colorTargets[i].Handle;
}
IntPtr depthStencilTargetHandle;
if (depthStencilTarget == null)
{
depthStencilTargetHandle = IntPtr.Zero;
}
else
{
depthStencilTargetHandle = depthStencilTarget.Handle;
}
fixed (IntPtr* colorTargetHandlesPtr = colorTargetHandles)
{
Refresh.FramebufferCreateInfo framebufferCreateInfo = new Refresh.FramebufferCreateInfo
{
width = width,
height = height,
colorTargetCount = (uint) colorTargets.Length,
pColorTargets = (IntPtr) colorTargetHandlesPtr,
depthStencilTarget = depthStencilTargetHandle,
renderPass = renderPass.Handle
};
Handle = Refresh.Refresh_CreateFramebuffer(device.Handle, framebufferCreateInfo);
}
DepthStencilTarget = depthStencilTarget;
this.colorTargets = new RenderTarget[colorTargets.Length];
for (var i = 0; i < colorTargets.Length; i++)
{
this.colorTargets[i] = colorTargets[i];
}
RenderPass = renderPass;
}
}
}

View File

@ -1,125 +1,162 @@
using System;
using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
/// These pipelines are bound before draw calls are issued.
/// </summary>
public class GraphicsPipeline : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
/// <summary>
/// Graphics pipelines encapsulate all of the render state in a single object.
/// These pipelines are bound before draw calls are issued.
/// </summary>
public class GraphicsPipeline : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
public GraphicsShaderInfo VertexShaderInfo { get; }
public GraphicsShaderInfo FragmentShaderInfo { get; }
public SampleCount SampleCount { get; }
public ShaderStageState VertexShaderState { get; }
public ShaderStageState FragmentShaderState { get; }
public RenderPass RenderPass { get; }
#if DEBUG
internal GraphicsPipelineAttachmentInfo AttachmentInfo { get; }
#endif
public unsafe GraphicsPipeline(
GraphicsDevice device,
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
) : base(device)
{
ColorBlendState colorBlendState = graphicsPipelineCreateInfo.ColorBlendState;
DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState;
ShaderStageState vertexShaderState = graphicsPipelineCreateInfo.VertexShaderState;
ShaderStageState fragmentShaderState = graphicsPipelineCreateInfo.FragmentShaderState;
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
GraphicsPipelineLayoutInfo pipelineLayoutInfo = graphicsPipelineCreateInfo.PipelineLayoutInfo;
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
ViewportState viewportState = graphicsPipelineCreateInfo.ViewportState;
RenderPass renderPass = graphicsPipelineCreateInfo.RenderPass;
public unsafe GraphicsPipeline(
GraphicsDevice device,
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
) : base(device)
{
DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState;
GraphicsShaderInfo vertexShaderInfo = graphicsPipelineCreateInfo.VertexShaderInfo;
GraphicsShaderInfo fragmentShaderInfo = graphicsPipelineCreateInfo.FragmentShaderInfo;
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
GraphicsPipelineAttachmentInfo attachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
BlendConstants blendConstants = graphicsPipelineCreateInfo.BlendConstants;
var vertexAttributesHandle = GCHandle.Alloc(
vertexInputState.VertexAttributes,
GCHandleType.Pinned
);
var vertexBindingsHandle = GCHandle.Alloc(
vertexInputState.VertexBindings,
GCHandleType.Pinned
);
var viewportHandle = GCHandle.Alloc(
viewportState.Viewports,
GCHandleType.Pinned
);
var scissorHandle = GCHandle.Alloc(
viewportState.Scissors,
GCHandleType.Pinned
);
var vertexAttributesHandle = GCHandle.Alloc(
vertexInputState.VertexAttributes,
GCHandleType.Pinned
);
var vertexBindingsHandle = GCHandle.Alloc(
vertexInputState.VertexBindings,
GCHandleType.Pinned
);
var colorTargetBlendStates = stackalloc Refresh.ColorTargetBlendState[
colorBlendState.ColorTargetBlendStates.Length
];
var colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
(int) attachmentInfo.ColorAttachmentDescriptions.Length
];
for (var i = 0; i < colorBlendState.ColorTargetBlendStates.Length; i += 1)
{
colorTargetBlendStates[i] = colorBlendState.ColorTargetBlendStates[i].ToRefreshColorTargetBlendState();
}
for (var i = 0; i < attachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
{
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
}
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
refreshGraphicsPipelineCreateInfo.colorBlendState.logicOpEnable = Conversions.BoolToByte(colorBlendState.LogicOpEnable);
refreshGraphicsPipelineCreateInfo.colorBlendState.logicOp = (Refresh.LogicOp) colorBlendState.LogicOp;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendStates = (IntPtr) colorTargetBlendStates;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendStateCount = (uint) colorBlendState.ColorTargetBlendStates.Length;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[0] = colorBlendState.BlendConstants.R;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[1] = colorBlendState.BlendConstants.G;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[2] = colorBlendState.BlendConstants.B;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[3] = colorBlendState.BlendConstants.A;
refreshGraphicsPipelineCreateInfo.blendConstants[0] = blendConstants.R;
refreshGraphicsPipelineCreateInfo.blendConstants[1] = blendConstants.G;
refreshGraphicsPipelineCreateInfo.blendConstants[2] = blendConstants.B;
refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A;
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp;
refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.depthTestEnable = Conversions.BoolToByte(depthStencilState.DepthTestEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.depthWriteEnable = Conversions.BoolToByte(depthStencilState.DepthWriteEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.maxDepthBounds = depthStencilState.MaxDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp;
refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.depthTestEnable = Conversions.BoolToByte(depthStencilState.DepthTestEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.depthWriteEnable = Conversions.BoolToByte(depthStencilState.DepthWriteEnable);
refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.maxDepthBounds = depthStencilState.MaxDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable);
refreshGraphicsPipelineCreateInfo.vertexShaderState.entryPointName = vertexShaderState.EntryPointName;
refreshGraphicsPipelineCreateInfo.vertexShaderState.shaderModule = vertexShaderState.ShaderModule.Handle;
refreshGraphicsPipelineCreateInfo.vertexShaderState.uniformBufferSize = vertexShaderState.UniformBufferSize;
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.entryPointName = vertexShaderInfo.EntryPointName;
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.shaderModule = vertexShaderInfo.ShaderModule.Handle;
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.uniformBufferSize = vertexShaderInfo.UniformBufferSize;
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.samplerBindingCount = vertexShaderInfo.SamplerBindingCount;
refreshGraphicsPipelineCreateInfo.fragmentShaderState.entryPointName = fragmentShaderState.EntryPointName;
refreshGraphicsPipelineCreateInfo.fragmentShaderState.shaderModule = fragmentShaderState.ShaderModule.Handle;
refreshGraphicsPipelineCreateInfo.fragmentShaderState.uniformBufferSize = fragmentShaderState.UniformBufferSize;
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.entryPointName = fragmentShaderInfo.EntryPointName;
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.shaderModule = fragmentShaderInfo.ShaderModule.Handle;
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.uniformBufferSize = fragmentShaderInfo.UniformBufferSize;
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.samplerBindingCount = fragmentShaderInfo.SamplerBindingCount;
refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount)multisampleState.MultisampleCount;
refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask;
refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount) multisampleState.MultisampleCount;
refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask;
refreshGraphicsPipelineCreateInfo.pipelineLayoutCreateInfo.vertexSamplerBindingCount = pipelineLayoutInfo.VertexSamplerBindingCount;
refreshGraphicsPipelineCreateInfo.pipelineLayoutCreateInfo.fragmentSamplerBindingCount = pipelineLayoutInfo.FragmentSamplerBindingCount;
refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode) rasterizerState.CullMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable);
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor;
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace;
refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode)rasterizerState.CullMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable);
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthClampEnable = Conversions.BoolToByte(rasterizerState.DepthClampEnable);
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode)rasterizerState.FillMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace)rasterizerState.FrontFace;
refreshGraphicsPipelineCreateInfo.rasterizerState.lineWidth = rasterizerState.LineWidth;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindingCount = (uint) vertexInputState.VertexBindings.Length;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindingCount = (uint) vertexInputState.VertexBindings.Length;
refreshGraphicsPipelineCreateInfo.primitiveType = (Refresh.PrimitiveType) primitiveType;
refreshGraphicsPipelineCreateInfo.viewportState.viewports = viewportHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.viewportState.viewportCount = (uint) viewportState.Viewports.Length;
refreshGraphicsPipelineCreateInfo.viewportState.scissors = scissorHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.viewportState.scissorCount = (uint) viewportState.Scissors.Length;
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentCount = (uint) attachmentInfo.ColorAttachmentDescriptions.Length;
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentDescriptions = (IntPtr) colorAttachmentDescriptions;
refreshGraphicsPipelineCreateInfo.attachmentInfo.depthStencilFormat = (Refresh.TextureFormat) attachmentInfo.DepthStencilFormat;
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment);
refreshGraphicsPipelineCreateInfo.primitiveType = (Refresh.PrimitiveType) primitiveType;
refreshGraphicsPipelineCreateInfo.renderPass = renderPass.Handle;
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
if (Handle == IntPtr.Zero)
{
throw new Exception("Could not create graphics pipeline!");
}
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
vertexAttributesHandle.Free();
vertexBindingsHandle.Free();
vertexAttributesHandle.Free();
vertexBindingsHandle.Free();
viewportHandle.Free();
scissorHandle.Free();
VertexShaderInfo = vertexShaderInfo;
FragmentShaderInfo = fragmentShaderInfo;
SampleCount = multisampleState.MultisampleCount;
VertexShaderState = vertexShaderState;
FragmentShaderState = fragmentShaderState;
RenderPass = renderPass;
}
#if DEBUG
AttachmentInfo = attachmentInfo;
#endif
}
}
public unsafe uint PushVertexShaderUniforms<T>(
params T[] uniforms
) where T : unmanaged
{
fixed (T* ptr = &uniforms[0])
{
return Refresh.Refresh_PushVertexShaderUniforms(
Device.Handle,
Handle,
(IntPtr) ptr,
(uint) (uniforms.Length * Marshal.SizeOf<T>())
);
}
}
public unsafe uint PushFragmentShaderUniforms<T>(
params T[] uniforms
) where T : unmanaged
{
fixed (T* ptr = &uniforms[0])
{
return Refresh.Refresh_PushFragmentShaderUniforms(
Device.Handle,
Handle,
(IntPtr) ptr,
(uint) (uniforms.Length * Marshal.SizeOf<T>())
);
}
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A render pass describes the kind of render targets that will be used in rendering.
/// </summary>
public class RenderPass : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyRenderPass;
/// <summary>
/// Creates a render pass using color target descriptions.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="colorTargetDescriptions">Up to 4 color target descriptions may be provided.</param>
public unsafe RenderPass(
GraphicsDevice device,
params ColorTargetDescription[] colorTargetDescriptions
) : base(device)
{
fixed (ColorTargetDescription* ptr = colorTargetDescriptions)
{
Refresh.RenderPassCreateInfo renderPassCreateInfo;
renderPassCreateInfo.colorTargetCount = (uint) colorTargetDescriptions.Length;
renderPassCreateInfo.colorTargetDescriptions = (IntPtr) ptr;
renderPassCreateInfo.depthStencilTargetDescription = IntPtr.Zero;
Handle = Refresh.Refresh_CreateRenderPass(device.Handle, renderPassCreateInfo);
}
}
/// <summary>
/// Creates a render pass using a depth/stencil target description and optional color target descriptions.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="depthStencilTargetDescription">A depth/stencil target description.</param>
/// <param name="colorTargetDescriptions">Up to 4 color target descriptions may be provided.</param>
public unsafe RenderPass(
GraphicsDevice device,
in DepthStencilTargetDescription depthStencilTargetDescription,
params ColorTargetDescription[] colorTargetDescriptions
) : base(device)
{
fixed (DepthStencilTargetDescription* depthStencilPtr = &depthStencilTargetDescription)
fixed (ColorTargetDescription* colorPtr = colorTargetDescriptions)
{
Refresh.RenderPassCreateInfo renderPassCreateInfo;
renderPassCreateInfo.colorTargetCount = (uint)colorTargetDescriptions.Length;
renderPassCreateInfo.colorTargetDescriptions = (IntPtr)colorPtr;
renderPassCreateInfo.depthStencilTargetDescription = (IntPtr) depthStencilPtr;
Handle = Refresh.Refresh_CreateRenderPass(device.Handle, renderPassCreateInfo);
}
}
}
}

View File

@ -0,0 +1,89 @@
using System;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A render target is a structure that wraps a texture so that it can be rendered to.
/// </summary>
public class RenderTarget : GraphicsResource
{
public TextureSlice TextureSlice { get; }
public TextureFormat Format => TextureSlice.Texture.Format;
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyRenderTarget;
/// <summary>
/// Creates a render target backed by a texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the render target.</param>
/// <param name="height">The height of the render target.</param>
/// <param name="format">The format of the render target.</param>
/// <param name="canBeSampled">Whether the render target can be used by a sampler.</param>
/// <param name="sampleCount">The multisample count of the render target.</param>
/// <param name="levelCount">The mip level of the render target.</param>
/// <returns></returns>
public static RenderTarget CreateBackedRenderTarget(
GraphicsDevice device,
uint width,
uint height,
TextureFormat format,
bool canBeSampled,
SampleCount sampleCount = SampleCount.One,
uint levelCount = 1
) {
TextureUsageFlags flags = 0;
if (
format == TextureFormat.D16 ||
format == TextureFormat.D32 ||
format == TextureFormat.D16S8 ||
format == TextureFormat.D32S8
) {
flags |= TextureUsageFlags.DepthStencilTarget;
}
else
{
flags |= TextureUsageFlags.ColorTarget;
}
if (canBeSampled)
{
flags |= TextureUsageFlags.Sampler;
}
var texture = Texture.CreateTexture2D(
device,
width,
height,
format,
flags,
sampleCount,
levelCount
);
return new RenderTarget(device, new TextureSlice(texture), sampleCount);
}
/// <summary>
/// Creates a render target using a texture slice and an optional sample count.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="textureSlice">The texture slice that will be rendered to.</param>
/// <param name="sampleCount">The desired multisample count of the render target.</param>
public RenderTarget(
GraphicsDevice device,
in TextureSlice textureSlice,
SampleCount sampleCount = SampleCount.One
) : base(device)
{
Handle = Refresh.Refresh_CreateRenderTarget(
device.Handle,
textureSlice.ToRefreshTextureSlice(),
(Refresh.SampleCount) sampleCount
);
TextureSlice = textureSlice;
}
}
}

View File

@ -1,24 +1,24 @@
using System;
using System;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A sampler specifies how a texture will be sampled in a shader.
/// </summary>
public class Sampler : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
/// <summary>
/// A sampler specifies how a texture will be sampled in a shader.
/// </summary>
public class Sampler : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
public Sampler(
GraphicsDevice device,
in SamplerCreateInfo samplerCreateInfo
) : base(device)
{
Handle = Refresh.Refresh_CreateSampler(
device.Handle,
samplerCreateInfo.ToRefreshSamplerStateCreateInfo()
);
}
}
public Sampler(
GraphicsDevice device,
in SamplerCreateInfo samplerCreateInfo
) : base(device)
{
Handle = Refresh.Refresh_CreateSampler(
device.Handle,
samplerCreateInfo.ToRefreshSamplerStateCreateInfo()
);
}
}
}

View File

@ -1,42 +1,27 @@
using RefreshCS;
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Shader modules expect input in Refresh bytecode format.
/// </summary>
public class ShaderModule : RefreshResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
/// <summary>
/// Shader modules expect input in SPIR-V bytecode format.
/// </summary>
public class ShaderModule : GraphicsResource
{
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Handle = CreateFromStream(device, stream);
}
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
{
var bytecode = Bytecode.ReadBytecodeAsUInt32(filePath);
public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
{
Handle = CreateFromStream(device, stream);
}
fixed (uint* ptr = bytecode)
{
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
shaderModuleCreateInfo.codeSize = (UIntPtr) (bytecode.Length * sizeof(uint));
shaderModuleCreateInfo.byteCode = (IntPtr) ptr;
private static unsafe IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
{
var bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
var bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
stream.ReadExactly(bytecodeSpan);
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
shaderModuleCreateInfo.codeSize = (nuint) stream.Length;
shaderModuleCreateInfo.byteCode = (nint) bytecodeBuffer;
var shaderModule = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
NativeMemory.Free(bytecodeBuffer);
return shaderModule;
}
}
Handle = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
}
}
}
}

View File

@ -5,739 +5,229 @@ using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A container for pixel data.
/// </summary>
public class Texture : RefreshResource
{
public uint Width { get; internal set; }
public uint Height { get; internal set; }
public uint Depth { get; }
public TextureFormat Format { get; internal set; }
public bool IsCube { get; }
public uint LevelCount { get; }
public SampleCount SampleCount { get; }
public TextureUsageFlags UsageFlags { get; }
public uint Size { get; }
// FIXME: this allocates a delegate instance
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
/// <summary>
/// Creates a 2D Texture using PNG or QOI data from raw byte data.
/// </summary>
public static unsafe Texture FromImageBytes(
GraphicsDevice device,
CommandBuffer commandBuffer,
Span<byte> data
) {
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);
Texture texture;
int faces;
ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
if (isCube)
{
texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 6;
}
else
{
texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
faces = 1;
}
for (int i = 0; i < faces; i += 1)
{
for (int j = 0; j < levels; j += 1)
{
var levelWidth = width >> j;
var levelHeight = height >> j;
var levelSize = CalculateDDSLevelSize(levelWidth, levelHeight, format);
var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
var byteSpan = new Span<byte>(byteBuffer, levelSize);
stream.ReadExactly(byteSpan);
var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j);
commandBuffer.SetTextureData(textureSlice, (nint) byteBuffer, (uint) levelSize);
NativeMemory.Free(byteBuffer);
}
}
return texture;
}
/// <summary>
/// Creates a 2D texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height 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="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture2D(
GraphicsDevice device,
uint width,
uint height,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1,
SampleCount sampleCount = SampleCount.One
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = 1,
IsCube = false,
LevelCount = levelCount,
SampleCount = sampleCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a 3D texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="depth">The depth 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="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture3D(
GraphicsDevice device,
uint width,
uint height,
uint depth,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = depth,
IsCube = false,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a cube texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="size">The length of one side of the cube.</param>
/// <param name="format">The format of the texture.</param>
/// <param name="usageFlags">Specifies how the texture will be used.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTextureCube(
GraphicsDevice device,
uint size,
TextureFormat format,
TextureUsageFlags usageFlags,
uint levelCount = 1
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = size,
Height = size,
Depth = 1,
IsCube = true,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a new texture using a TextureCreateInfo struct.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="textureCreateInfo">The parameters to use when creating the texture.</param>
public Texture(
GraphicsDevice device,
in TextureCreateInfo textureCreateInfo
) : base(device)
{
Handle = Refresh.Refresh_CreateTexture(
device.Handle,
textureCreateInfo.ToRefreshTextureCreateInfo()
);
Format = textureCreateInfo.Format;
Width = textureCreateInfo.Width;
Height = textureCreateInfo.Height;
Depth = textureCreateInfo.Depth;
IsCube = textureCreateInfo.IsCube;
LevelCount = textureCreateInfo.LevelCount;
SampleCount = textureCreateInfo.SampleCount;
UsageFlags = textureCreateInfo.UsageFlags;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
}
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
// Used by AcquireSwapchainTexture.
// Should not be tracked, because swapchain textures are managed by Vulkan.
internal Texture(
GraphicsDevice device,
TextureFormat format
) : base(device)
{
Handle = IntPtr.Zero;
Format = format;
Width = 0;
Height = 0;
Depth = 1;
IsCube = false;
LevelCount = 1;
SampleCount = SampleCount.One;
UsageFlags = TextureUsageFlags.ColorTarget;
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
}
// DDS loading extension, based on MojoDDS
// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194
private static void ParseDDS(
BinaryReader reader,
out TextureFormat format,
out int width,
out int height,
out int levels,
out bool isCube
) {
// A whole bunch of magic numbers, yay DDS!
const uint DDS_MAGIC = 0x20534444;
const uint DDS_HEADERSIZE = 124;
const uint DDS_PIXFMTSIZE = 32;
const uint DDSD_HEIGHT = 0x2;
const uint DDSD_WIDTH = 0x4;
const uint DDSD_PITCH = 0x8;
const uint DDSD_LINEARSIZE = 0x80000;
const uint DDSD_REQ = (
DDSD_HEIGHT | DDSD_WIDTH
);
const uint DDSCAPS_MIPMAP = 0x400000;
const uint DDSCAPS_TEXTURE = 0x1000;
const uint DDSCAPS2_CUBEMAP = 0x200;
const uint DDPF_FOURCC = 0x4;
const uint DDPF_RGB = 0x40;
const uint FOURCC_DXT1 = 0x31545844;
const uint FOURCC_DXT3 = 0x33545844;
const uint FOURCC_DXT5 = 0x35545844;
const uint FOURCC_DX10 = 0x30315844;
const uint pitchAndLinear = (
DDSD_PITCH | DDSD_LINEARSIZE
);
// File should start with 'DDS '
if (reader.ReadUInt32() != DDS_MAGIC)
{
throw new NotSupportedException("Not a DDS!");
}
// Texture info
uint size = reader.ReadUInt32();
if (size != DDS_HEADERSIZE)
{
throw new NotSupportedException("Invalid DDS header!");
}
uint flags = reader.ReadUInt32();
if ((flags & DDSD_REQ) != DDSD_REQ)
{
throw new NotSupportedException("Invalid DDS flags!");
}
if ((flags & pitchAndLinear) == pitchAndLinear)
{
throw new NotSupportedException("Invalid DDS flags!");
}
height = reader.ReadInt32();
width = reader.ReadInt32();
reader.ReadUInt32(); // dwPitchOrLinearSize, unused
reader.ReadUInt32(); // dwDepth, unused
levels = reader.ReadInt32();
// "Reserved"
reader.ReadBytes(4 * 11);
// Format info
uint formatSize = reader.ReadUInt32();
if (formatSize != DDS_PIXFMTSIZE)
{
throw new NotSupportedException("Bogus PIXFMTSIZE!");
}
uint formatFlags = reader.ReadUInt32();
uint formatFourCC = reader.ReadUInt32();
uint formatRGBBitCount = reader.ReadUInt32();
uint formatRBitMask = reader.ReadUInt32();
uint formatGBitMask = reader.ReadUInt32();
uint formatBBitMask = reader.ReadUInt32();
uint formatABitMask = reader.ReadUInt32();
// dwCaps "stuff"
uint caps = reader.ReadUInt32();
if ((caps & DDSCAPS_TEXTURE) == 0)
{
throw new NotSupportedException("Not a texture!");
}
isCube = false;
uint caps2 = reader.ReadUInt32();
if (caps2 != 0)
{
if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP)
{
isCube = true;
}
else
{
throw new NotSupportedException("Invalid caps2!");
}
}
reader.ReadUInt32(); // dwCaps3, unused
reader.ReadUInt32(); // dwCaps4, unused
// "Reserved"
reader.ReadUInt32();
// Mipmap sanity check
if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP)
{
levels = 1;
}
// Determine texture format
if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC)
{
switch (formatFourCC)
{
case 0x71: // D3DFMT_A16B16G16R16F
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 0x74: // D3DFMT_A32B32G32R32F
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case FOURCC_DXT1:
format = TextureFormat.BC1;
break;
case FOURCC_DXT3:
format = TextureFormat.BC2;
break;
case FOURCC_DXT5:
format = TextureFormat.BC3;
break;
case FOURCC_DX10:
// If the fourCC is DX10, there is an extra header with additional format information.
uint dxgiFormat = reader.ReadUInt32();
// These values are taken from the DXGI_FORMAT enum.
switch (dxgiFormat)
{
case 2:
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case 10:
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 71:
format = TextureFormat.BC1;
break;
case 74:
format = TextureFormat.BC2;
break;
case 77:
format = TextureFormat.BC3;
break;
case 98:
format = TextureFormat.BC7;
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
uint resourceDimension = reader.ReadUInt32();
// These values are taken from the D3D10_RESOURCE_DIMENSION enum.
switch (resourceDimension)
{
case 0: // Unknown
case 1: // Buffer
throw new NotSupportedException(
"Unsupported DDS texture format"
);
default:
break;
}
/*
* This flag seemingly only indicates if the texture is a cube map.
* This is already determined above. Cool!
*/
uint miscFlag = reader.ReadUInt32();
/*
* Indicates the number of elements in the texture array.
* We don't support texture arrays so just throw if it's greater than 1.
*/
uint arraySize = reader.ReadUInt32();
if (arraySize > 1)
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
reader.ReadUInt32(); // reserved
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
else if ((formatFlags & DDPF_RGB) == DDPF_RGB)
{
if ( formatRGBBitCount != 32 ||
formatRBitMask != 0x00FF0000 ||
formatGBitMask != 0x0000FF00 ||
formatBBitMask != 0x000000FF ||
formatABitMask != 0xFF000000 )
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
format = TextureFormat.B8G8R8A8;
}
else
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
private static int CalculateDDSLevelSize(
int width,
int height,
TextureFormat format
) {
if (format == TextureFormat.R8G8B8A8)
{
return (((width * 32) + 7) / 8) * height;
}
else if (format == TextureFormat.R16G16B16A16_SFLOAT)
{
return (((width * 64) + 7) / 8) * height;
}
else if (format == TextureFormat.R32G32B32A32_SFLOAT)
{
return (((width * 128) + 7) / 8) * height;
}
else
{
int blockSize = 16;
if (format == TextureFormat.BC1)
{
blockSize = 8;
}
width = System.Math.Max(width, 1);
height = System.Math.Max(height, 1);
return (
((width + 3) / 4) *
((height + 3) / 4) *
blockSize
);
}
}
/// <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;
}
}
}
/// <summary>
/// A container for pixel data.
/// </summary>
public class Texture : GraphicsResource
{
public uint Width { get; }
public uint Height { get; }
public TextureFormat Format { get; }
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
public static Texture LoadPNG(GraphicsDevice device, string filePath)
{
var pixels = Refresh.Refresh_Image_Load(
filePath,
out var width,
out var height,
out var channels
);
TextureCreateInfo textureCreateInfo;
textureCreateInfo.Width = (uint)width;
textureCreateInfo.Height = (uint)height;
textureCreateInfo.Depth = 1;
textureCreateInfo.Format = TextureFormat.R8G8B8A8;
textureCreateInfo.IsCube = false;
textureCreateInfo.LevelCount = 1;
textureCreateInfo.SampleCount = SampleCount.One;
textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
var texture = new Texture(device, textureCreateInfo);
texture.SetData(pixels, (uint)(width * height * 4));
Refresh.Refresh_Image_Free(pixels);
return texture;
}
public unsafe static void SavePNG(string path, int width, int height, byte[] pixels)
{
fixed (byte* ptr = &pixels[0])
{
Refresh.Refresh_Image_SavePNG(path, width, height, (IntPtr) ptr);
}
}
/// <summary>
/// Creates a 2D texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height 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="sampleCount">Specifies the multisample count.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture2D(
GraphicsDevice device,
uint width,
uint height,
TextureFormat format,
TextureUsageFlags usageFlags,
SampleCount sampleCount = SampleCount.One,
uint levelCount = 1
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = 1,
IsCube = false,
SampleCount = sampleCount,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a 3D texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="width">The width of the texture.</param>
/// <param name="height">The height of the texture.</param>
/// <param name="depth">The depth 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="sampleCount">Specifies the multisample count.</param>
/// <param name="levelCount">Specifies the number of mip levels.</param>
public static Texture CreateTexture3D(
GraphicsDevice device,
uint width,
uint height,
uint depth,
TextureFormat format,
TextureUsageFlags usageFlags,
SampleCount sampleCount = SampleCount.One,
uint levelCount = 1
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = width,
Height = height,
Depth = depth,
IsCube = false,
SampleCount = sampleCount,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a cube texture.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="size">The length of one side of the cube.</param>
/// <param name="format">The format of the texture.</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>
public static Texture CreateTextureCube(
GraphicsDevice device,
uint size,
TextureFormat format,
TextureUsageFlags usageFlags,
SampleCount sampleCount = SampleCount.One,
uint levelCount = 1
) {
var textureCreateInfo = new TextureCreateInfo
{
Width = size,
Height = size,
Depth = 1,
IsCube = true,
SampleCount = sampleCount,
LevelCount = levelCount,
Format = format,
UsageFlags = usageFlags
};
return new Texture(device, textureCreateInfo);
}
/// <summary>
/// Creates a new texture using a TextureCreateInfo struct.
/// </summary>
/// <param name="device">An initialized GraphicsDevice.</param>
/// <param name="textureCreateInfo">The parameters to use when creating the texture.</param>
public Texture(
GraphicsDevice device,
in TextureCreateInfo textureCreateInfo
) : base(device)
{
Handle = Refresh.Refresh_CreateTexture(
device.Handle,
textureCreateInfo.ToRefreshTextureCreateInfo()
);
Format = textureCreateInfo.Format;
Width = textureCreateInfo.Width;
Height = textureCreateInfo.Height;
}
/// <summary>
/// Asynchronously copies data into the texture.
/// </summary>
/// <param name="textureSlice">The texture slice to copy into.</param>
/// <param name="dataPtr">A pointer to an array of data to copy from.</param>
/// <param name="dataLengthInBytes">The amount of data to copy from the array.</param>
public void SetData(in TextureSlice textureSlice, IntPtr dataPtr, uint dataLengthInBytes)
{
Refresh.Refresh_SetTextureData(
Device.Handle,
textureSlice.ToRefreshTextureSlice(),
dataPtr,
dataLengthInBytes
);
}
/// <summary>
/// Asynchronously copies data into the texture.
/// This variant copies into the entire texture.
/// </summary>
/// <param name="dataPtr">A pointer to an array of data to copy from.</param>
/// <param name="dataLengthInBytes">The amount of data to copy from the array.</param>
public void SetData(IntPtr dataPtr, uint dataLengthInBytes)
{
SetData(new TextureSlice(this), dataPtr, dataLengthInBytes);
}
/// <summary>
/// Asynchronously copies data into the texture.
/// </summary>
/// <param name="textureSlice">The texture slice to copy into.</param>
/// <param name="data">An array of data to copy into the texture.</param>
public unsafe void SetData<T>(in TextureSlice textureSlice, T[] data) where T : unmanaged
{
var size = Marshal.SizeOf<T>();
fixed (T* ptr = &data[0])
{
Refresh.Refresh_SetTextureData(
Device.Handle,
textureSlice.ToRefreshTextureSlice(),
(IntPtr) ptr,
(uint) (data.Length * size)
);
}
}
/// <summary>
/// Asynchronously copies data into the texture.
/// This variant copies data into the entire texture.
/// </summary>
/// <param name="data">An array of data to copy into the texture.</param>
public unsafe void SetData<T>(T[] data) where T : unmanaged
{
SetData(new TextureSlice(this), data);
}
}
}

View File

@ -1,124 +0,0 @@
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// Defines how color blending will be performed in a GraphicsPipeline.
/// </summary>
public struct ColorAttachmentBlendState
{
/// <summary>
/// If disabled, no blending will occur.
/// </summary>
public bool BlendEnable;
/// <summary>
/// Selects which blend operation to use with alpha values.
/// </summary>
public BlendOp AlphaBlendOp;
/// <summary>
/// Selects which blend operation to use with color values.
/// </summary>
public BlendOp ColorBlendOp;
/// <summary>
/// Specifies which of the RGBA components are enabled for writing.
/// </summary>
public ColorComponentFlags ColorWriteMask;
/// <summary>
/// Selects which blend factor is used to determine the alpha destination factor.
/// </summary>
public BlendFactor DestinationAlphaBlendFactor;
/// <summary>
/// Selects which blend factor is used to determine the color destination factor.
/// </summary>
public BlendFactor DestinationColorBlendFactor;
/// <summary>
/// Selects which blend factor is used to determine the alpha source factor.
/// </summary>
public BlendFactor SourceAlphaBlendFactor;
/// <summary>
/// Selects which blend factor is used to determine the color source factor.
/// </summary>
public BlendFactor SourceColorBlendFactor;
public static readonly ColorAttachmentBlendState Additive = new ColorAttachmentBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.SourceAlpha,
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
DestinationColorBlendFactor = BlendFactor.One,
DestinationAlphaBlendFactor = BlendFactor.One
};
public static readonly ColorAttachmentBlendState AlphaBlend = new ColorAttachmentBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.One,
SourceAlphaBlendFactor = BlendFactor.One,
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
};
public static readonly ColorAttachmentBlendState NonPremultiplied = new ColorAttachmentBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.SourceAlpha,
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
};
public static readonly ColorAttachmentBlendState Opaque = new ColorAttachmentBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.One,
SourceAlphaBlendFactor = BlendFactor.One,
DestinationColorBlendFactor = BlendFactor.Zero,
DestinationAlphaBlendFactor = BlendFactor.Zero
};
public static readonly ColorAttachmentBlendState None = new ColorAttachmentBlendState
{
BlendEnable = false,
ColorWriteMask = ColorComponentFlags.RGBA
};
public static readonly ColorAttachmentBlendState Disable = new ColorAttachmentBlendState
{
BlendEnable = false,
ColorWriteMask = ColorComponentFlags.None
};
public Refresh.ColorAttachmentBlendState ToRefresh()
{
return new Refresh.ColorAttachmentBlendState
{
blendEnable = Conversions.BoolToByte(BlendEnable),
alphaBlendOp = (Refresh.BlendOp) AlphaBlendOp,
colorBlendOp = (Refresh.BlendOp) ColorBlendOp,
colorWriteMask = (Refresh.ColorComponentFlags) ColorWriteMask,
destinationAlphaBlendFactor = (Refresh.BlendFactor) DestinationAlphaBlendFactor,
destinationColorBlendFactor = (Refresh.BlendFactor) DestinationColorBlendFactor,
sourceAlphaBlendFactor = (Refresh.BlendFactor) SourceAlphaBlendFactor,
sourceColorBlendFactor = (Refresh.BlendFactor) SourceColorBlendFactor
};
}
}
}

View File

@ -0,0 +1,14 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// Describes how the graphics pipeline will blend colors.
/// You must provide one ColorTargetBlendState per color target in the pipeline.
/// </summary>
public unsafe struct ColorBlendState
{
public bool LogicOpEnable;
public LogicOp LogicOp;
public BlendConstants BlendConstants;
public ColorTargetBlendState[] ColorTargetBlendStates;
}
}

View File

@ -0,0 +1,121 @@
using RefreshCS;
namespace MoonWorks.Graphics
{
public struct ColorTargetBlendState
{
/// <summary>
/// If disabled, no blending will occur.
/// </summary>
public bool BlendEnable;
/// <summary>
/// Selects which blend operation to use with alpha values.
/// </summary>
public BlendOp AlphaBlendOp;
/// <summary>
/// Selects which blend operation to use with color values.
/// </summary>
public BlendOp ColorBlendOp;
/// <summary>
/// Specifies which of the RGBA components are enabled for writing.
/// </summary>
public ColorComponentFlags ColorWriteMask;
/// <summary>
/// Selects which blend factor is used to determine the alpha destination factor.
/// </summary>
public BlendFactor DestinationAlphaBlendFactor;
/// <summary>
/// Selects which blend factor is used to determine the color destination factor.
/// </summary>
public BlendFactor DestinationColorBlendFactor;
/// <summary>
/// Selects which blend factor is used to determine the alpha source factor.
/// </summary>
public BlendFactor SourceAlphaBlendFactor;
/// <summary>
/// Selects which blend factor is used to determine the color source factor.
/// </summary>
public BlendFactor SourceColorBlendFactor;
public static readonly ColorTargetBlendState Additive = new ColorTargetBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.SourceAlpha,
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
DestinationColorBlendFactor = BlendFactor.One,
DestinationAlphaBlendFactor = BlendFactor.One
};
public static readonly ColorTargetBlendState AlphaBlend = new ColorTargetBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.One,
SourceAlphaBlendFactor = BlendFactor.One,
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
};
public static readonly ColorTargetBlendState NonPremultiplied = new ColorTargetBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.SourceAlpha,
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
};
public static readonly ColorTargetBlendState Opaque = new ColorTargetBlendState
{
BlendEnable = true,
AlphaBlendOp = BlendOp.Add,
ColorBlendOp = BlendOp.Add,
ColorWriteMask = ColorComponentFlags.RGBA,
SourceColorBlendFactor = BlendFactor.One,
SourceAlphaBlendFactor = BlendFactor.One,
DestinationColorBlendFactor = BlendFactor.Zero,
DestinationAlphaBlendFactor = BlendFactor.Zero
};
public static readonly ColorTargetBlendState None = new ColorTargetBlendState
{
BlendEnable = false,
ColorWriteMask = ColorComponentFlags.RGBA
};
public static readonly ColorTargetBlendState Disable = new ColorTargetBlendState
{
BlendEnable = false,
ColorWriteMask = ColorComponentFlags.None
};
public Refresh.ColorTargetBlendState ToRefreshColorTargetBlendState()
{
return new Refresh.ColorTargetBlendState
{
blendEnable = Conversions.BoolToByte(BlendEnable),
alphaBlendOp = (Refresh.BlendOp)AlphaBlendOp,
colorBlendOp = (Refresh.BlendOp)ColorBlendOp,
colorWriteMask = (Refresh.ColorComponentFlags)ColorWriteMask,
destinationAlphaBlendFactor = (Refresh.BlendFactor)DestinationAlphaBlendFactor,
destinationColorBlendFactor = (Refresh.BlendFactor)DestinationColorBlendFactor,
sourceAlphaBlendFactor = (Refresh.BlendFactor)SourceAlphaBlendFactor,
sourceColorBlendFactor = (Refresh.BlendFactor)SourceColorBlendFactor
};
}
}
}

View File

@ -1,50 +0,0 @@
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Information that the compute pipeline needs about a compute shader.
/// </summary>
public struct ComputeShaderInfo
{
public ShaderModule ShaderModule;
public string EntryPointName;
public uint UniformBufferSize;
public uint BufferBindingCount;
public uint ImageBindingCount;
public unsafe static ComputeShaderInfo Create<T>(
ShaderModule shaderModule,
string entryPointName,
uint bufferBindingCount,
uint imageBindingCount
) where T : unmanaged
{
return new ComputeShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
BufferBindingCount = bufferBindingCount,
ImageBindingCount = imageBindingCount
};
}
public static ComputeShaderInfo Create(
ShaderModule shaderModule,
string entryPointName,
uint bufferBindingCount,
uint imageBindingCount
)
{
return new ComputeShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = 0,
BufferBindingCount = bufferBindingCount,
ImageBindingCount = imageBindingCount
};
}
}
}

View File

@ -1,79 +1,79 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
/// <summary>
/// Determines how data is written to and read from the depth/stencil buffer.
/// </summary>
public struct DepthStencilState
{
/// <summary>
/// If disabled, no depth culling will occur.
/// </summary>
public bool DepthTestEnable;
/// <summary>
/// Determines how data is written to and read from the depth/stencil buffer.
/// </summary>
public struct DepthStencilState
{
/// <summary>
/// If disabled, no depth culling will occur.
/// </summary>
public bool DepthTestEnable;
/// <summary>
/// Describes the stencil operation for back-facing primitives.
/// </summary>
public StencilOpState BackStencilState;
/// <summary>
/// Describes the stencil operation for back-facing primitives.
/// </summary>
public StencilOpState BackStencilState;
/// <summary>
/// Describes the stencil operation for front-facing primitives.
/// </summary>
public StencilOpState FrontStencilState;
/// <summary>
/// Describes the stencil operation for front-facing primitives.
/// </summary>
public StencilOpState FrontStencilState;
/// <summary>
/// The comparison operator used in the depth test.
/// </summary>
public CompareOp CompareOp;
/// <summary>
/// The comparison operator used in the depth test.
/// </summary>
public CompareOp CompareOp;
/// <summary>
/// If depth lies outside of these bounds the pixel will be culled.
/// </summary>
public bool DepthBoundsTestEnable;
/// <summary>
/// If depth lies outside of these bounds the pixel will be culled.
/// </summary>
public bool DepthBoundsTestEnable;
/// <summary>
/// Specifies whether depth values will be written to the buffer during rendering.
/// </summary>
public bool DepthWriteEnable;
/// <summary>
/// Specifies whether depth values will be written to the buffer during rendering.
/// </summary>
public bool DepthWriteEnable;
/// <summary>
/// The minimum depth value in the depth bounds test.
/// </summary>
public float MinDepthBounds;
/// <summary>
/// The minimum depth value in the depth bounds test.
/// </summary>
public float MinDepthBounds;
/// <summary>
/// The maximum depth value in the depth bounds test.
/// </summary>
public float MaxDepthBounds;
/// <summary>
/// The maximum depth value in the depth bounds test.
/// </summary>
public float MaxDepthBounds;
/// <summary>
/// If disabled, no stencil culling will occur.
/// </summary>
public bool StencilTestEnable;
/// <summary>
/// If disabled, no stencil culling will occur.
/// </summary>
public bool StencilTestEnable;
public static readonly DepthStencilState DepthReadWrite = new DepthStencilState
{
DepthTestEnable = true,
DepthWriteEnable = true,
DepthBoundsTestEnable = false,
StencilTestEnable = false,
CompareOp = CompareOp.LessOrEqual
};
public static readonly DepthStencilState DepthReadWrite = new DepthStencilState
{
DepthTestEnable = true,
DepthWriteEnable = true,
DepthBoundsTestEnable = false,
StencilTestEnable = false,
CompareOp = CompareOp.LessOrEqual
};
public static readonly DepthStencilState DepthRead = new DepthStencilState
{
DepthTestEnable = true,
DepthWriteEnable = false,
DepthBoundsTestEnable = false,
StencilTestEnable = false,
CompareOp = CompareOp.LessOrEqual
};
public static readonly DepthStencilState DepthRead = new DepthStencilState
{
DepthTestEnable = true,
DepthWriteEnable = false,
DepthBoundsTestEnable = false,
StencilTestEnable = false,
CompareOp = CompareOp.LessOrEqual
};
public static readonly DepthStencilState Disable = new DepthStencilState
{
DepthTestEnable = false,
DepthWriteEnable = false,
DepthBoundsTestEnable = false,
StencilTestEnable = false
};
}
public static readonly DepthStencilState Disable = new DepthStencilState
{
DepthTestEnable = false,
DepthWriteEnable = false,
DepthBoundsTestEnable = false,
StencilTestEnable = false
};
}
}

View File

@ -1,29 +0,0 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// Describes the kind of attachments that will be used with this pipeline.
/// </summary>
public struct GraphicsPipelineAttachmentInfo
{
public ColorAttachmentDescription[] ColorAttachmentDescriptions;
public bool HasDepthStencilAttachment;
public TextureFormat DepthStencilFormat;
public GraphicsPipelineAttachmentInfo(
params ColorAttachmentDescription[] colorAttachmentDescriptions
) {
ColorAttachmentDescriptions = colorAttachmentDescriptions;
HasDepthStencilAttachment = false;
DepthStencilFormat = TextureFormat.D16;
}
public GraphicsPipelineAttachmentInfo(
TextureFormat depthStencilFormat,
params ColorAttachmentDescription[] colorAttachmentDescriptions
) {
ColorAttachmentDescriptions = colorAttachmentDescriptions;
HasDepthStencilAttachment = true;
DepthStencilFormat = depthStencilFormat;
}
}
}

View File

@ -1,18 +1,17 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
/// <summary>
/// All of the information that is used to create a GraphicsPipeline.
/// </summary>
public struct GraphicsPipelineCreateInfo
{
public DepthStencilState DepthStencilState;
public GraphicsShaderInfo VertexShaderInfo;
public GraphicsShaderInfo FragmentShaderInfo;
public MultisampleState MultisampleState;
public RasterizerState RasterizerState;
public PrimitiveType PrimitiveType;
public VertexInputState VertexInputState;
public GraphicsPipelineAttachmentInfo AttachmentInfo;
public BlendConstants BlendConstants;
}
public struct GraphicsPipelineCreateInfo
{
public ColorBlendState ColorBlendState;
public DepthStencilState DepthStencilState;
public ShaderStageState VertexShaderState;
public ShaderStageState FragmentShaderState;
public MultisampleState MultisampleState;
public GraphicsPipelineLayoutInfo PipelineLayoutInfo;
public RasterizerState RasterizerState;
public PrimitiveType PrimitiveType;
public VertexInputState VertexInputState;
public ViewportState ViewportState;
public RenderPass RenderPass;
}
}

View File

@ -0,0 +1,11 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// Describes how many samplers will be used in each shader stage.
/// </summary>
public struct GraphicsPipelineLayoutInfo
{
public uint VertexSamplerBindingCount;
public uint FragmentSamplerBindingCount;
}
}

View File

@ -1,44 +0,0 @@
using System.Runtime.InteropServices;
namespace MoonWorks.Graphics
{
/// <summary>
/// Information that the pipeline needs about a graphics shader.
/// </summary>
public struct GraphicsShaderInfo
{
public ShaderModule ShaderModule;
public string EntryPointName;
public uint UniformBufferSize;
public uint SamplerBindingCount;
public unsafe static GraphicsShaderInfo Create<T>(
ShaderModule shaderModule,
string entryPointName,
uint samplerBindingCount
) where T : unmanaged
{
return new GraphicsShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
SamplerBindingCount = samplerBindingCount
};
}
public static GraphicsShaderInfo Create(
ShaderModule shaderModule,
string entryPointName,
uint samplerBindingCount
) {
return new GraphicsShaderInfo
{
ShaderModule = shaderModule,
EntryPointName = entryPointName,
UniformBufferSize = 0,
SamplerBindingCount = samplerBindingCount
};
}
}
}

View File

@ -1,25 +1,17 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
/// <summary>
/// Specifies how many samples should be used in rasterization.
/// </summary>
public struct MultisampleState
{
public SampleCount MultisampleCount;
public uint SampleMask;
/// <summary>
/// Specifies how many samples should be used in rasterization.
/// </summary>
public struct MultisampleState
{
public SampleCount MultisampleCount;
public uint SampleMask;
public static readonly MultisampleState None = new MultisampleState
{
MultisampleCount = SampleCount.One,
SampleMask = uint.MaxValue
};
public MultisampleState(
SampleCount sampleCount,
uint sampleMask = uint.MaxValue
) {
MultisampleCount = sampleCount;
SampleMask = sampleMask;
}
}
public static readonly MultisampleState None = new MultisampleState
{
MultisampleCount = SampleCount.One,
SampleMask = uint.MaxValue
};
}
}

View File

@ -1,107 +1,121 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
/// <summary>
/// Specifies how the rasterizer should be configured for a graphics pipeline.
/// </summary>
public struct RasterizerState
{
/// <summary>
/// Specifies whether front faces, back faces, none, or both should be culled.
/// </summary>
public CullMode CullMode;
/// <summary>
/// Specifies how the rasterizer should be configured for a graphics pipeline.
/// </summary>
public struct RasterizerState
{
/// <summary>
/// Specifies whether front faces, back faces, none, or both should be culled.
/// </summary>
public CullMode CullMode;
/// <summary>
/// Specifies maximum depth bias of a fragment. Only applies if depth biasing is enabled.
/// </summary>
public float DepthBiasClamp;
/// <summary>
/// Specifies maximum depth bias of a fragment. Only applies if depth biasing is enabled.
/// </summary>
public float DepthBiasClamp;
/// <summary>
/// The constant depth value added to each fragment. Only applies if depth biasing is enabled.
/// </summary>
public float DepthBiasConstantFactor;
/// <summary>
/// The constant depth value added to each fragment. Only applies if depth biasing is enabled.
/// </summary>
public float DepthBiasConstantFactor;
/// <summary>
/// Specifies whether depth biasing is enabled. Only applies if depth biasing is enabled.
/// </summary>
public bool DepthBiasEnable;
/// <summary>
/// Specifies whether depth biasing is enabled. Only applies if depth biasing is enabled.
/// </summary>
public bool DepthBiasEnable;
/// <summary>
/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
/// </summary>
public float DepthBiasSlopeFactor;
/// <summary>
/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
/// </summary>
public float DepthBiasSlopeFactor;
public bool DepthClampEnable;
/// <summary>
/// Specifies how triangles should be drawn.
/// </summary>
public FillMode FillMode;
/// <summary>
/// Specifies how triangles should be drawn.
/// </summary>
public FillMode FillMode;
/// <summary>
/// Specifies which triangle winding order is designated as front-facing.
/// </summary>
public FrontFace FrontFace;
/// <summary>
/// Specifies which triangle winding order is designated as front-facing.
/// </summary>
public FrontFace FrontFace;
public static readonly RasterizerState CW_CullFront = new RasterizerState
{
CullMode = CullMode.Front,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false
};
/// <summary>
/// Describes the width of the line rendering in terms of pixels.
/// </summary>
public float LineWidth;
public static readonly RasterizerState CW_CullBack = new RasterizerState
{
CullMode = CullMode.Back,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false
};
public static readonly RasterizerState CW_CullFront = new RasterizerState
{
CullMode = CullMode.Front,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CW_CullNone = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false
};
public static readonly RasterizerState CW_CullBack = new RasterizerState
{
CullMode = CullMode.Back,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CW_Wireframe = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Line,
DepthBiasEnable = false
};
public static readonly RasterizerState CW_CullNone = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CCW_CullFront = new RasterizerState
{
CullMode = CullMode.Front,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false
};
public static readonly RasterizerState CW_Wireframe = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CCW_CullBack = new RasterizerState
{
CullMode = CullMode.Back,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false
};
public static readonly RasterizerState CCW_CullFront = new RasterizerState
{
CullMode = CullMode.Front,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CCW_CullNone = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false
};
public static readonly RasterizerState CCW_CullBack = new RasterizerState
{
CullMode = CullMode.Back,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CCW_Wireframe = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Line,
DepthBiasEnable = false
};
}
public static readonly RasterizerState CCW_CullNone = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
public static readonly RasterizerState CCW_Wireframe = new RasterizerState
{
CullMode = CullMode.None,
FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill,
DepthBiasEnable = false,
LineWidth = 1f
};
}
}

View File

@ -2,173 +2,134 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// All of the information that is used to create a sampler.
/// </summary>
public struct SamplerCreateInfo
{
/// <summary>
/// Minification filter mode. Used when the image is downscaled.
/// </summary>
public Filter MinFilter;
/// <summary>
/// Magnification filter mode. Used when the image is upscaled.
/// </summary>
public Filter MagFilter;
/// <summary>
/// Filter mode applied to mipmap lookups.
/// </summary>
public SamplerMipmapMode MipmapMode;
/// <summary>
/// Horizontal addressing mode.
/// </summary>
public SamplerAddressMode AddressModeU;
/// <summary>
/// Vertical addressing mode.
/// </summary>
public SamplerAddressMode AddressModeV;
/// <summary>
/// Depth addressing mode.
/// </summary>
public SamplerAddressMode AddressModeW;
/// <summary>
/// Bias value added to mipmap level of detail calculation.
/// </summary>
public float MipLodBias;
/// <summary>
/// Enables anisotropic filtering.
/// </summary>
public bool AnisotropyEnable;
/// <summary>
/// Maximum anisotropy value.
/// </summary>
public float MaxAnisotropy;
public bool CompareEnable;
public CompareOp CompareOp;
/// <summary>
/// Clamps the LOD value to a specified minimum.
/// </summary>
public float MinLod;
/// <summary>
/// Clamps the LOD value to a specified maximum.
/// </summary>
public float MaxLod;
/// <summary>
/// If an address mode is set to ClampToBorder, will replace color with this color when samples are outside the [0, 1) range.
/// </summary>
public BorderColor BorderColor;
public struct SamplerCreateInfo
{
public Filter MinFilter;
public Filter MagFilter;
public SamplerMipmapMode MipmapMode;
public SamplerAddressMode AddressModeU;
public SamplerAddressMode AddressModeV;
public SamplerAddressMode AddressModeW;
public float MipLodBias;
public bool AnisotropyEnable;
public float MaxAnisotropy;
public bool CompareEnable;
public CompareOp CompareOp;
public float MinLod;
public float MaxLod;
public BorderColor BorderColor;
public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
CompareEnable = false,
AnisotropyEnable = true,
MaxAnisotropy = 4,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
};
public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
CompareEnable = false,
AnisotropyEnable = true,
MaxAnisotropy = 4,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
};
public static readonly SamplerCreateInfo AnisotropicWrap = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.Repeat,
AddressModeV = SamplerAddressMode.Repeat,
AddressModeW = SamplerAddressMode.Repeat,
CompareEnable = false,
AnisotropyEnable = true,
MaxAnisotropy = 4,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
};
public static readonly SamplerCreateInfo AnisotropicWrap = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.Repeat,
AddressModeV = SamplerAddressMode.Repeat,
AddressModeW = SamplerAddressMode.Repeat,
CompareEnable = false,
AnisotropyEnable = true,
MaxAnisotropy = 4,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
};
public static readonly SamplerCreateInfo LinearClamp = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo LinearClamp = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo LinearWrap = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.Repeat,
AddressModeV = SamplerAddressMode.Repeat,
AddressModeW = SamplerAddressMode.Repeat,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo LinearWrap = new SamplerCreateInfo
{
MinFilter = Filter.Linear,
MagFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.Repeat,
AddressModeV = SamplerAddressMode.Repeat,
AddressModeW = SamplerAddressMode.Repeat,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo PointClamp = new SamplerCreateInfo
{
MinFilter = Filter.Nearest,
MagFilter = Filter.Nearest,
MipmapMode = SamplerMipmapMode.Nearest,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo PointClamp = new SamplerCreateInfo
{
MinFilter = Filter.Nearest,
MagFilter = Filter.Nearest,
MipmapMode = SamplerMipmapMode.Nearest,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo PointWrap = new SamplerCreateInfo
{
MinFilter = Filter.Nearest,
MagFilter = Filter.Nearest,
MipmapMode = SamplerMipmapMode.Nearest,
AddressModeU = SamplerAddressMode.Repeat,
AddressModeV = SamplerAddressMode.Repeat,
AddressModeW = SamplerAddressMode.Repeat,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public static readonly SamplerCreateInfo PointWrap = new SamplerCreateInfo
{
MinFilter = Filter.Nearest,
MagFilter = Filter.Nearest,
MipmapMode = SamplerMipmapMode.Nearest,
AddressModeU = SamplerAddressMode.Repeat,
AddressModeV = SamplerAddressMode.Repeat,
AddressModeW = SamplerAddressMode.Repeat,
CompareEnable = false,
AnisotropyEnable = false,
MipLodBias = 0f,
MinLod = 0,
MaxLod = 1000
};
public Refresh.SamplerStateCreateInfo ToRefreshSamplerStateCreateInfo()
{
return new Refresh.SamplerStateCreateInfo
{
minFilter = (Refresh.Filter) MinFilter,
magFilter = (Refresh.Filter) MagFilter,
mipmapMode = (Refresh.SamplerMipmapMode) MipmapMode,
addressModeU = (Refresh.SamplerAddressMode) AddressModeU,
addressModeV = (Refresh.SamplerAddressMode) AddressModeV,
addressModeW = (Refresh.SamplerAddressMode) AddressModeW,
mipLodBias = MipLodBias,
anisotropyEnable = Conversions.BoolToByte(AnisotropyEnable),
maxAnisotropy = MaxAnisotropy,
compareEnable = Conversions.BoolToByte(CompareEnable),
compareOp = (Refresh.CompareOp) CompareOp,
minLod = MinLod,
maxLod = MaxLod,
borderColor = (Refresh.BorderColor) BorderColor
};
}
}
public Refresh.SamplerStateCreateInfo ToRefreshSamplerStateCreateInfo()
{
return new Refresh.SamplerStateCreateInfo
{
minFilter = (Refresh.Filter)MinFilter,
magFilter = (Refresh.Filter)MagFilter,
mipmapMode = (Refresh.SamplerMipmapMode)MipmapMode,
addressModeU = (Refresh.SamplerAddressMode)AddressModeU,
addressModeV = (Refresh.SamplerAddressMode)AddressModeV,
addressModeW = (Refresh.SamplerAddressMode)AddressModeW,
mipLodBias = MipLodBias,
anisotropyEnable = Conversions.BoolToByte(AnisotropyEnable),
maxAnisotropy = MaxAnisotropy,
compareEnable = Conversions.BoolToByte(CompareEnable),
compareOp = (Refresh.CompareOp)CompareOp,
minLod = MinLod,
maxLod = MaxLod,
borderColor = (Refresh.BorderColor)BorderColor
};
}
}
}

View File

@ -0,0 +1,12 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// Specifies how the graphics pipeline will make use of a shader.
/// </summary>
public struct ShaderStageState
{
public ShaderModule ShaderModule;
public string EntryPointName;
public uint UniformBufferSize;
}
}

View File

@ -2,33 +2,30 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// All of the information that is used to create a texture.
/// </summary>
public struct TextureCreateInfo
{
public uint Width;
public uint Height;
public uint Depth;
public bool IsCube;
public uint LevelCount;
public SampleCount SampleCount;
public TextureFormat Format;
public TextureUsageFlags UsageFlags;
public struct TextureCreateInfo
{
public uint Width;
public uint Height;
public uint Depth;
public bool IsCube;
public SampleCount SampleCount;
public uint LevelCount;
public TextureFormat Format;
public TextureUsageFlags UsageFlags;
public Refresh.TextureCreateInfo ToRefreshTextureCreateInfo()
{
return new Refresh.TextureCreateInfo
{
width = Width,
height = Height,
depth = Depth,
isCube = Conversions.BoolToByte(IsCube),
levelCount = LevelCount,
sampleCount = (Refresh.SampleCount) SampleCount,
format = (Refresh.TextureFormat) Format,
usageFlags = (Refresh.TextureUsageFlags) UsageFlags
};
}
}
public Refresh.TextureCreateInfo ToRefreshTextureCreateInfo()
{
return new Refresh.TextureCreateInfo
{
width = Width,
height = Height,
depth = Depth,
isCube = Conversions.BoolToByte(IsCube),
sampleCount = (Refresh.SampleCount) SampleCount,
levelCount = LevelCount,
format = (Refresh.TextureFormat) Format,
usageFlags = (Refresh.TextureUsageFlags) UsageFlags
};
}
}
}

View File

@ -1,70 +1,11 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
/// <summary>
/// Specifies how the vertex shader will interpet vertex data in a buffer.
/// </summary>
public struct VertexInputState
{
public VertexBinding[] VertexBindings;
public VertexAttribute[] VertexAttributes;
public static readonly VertexInputState Empty = new VertexInputState
{
VertexBindings = System.Array.Empty<VertexBinding>(),
VertexAttributes = System.Array.Empty<VertexAttribute>()
};
public VertexInputState(
VertexBinding vertexBinding,
VertexAttribute[] vertexAttributes
) {
VertexBindings = new VertexBinding[] { vertexBinding };
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));
}
}
/// <summary>
/// Specifies how to interpet vertex data in a buffer to be passed to the vertex shader.
/// </summary>
public struct VertexInputState
{
public VertexBinding[] VertexBindings;
public VertexAttribute[] VertexAttributes;
}
}

View File

@ -0,0 +1,11 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// Describes the dimensions of viewports and scissor areas.
/// </summary>
public struct ViewportState
{
public Viewport[] Viewports;
public Rect[] Scissors;
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -1,9 +0,0 @@
#version 450
layout(location = 0) out vec2 outTexCoord;
void main()
{
outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outTexCoord * 2.0 - 1.0, 0.0, 1.0);
}

View File

@ -1,38 +0,0 @@
/*
* This effect is based on the YUV-to-RGBA GLSL shader found in SDL.
* Thus, it also released under the zlib license:
* http://libsdl.org/license.php
*/
#version 450
layout(location = 0) in vec2 TexCoord;
layout(location = 0) out vec4 FragColor;
layout(binding = 0, set = 1) uniform sampler2D YSampler;
layout(binding = 1, set = 1) uniform sampler2D USampler;
layout(binding = 2, set = 1) uniform sampler2D VSampler;
/* More info about colorspace conversion:
* http://www.equasys.de/colorconversion.html
* http://www.equasys.de/colorformat.html
*/
const vec3 offset = vec3(-0.0625, -0.5, -0.5);
const vec3 Rcoeff = vec3(1.164, 0.000, 1.793);
const vec3 Gcoeff = vec3(1.164, -0.213, -0.533);
const vec3 Bcoeff = vec3(1.164, 2.112, 0.000);
void main()
{
vec3 yuv;
yuv.x = texture(YSampler, TexCoord).r;
yuv.y = texture(USampler, TexCoord).r;
yuv.z = texture(VSampler, TexCoord).r;
yuv += offset;
FragColor.r = dot(yuv, Rcoeff);
FragColor.g = dot(yuv, Gcoeff);
FragColor.b = dot(yuv, Bcoeff);
FragColor.a = 1.0;
}

View File

@ -1,10 +1,10 @@
namespace MoonWorks.Graphics
namespace MoonWorks.Graphics
{
public struct BlendConstants
{
public float R;
public float G;
public float B;
public float A;
}
public struct BlendConstants
{
public float R;
public float G;
public float B;
public float A;
}
}

View File

@ -2,56 +2,54 @@
namespace MoonWorks.Graphics
{
/// <summary>
/// A texture slice specifies a subregion of a texture.
/// Many operations can use texture slices in place of textures for the sake of convenience.
/// </summary>
public struct TextureSlice
{
public Texture Texture { get; }
public Rect Rectangle { get; }
public uint Depth { get; }
public uint Layer { get; }
public uint Level { get; }
/// <summary>
/// A texture slice specifies a subregion of a texture.
/// Many operations can use texture slices in place of textures for the sake of convenience.
/// </summary>
public struct TextureSlice
{
public Texture Texture { get; }
public Rect Rectangle { get; }
public uint Depth { get; }
public uint Layer { get; }
public uint Level { get; }
public uint Size => (uint) (Rectangle.W * Rectangle.H * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format));
public TextureSlice(Texture texture)
{
Texture = texture;
Rectangle = new Rect
{
X = 0,
Y = 0,
W = (int) texture.Width,
H = (int) texture.Height
};
Depth = 0;
Layer = 0;
Level = 0;
}
public TextureSlice(Texture texture)
{
Texture = texture;
Rectangle = new Rect
{
X = 0,
Y = 0,
W = (int) texture.Width,
H = (int) texture.Height
};
Depth = 0;
Layer = 0;
Level = 0;
}
public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0)
{
Texture = texture;
Rectangle = rectangle;
Depth = depth;
Layer = layer;
Level = level;
}
public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0)
{
Texture = texture;
Rectangle = rectangle;
Depth = depth;
Layer = layer;
Level = level;
}
public Refresh.TextureSlice ToRefreshTextureSlice()
{
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice
{
texture = Texture.Handle,
rectangle = Rectangle.ToRefresh(),
depth = Depth,
layer = Layer,
level = Level
};
public Refresh.TextureSlice ToRefreshTextureSlice()
{
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice
{
texture = Texture.Handle,
rectangle = Rectangle.ToRefresh(),
depth = Depth,
layer = Layer,
level = Level
};
return textureSlice;
}
}
return textureSlice;
}
}
}

View File

@ -0,0 +1,33 @@
using System.IO;
namespace MoonWorks.Graphics
{
public static class Bytecode
{
public static uint[] ReadBytecodeAsUInt32(string filePath)
{
byte[] data;
int size;
using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
size = (int)stream.Length;
data = new byte[size];
stream.Read(data, 0, size);
}
uint[] uintData = new uint[size / 4];
using (var memoryStream = new MemoryStream(data))
{
using (var reader = new BinaryReader(memoryStream))
{
for (int i = 0; i < size / 4; i++)
{
uintData[i] = reader.ReadUInt32();
}
}
}
return uintData;
}
}
}

View File

@ -0,0 +1,15 @@
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 ? false : true;
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,40 +0,0 @@
using MoonWorks.Math;
using SDL2;
namespace MoonWorks.Input
{
/// <summary>
/// Represents a specific joystick direction on a gamepad.
/// </summary>
public class Axis
{
public Gamepad Parent { get; }
SDL.SDL_GameControllerAxis SDL_Axis;
public AxisCode Code { get; private set; }
/// <summary>
/// An axis value between -1 and 1.
/// </summary>
public float Value { get; private set; }
public Axis(
Gamepad parent,
AxisCode code,
SDL.SDL_GameControllerAxis sdlAxis
) {
Parent = parent;
SDL_Axis = sdlAxis;
Code = code;
}
internal void Update()
{
Value = MathHelper.Normalize(
SDL.SDL_GameControllerGetAxis(Parent.Handle, SDL_Axis),
short.MinValue, short.MaxValue,
-1, 1
);
}
}
}

View File

@ -1,17 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// Can be used to access a gamepad axis virtual button without a direct reference to the button object.
/// </summary>
public enum AxisButtonCode
{
LeftX_Left,
LeftX_Right,
LeftY_Up,
LeftY_Down,
RightX_Left,
RightX_Right,
RightY_Up,
RightY_Down
}
}

View File

@ -1,14 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// Can be used to access a gamepad axis without a direct reference to the axis object.
/// Enum values are equivalent to SDL_GameControllerAxis.
/// </summary>
public enum AxisCode
{
LeftX,
LeftY,
RightX,
RightY
}
}

View File

@ -1,25 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// Can be used to access a gamepad button without a direct reference to the button object.
/// Enum values are equivalent to the SDL GameControllerButton value.
/// </summary>
public enum GamepadButtonCode
{
A,
B,
X,
Y,
Back,
Guide,
Start,
LeftStick,
RightStick,
LeftShoulder,
RightShoulder,
DpadUp,
DpadDown,
DpadLeft,
DpadRight
}
}

View File

@ -1,97 +1,31 @@
namespace MoonWorks.Input
namespace MoonWorks.Input
{
/// <summary>
/// Container for the current state of a binary input.
/// </summary>
public struct ButtonState
{
public ButtonStatus ButtonStatus { get; }
public class ButtonState
{
private ButtonStatus ButtonStatus { get; set; }
/// <summary>
/// True if the button was pressed this frame.
/// </summary>
public bool IsPressed => ButtonStatus == ButtonStatus.Pressed;
public bool IsPressed => ButtonStatus == ButtonStatus.Pressed;
public bool IsHeld => ButtonStatus == ButtonStatus.Held;
public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held;
public bool IsReleased => ButtonStatus == ButtonStatus.Released;
/// <summary>
/// True if the button was pressed this frame and the previous frame.
/// </summary>
public bool IsHeld => ButtonStatus == ButtonStatus.Held;
/// <summary>
/// True if the button was either pressed or continued to be held this frame.
/// </summary>
public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held;
/// <summary>
/// True if the button was let go this frame.
/// </summary>
public bool IsReleased => ButtonStatus == ButtonStatus.Released;
/// <summary>
/// True if the button was not pressed this frame or the previous frame.
/// </summary>
public bool IsIdle => ButtonStatus == ButtonStatus.Idle;
/// <summary>
/// True if the button was either idle or released this frame.
/// </summary>
public bool IsUp => ButtonStatus == ButtonStatus.Idle || ButtonStatus == ButtonStatus.Released;
public ButtonState(ButtonStatus buttonStatus)
{
ButtonStatus = buttonStatus;
}
internal ButtonState Update(bool isPressed)
{
if (isPressed)
{
if (IsUp)
{
return new ButtonState(ButtonStatus.Pressed);
}
else
{
return new ButtonState(ButtonStatus.Held);
}
}
else
{
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)
{
if (a.ButtonStatus == ButtonStatus.Idle || a.ButtonStatus == ButtonStatus.Released)
{
return b;
}
else if (a.ButtonStatus == ButtonStatus.Pressed)
{
if (b.ButtonStatus == ButtonStatus.Held)
{
return new ButtonState(ButtonStatus.Held);
}
else
{
return a;
}
}
else // held
{
return a;
}
}
}
internal void Update(bool isPressed)
{
if (isPressed)
{
if (ButtonStatus == ButtonStatus.Pressed)
{
ButtonStatus = ButtonStatus.Held;
}
else if (ButtonStatus == ButtonStatus.Released)
{
ButtonStatus = ButtonStatus.Pressed;
}
}
else
{
ButtonStatus = ButtonStatus.Released;
}
}
}
}

View File

@ -1,25 +1,18 @@
namespace MoonWorks.Input
namespace MoonWorks.Input
{
/// <summary>
/// Represents the current status of a binary input.
/// </summary>
public enum ButtonStatus
{
/// <summary>
/// Indicates that the button was not pressed last frame and is still not pressed.
/// </summary>
Idle,
/// <summary>
/// Indicates that the button was released this frame.
/// </summary>
Released,
/// <summary>
/// Indicates that the button was pressed this frame.
/// </summary>
Pressed,
/// <summary>
/// Indicates that the button has been held for multiple frames.
/// </summary>
Held
}
internal enum ButtonStatus
{
/// <summary>
/// Indicates that the input is not pressed.
/// </summary>
Released,
/// <summary>
/// Indicates that the input was pressed this frame.
/// </summary>
Pressed,
/// <summary>
/// Indicates that the input has been held for multiple frames.
/// </summary>
Held
}
}

View File

@ -1,325 +1,98 @@
using System;
using System.Collections.Generic;
using System;
using MoonWorks.Math;
using SDL2;
namespace MoonWorks.Input
{
/// <summary>
/// A Gamepad input abstraction that represents input coming from a console controller or other such devices.
/// The button names map to a standard Xbox 360 controller.
/// For different controllers the relative position of the face buttons will determine the button mapping.
/// For example on a DualShock controller the Cross button will map to the A button.
/// </summary>
public class Gamepad
{
internal IntPtr Handle;
internal int JoystickInstanceID;
public class Gamepad
{
internal IntPtr Handle;
public int Slot { get; internal set; }
public ButtonState A { get; } = new ButtonState();
public ButtonState B { get; } = new ButtonState();
public ButtonState X { get; } = new ButtonState();
public ButtonState Y { get; } = new ButtonState();
public ButtonState Back { get; } = new ButtonState();
public ButtonState Guide { get; } = new ButtonState();
public ButtonState Start { get; } = new ButtonState();
public ButtonState LeftStick { get; } = new ButtonState();
public ButtonState RightStick { get; } = new ButtonState();
public ButtonState LeftShoulder { get; } = new ButtonState();
public ButtonState RightShoulder { get; } = new ButtonState();
public ButtonState DpadUp { get; } = new ButtonState();
public ButtonState DpadDown { get; } = new ButtonState();
public ButtonState DpadLeft { get; } = new ButtonState();
public ButtonState DpadRight { get; } = new ButtonState();
public GamepadButton A { get; }
public GamepadButton B { get; }
public GamepadButton X { get; }
public GamepadButton Y { get; }
public GamepadButton Back { get; }
public GamepadButton Guide { get; }
public GamepadButton Start { get; }
public GamepadButton LeftStick { get; }
public GamepadButton RightStick { get; }
public GamepadButton LeftShoulder { get; }
public GamepadButton RightShoulder { get; }
public GamepadButton DpadUp { get; }
public GamepadButton DpadDown { get; }
public GamepadButton DpadLeft { get; }
public GamepadButton DpadRight { get; }
public float LeftX { get; private set; }
public float LeftY { get; private set; }
public float RightX { get; private set; }
public float RightY { get; private set; }
public float TriggerLeft { get; private set; }
public float TriggerRight { get; private set; }
public Axis LeftX { get; }
public Axis LeftY { get; }
public Axis RightX { get; }
public Axis RightY { get; }
internal Gamepad(IntPtr handle)
{
Handle = handle;
}
public AxisButton LeftXLeft { get; }
public AxisButton LeftXRight { get; }
public AxisButton LeftYUp { get; }
public AxisButton LeftYDown { get; }
public bool SetVibration(float leftMotor, float rightMotor, uint durationInMilliseconds)
{
return SDL.SDL_GameControllerRumble(
Handle,
(ushort)(MathHelper.Clamp(leftMotor, 0f, 1f) * 0xFFFF),
(ushort)(MathHelper.Clamp(rightMotor, 0f, 1f) * 0xFFFF),
durationInMilliseconds
) == 0;
}
public AxisButton RightXLeft { get; }
public AxisButton RightXRight { get; }
public AxisButton RightYUp { get; }
public AxisButton RightYDown { get; }
internal void Update()
{
A.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A));
B.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B));
X.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X));
Y.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y));
Back.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK));
Guide.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE));
Start.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START));
LeftStick.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK));
RightStick.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK));
LeftShoulder.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER));
RightShoulder.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER));
DpadUp.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP));
DpadDown.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN));
DpadLeft.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT));
DpadRight.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT));
public Trigger TriggerLeft { get; }
public Trigger TriggerRight { get; }
LeftX = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX);
LeftY = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY);
RightX = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX);
RightY = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY);
TriggerLeft = UpdateTrigger(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT);
TriggerRight = UpdateTrigger(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
}
public TriggerButton TriggerLeftButton { get; }
public TriggerButton TriggerRightButton { get; }
private bool IsPressed(SDL.SDL_GameControllerButton button)
{
return MoonWorks.Conversions.ByteToBool(SDL.SDL_GameControllerGetButton(Handle, button));
}
public bool IsDummy => Handle == IntPtr.Zero;
private float UpdateAxis(SDL.SDL_GameControllerAxis axis)
{
var axisValue = SDL.SDL_GameControllerGetAxis(Handle, axis);
return Normalize(axisValue, short.MinValue, short.MaxValue, -1, 1);
}
/// <summary>
/// True if any input on the gamepad is active. Useful for input remapping.
/// </summary>
public bool AnyPressed { get; private set; }
// Triggers only go from 0 to short.MaxValue
private float UpdateTrigger(SDL.SDL_GameControllerAxis trigger)
{
var triggerValue = SDL.SDL_GameControllerGetAxis(Handle, trigger);
return Normalize(triggerValue, 0, short.MaxValue, 0, 1);
}
/// <summary>
/// Contains a reference to an arbitrary VirtualButton that was pressed on the gamepad this frame. Useful for input remapping.
/// </summary>
public VirtualButton AnyPressedButton { get; private set; }
private Dictionary<SDL.SDL_GameControllerButton, GamepadButton> EnumToButton;
private Dictionary<SDL.SDL_GameControllerAxis, Axis> EnumToAxis;
private Dictionary<SDL.SDL_GameControllerAxis, Trigger> EnumToTrigger;
private Dictionary<AxisButtonCode, AxisButton> AxisButtonCodeToAxisButton;
private Dictionary<TriggerCode, TriggerButton> TriggerCodeToTriggerButton;
private VirtualButton[] VirtualButtons;
internal Gamepad(IntPtr handle, int slot)
{
Handle = handle;
Slot = slot;
IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(Handle);
JoystickInstanceID = SDL.SDL_JoystickInstanceID(joystickHandle);
AnyPressed = false;
A = new GamepadButton(this, GamepadButtonCode.A, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A);
B = new GamepadButton(this, GamepadButtonCode.B, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B);
X = new GamepadButton(this, GamepadButtonCode.X, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X);
Y = new GamepadButton(this, GamepadButtonCode.Y, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y);
Back = new GamepadButton(this, GamepadButtonCode.Back, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK);
Guide = new GamepadButton(this, GamepadButtonCode.Guide, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE);
Start = new GamepadButton(this, GamepadButtonCode.Start, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START);
LeftStick = new GamepadButton(this, GamepadButtonCode.LeftStick, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK);
RightStick = new GamepadButton(this, GamepadButtonCode.RightStick, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK);
LeftShoulder = new GamepadButton(this, GamepadButtonCode.LeftShoulder, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
RightShoulder = new GamepadButton(this, GamepadButtonCode.RightShoulder, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
DpadUp = new GamepadButton(this, GamepadButtonCode.DpadUp, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP);
DpadDown = new GamepadButton(this, GamepadButtonCode.DpadDown, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN);
DpadLeft = new GamepadButton(this, GamepadButtonCode.DpadLeft, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT);
DpadRight = new GamepadButton(this, GamepadButtonCode.DpadRight, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
LeftX = new Axis(this, AxisCode.LeftX, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX);
LeftY = new Axis(this, AxisCode.LeftY, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY);
RightX = new Axis(this, AxisCode.RightX, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX);
RightY = new Axis(this, AxisCode.RightY, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY);
LeftXLeft = new AxisButton(LeftX, false);
LeftXRight = new AxisButton(LeftX, true);
LeftYUp = new AxisButton(LeftY, true);
LeftYDown = new AxisButton(LeftY, false);
RightXLeft = new AxisButton(RightX, false);
RightXRight = new AxisButton(RightX, true);
RightYUp = new AxisButton(RightY, true);
RightYDown = new AxisButton(RightY, false);
TriggerLeft = new Trigger(this, TriggerCode.Left, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT);
TriggerRight = new Trigger(this, TriggerCode.Right, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
TriggerLeftButton = new TriggerButton(TriggerLeft);
TriggerRightButton = new TriggerButton(TriggerRight);
EnumToButton = new Dictionary<SDL.SDL_GameControllerButton, GamepadButton>
{
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A, A },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B, B },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X, X },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y, Y },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK, Back },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE, Guide },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START, Start },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK, LeftStick },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK, RightStick },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, LeftShoulder },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, RightShoulder },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP, DpadUp },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN, DpadDown },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT, DpadLeft },
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, DpadRight }
};
EnumToAxis = new Dictionary<SDL.SDL_GameControllerAxis, Axis>
{
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX, LeftX },
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY, LeftY },
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX, RightX },
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY, RightY }
};
EnumToTrigger = new Dictionary<SDL.SDL_GameControllerAxis, Trigger>
{
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT, TriggerLeft },
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT, TriggerRight }
};
AxisButtonCodeToAxisButton = new Dictionary<AxisButtonCode, AxisButton>
{
{ AxisButtonCode.LeftX_Left, LeftXLeft },
{ AxisButtonCode.LeftX_Right, LeftXRight },
{ AxisButtonCode.LeftY_Down, LeftYDown },
{ AxisButtonCode.LeftY_Up, LeftYUp },
{ AxisButtonCode.RightX_Left, RightXLeft },
{ AxisButtonCode.RightX_Right, RightXRight },
{ AxisButtonCode.RightY_Up, RightYUp },
{ AxisButtonCode.RightY_Down, RightYDown }
};
TriggerCodeToTriggerButton = new Dictionary<TriggerCode, TriggerButton>
{
{ TriggerCode.Left, TriggerLeftButton },
{ TriggerCode.Right, TriggerRightButton }
};
VirtualButtons = new VirtualButton[]
{
A,
B,
X,
Y,
Back,
Guide,
Start,
LeftStick,
RightStick,
LeftShoulder,
RightShoulder,
DpadUp,
DpadDown,
DpadLeft,
DpadRight,
LeftXLeft,
LeftXRight,
LeftYUp,
LeftYDown,
RightXLeft,
RightXRight,
RightYUp,
RightYDown,
TriggerLeftButton,
TriggerRightButton
};
}
internal void Register(IntPtr handle)
{
Handle = handle;
IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(Handle);
JoystickInstanceID = SDL.SDL_JoystickInstanceID(joystickHandle);
}
internal void Unregister()
{
Handle = IntPtr.Zero;
JoystickInstanceID = -1;
}
internal void Update()
{
AnyPressed = false;
if (!IsDummy)
{
foreach (var button in EnumToButton.Values)
{
button.Update();
}
foreach (var axis in EnumToAxis.Values)
{
axis.Update();
}
foreach (var trigger in EnumToTrigger.Values)
{
trigger.Update();
}
LeftXLeft.Update();
LeftXRight.Update();
LeftYUp.Update();
LeftYDown.Update();
RightXLeft.Update();
RightXRight.Update();
RightYUp.Update();
RightYDown.Update();
TriggerLeftButton.Update();
TriggerRightButton.Update();
foreach (var button in VirtualButtons)
{
if (button.IsPressed)
{
AnyPressed = true;
AnyPressedButton = button;
break;
}
}
}
}
/// <summary>
/// Sets vibration values on the left and right motors.
/// </summary>
public bool SetVibration(float leftMotor, float rightMotor, uint durationInMilliseconds)
{
return SDL.SDL_GameControllerRumble(
Handle,
(ushort) (MathHelper.Clamp(leftMotor, 0f, 1f) * 0xFFFF),
(ushort) (MathHelper.Clamp(rightMotor, 0f, 1f) * 0xFFFF),
durationInMilliseconds
) == 0;
}
/// <summary>
/// Obtains a gamepad button object given a button code.
/// </summary>
public GamepadButton Button(GamepadButtonCode buttonCode)
{
return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
}
/// <summary>
/// Obtains an axis button object given a button code.
/// </summary>
public AxisButton Button(AxisButtonCode axisButtonCode)
{
return AxisButtonCodeToAxisButton[axisButtonCode];
}
/// <summary>
/// Obtains a trigger button object given a button code.
/// </summary>
public TriggerButton Button(TriggerCode triggerCode)
{
return TriggerCodeToTriggerButton[triggerCode];
}
/// <summary>
/// Obtains the axis value given an AxisCode.
/// </summary>
/// <returns>A value between -1 and 1.</returns>
public float AxisValue(AxisCode axisCode)
{
return EnumToAxis[(SDL.SDL_GameControllerAxis) axisCode].Value;
}
/// <summary>
/// Obtains the trigger value given an TriggerCode.
/// </summary>
/// <returns>A value between 0 and 1.</returns>
public float TriggerValue(TriggerCode triggerCode)
{
return EnumToTrigger[(SDL.SDL_GameControllerAxis) triggerCode].Value;
}
}
private float Normalize(float value, short min, short max, short newMin, short newMax)
{
return ((value - min) * (newMax - newMin)) / (max - min) + newMin;
}
}
}

View File

@ -1,182 +1,60 @@
using SDL2;
using SDL2;
using System;
using System.Collections.Generic;
namespace MoonWorks.Input
{
/// <summary>
/// The main container class for all input tracking.
/// Your Game class will automatically have a reference to this class.
/// </summary>
public class Inputs
{
public const int MAX_GAMEPADS = 4;
public class Inputs
{
public Keyboard Keyboard { get; }
public Mouse Mouse { get; }
/// <summary>
/// The reference to the Keyboard input abstraction.
/// </summary>
public Keyboard Keyboard { get; }
List<Gamepad> gamepads = new List<Gamepad>();
/// <summary>
/// The reference to the Mouse input abstraction.
/// </summary>
public Mouse Mouse { get; }
public static event Action<char> TextInput;
Gamepad[] Gamepads;
internal Inputs()
{
Keyboard = new Keyboard();
Mouse = new Mouse();
public static event Action<char> TextInput;
for (int i = 0; i < SDL.SDL_NumJoysticks(); i++)
{
if (SDL.SDL_IsGameController(i) == SDL.SDL_bool.SDL_TRUE)
{
gamepads.Add(new Gamepad(SDL.SDL_GameControllerOpen(i)));
}
}
}
/// <summary>
/// True if any input on any input device is active. Useful for input remapping.
/// </summary>
public bool AnyPressed { get; private set; }
// Assumes that SDL_PumpEvents has been called!
internal void Update()
{
Keyboard.Update();
Mouse.Update();
/// <summary>
/// Contains a reference to an arbitrary VirtualButton that was pressed this frame. Useful for input remapping.
/// </summary>
public VirtualButton AnyPressedButton { get; private set; }
foreach (var gamepad in gamepads)
{
gamepad.Update();
}
}
public delegate void OnGamepadConnectedFunc(int slot);
public bool GamepadExists(int slot)
{
return slot < gamepads.Count;
}
/// <summary>
/// Called when a gamepad has been connected.
/// </summary>
/// <param name="slot">The slot where the connection occurred.</param>
public OnGamepadConnectedFunc OnGamepadConnected = delegate { };
public Gamepad GetGamepad(int slot)
{
return gamepads[slot];
}
public delegate void OnGamepadDisconnectedFunc(int slot);
/// <summary>
/// Called when a gamepad has been disconnected.
/// </summary>
/// <param name="slot">The slot where the disconnection occurred.</param>
public OnGamepadDisconnectedFunc OnGamepadDisconnected = delegate { };
internal Inputs()
{
Keyboard = new Keyboard();
Mouse = new Mouse();
Gamepads = new Gamepad[MAX_GAMEPADS];
// initialize dummy controllers
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
{
Gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
}
}
// Assumes that SDL_PumpEvents has been called!
internal void Update()
{
AnyPressed = false;
AnyPressedButton = default; // DeviceKind.None
Keyboard.Update();
if (Keyboard.AnyPressed)
{
AnyPressed = true;
AnyPressedButton = Keyboard.AnyPressedButton;
}
Mouse.Update();
if (Mouse.AnyPressed)
{
AnyPressed = true;
AnyPressedButton = Mouse.AnyPressedButton;
}
foreach (var gamepad in Gamepads)
{
gamepad.Update();
if (gamepad.AnyPressed)
{
AnyPressed = true;
AnyPressedButton = gamepad.AnyPressedButton;
}
}
}
/// <summary>
/// Returns true if a gamepad is currently connected in the given slot.
/// </summary>
/// <param name="slot">Range: 0-3</param>
/// <returns></returns>
public bool GamepadExists(int slot)
{
if (slot < 0 || slot >= MAX_GAMEPADS)
{
return false;
}
return !Gamepads[slot].IsDummy;
}
/// <summary>
/// Gets a gamepad associated with the given slot.
/// The first n slots are guaranteed to occupied with gamepads if they are connected.
/// If a gamepad does not exist for the given slot, a dummy object with all inputs in default state will be returned.
/// You can check if a gamepad is connected in a slot with the GamepadExists function.
/// </summary>
/// <param name="slot">Range: 0-3</param>
public Gamepad GetGamepad(int slot)
{
return Gamepads[slot];
}
internal void AddGamepad(int index)
{
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
{
if (!GamepadExists(slot))
{
var openResult = SDL.SDL_GameControllerOpen(index);
if (openResult == 0)
{
Logger.LogError("Error opening gamepad!");
Logger.LogError(SDL.SDL_GetError());
}
else
{
Gamepads[slot].Register(openResult);
Logger.LogInfo($"Gamepad added to slot {slot}!");
if (OnGamepadConnected != null)
{
OnGamepadConnected(slot);
}
}
return;
}
}
Logger.LogInfo("Too many gamepads already!");
}
internal void RemoveGamepad(int joystickInstanceID)
{
for (int slot = 0; slot < MAX_GAMEPADS; slot += 1)
{
if (joystickInstanceID == Gamepads[slot].JoystickInstanceID)
{
SDL.SDL_GameControllerClose(Gamepads[slot].Handle);
Gamepads[slot].Unregister();
Logger.LogInfo($"Removing gamepad from slot {slot}!");
OnGamepadDisconnected(slot);
return;
}
}
}
internal static void OnTextInput(char c)
{
if (TextInput != null)
{
TextInput(c);
}
}
}
internal static void OnTextInput(char c)
{
if (TextInput != null)
{
TextInput(c);
}
}
}
}

View File

@ -1,116 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// Can be used to determine key state without a direct reference to the virtual button object.
/// Enum values are equivalent to the SDL Scancode value.
/// </summary>
public enum KeyCode : int
{
Unknown = 0,
A = 4,
B = 5,
C = 6,
D = 7,
E = 8,
F = 9,
G = 10,
H = 11,
I = 12,
J = 13,
K = 14,
L = 15,
M = 16,
N = 17,
O = 18,
P = 19,
Q = 20,
R = 21,
S = 22,
T = 23,
U = 24,
V = 25,
W = 26,
X = 27,
Y = 28,
Z = 29,
D1 = 30,
D2 = 31,
D3 = 32,
D4 = 33,
D5 = 34,
D6 = 35,
D7 = 36,
D8 = 37,
D9 = 38,
D0 = 39,
Return = 40,
Escape = 41,
Backspace = 42,
Tab = 43,
Space = 44,
Minus = 45,
Equals = 46,
LeftBracket = 47,
RightBracket = 48,
Backslash = 49,
NonUSHash = 50,
Semicolon = 51,
Apostrophe = 52,
Grave = 53,
Comma = 54,
Period = 55,
Slash = 56,
CapsLock = 57,
F1 = 58,
F2 = 59,
F3 = 60,
F4 = 61,
F5 = 62,
F6 = 63,
F7 = 64,
F8 = 65,
F9 = 66,
F10 = 67,
F11 = 68,
F12 = 69,
PrintScreen = 70,
ScrollLock = 71,
Pause = 72,
Insert = 73,
Home = 74,
PageUp = 75,
Delete = 76,
End = 77,
PageDown = 78,
Right = 79,
Left = 80,
Down = 81,
Up = 82,
NumLockClear = 83,
KeypadDivide = 84,
KeypadMultiply = 85,
KeypadMinus = 86,
KeypadPlus = 87,
KeypadEnter = 88,
Keypad1 = 89,
Keypad2 = 90,
Keypad3 = 91,
Keypad4 = 92,
Keypad5 = 93,
Keypad6 = 94,
Keypad7 = 95,
Keypad8 = 96,
Keypad9 = 97,
Keypad0 = 98,
KeypadPeriod = 99,
NonUSBackslash = 100,
LeftControl = 224,
LeftShift = 225,
LeftAlt = 226,
LeftMeta = 227, // Windows, Command, Meta
RightControl = 228,
RightShift = 229,
RightAlt = 230,
RightMeta = 231 // Windows, Command, Meta
}
}

View File

@ -1,29 +1,14 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using SDL2;
namespace MoonWorks.Input
{
/// <summary>
/// The keyboard input device abstraction.
/// </summary>
public class Keyboard
{
/// <summary>
/// True if any button on the keyboard is active. Useful for input remapping.
/// </summary>
public bool AnyPressed { get; private set; }
/// <summary>
/// Contains a reference to an arbitrary KeyboardButton that was pressed this frame. Useful for input remapping.
/// </summary>
public KeyboardButton AnyPressedButton { get; private set; }
internal IntPtr State { get; private set; }
private KeyCode[] KeyCodes;
private KeyboardButton[] Keys { get; }
private int numKeys;
public class Keyboard
{
private ButtonState[] Keys { get; }
private int numKeys;
private static readonly char[] TextInputCharacters = new char[]
{
@ -36,120 +21,69 @@ namespace MoonWorks.Input
(char) 22 // Ctrl+V (Paste)
};
private static readonly Dictionary<KeyCode, int> TextInputBindings = new Dictionary<KeyCode, int>()
private static readonly Dictionary<Keycode, int> TextInputBindings = new Dictionary<Keycode, int>()
{
{ KeyCode.Home, 0 },
{ KeyCode.End, 1 },
{ KeyCode.Backspace, 2 },
{ KeyCode.Tab, 3 },
{ KeyCode.Return, 4 },
{ KeyCode.Delete, 5 }
{ Keycode.Home, 0 },
{ Keycode.End, 1 },
{ Keycode.Backspace, 2 },
{ Keycode.Tab, 3 },
{ Keycode.Return, 4 },
{ Keycode.Delete, 5 }
// Ctrl+V is special!
};
internal Keyboard()
{
SDL.SDL_GetKeyboardState(out numKeys);
internal Keyboard()
{
SDL.SDL_GetKeyboardState(out numKeys);
KeyCodes = Enum.GetValues<KeyCode>();
Keys = new KeyboardButton[numKeys];
Keys = new ButtonState[numKeys];
foreach (Keycode keycode in Enum.GetValues(typeof(Keycode)))
{
Keys[(int)keycode] = new ButtonState();
}
}
foreach (KeyCode keycode in KeyCodes)
{
Keys[(int) keycode] = new KeyboardButton(this, keycode);
}
}
internal void Update()
{
IntPtr keyboardState = SDL.SDL_GetKeyboardState(out _);
internal void Update()
{
AnyPressed = false;
foreach (int keycode in Enum.GetValues(typeof(Keycode)))
{
var keyDown = Marshal.ReadByte(keyboardState, keycode);
Keys[keycode].Update(Conversions.ByteToBool(keyDown));
State = SDL.SDL_GetKeyboardState(out _);
if (Conversions.ByteToBool(keyDown))
{
if (TextInputBindings.TryGetValue((Keycode)keycode, out var textIndex))
{
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
}
else if (IsDown(Keycode.LeftControl) && (Keycode)keycode == Keycode.V)
{
Inputs.OnTextInput(TextInputCharacters[6]);
}
}
}
}
foreach (KeyCode keycode in KeyCodes)
{
var button = Keys[(int) keycode];
button.Update();
public bool IsDown(Keycode keycode)
{
return Keys[(int)keycode].IsDown;
}
if (button.IsPressed)
{
if (TextInputBindings.TryGetValue(keycode, out var textIndex))
{
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
}
else if (IsDown(KeyCode.LeftControl) && keycode == KeyCode.V)
{
Inputs.OnTextInput(TextInputCharacters[6]);
}
public bool IsPressed(Keycode keycode)
{
return Keys[(int)keycode].IsPressed;
}
AnyPressed = true;
AnyPressedButton = button;
}
}
}
public bool IsHeld(Keycode keycode)
{
return Keys[(int)keycode].IsHeld;
}
/// <summary>
/// True if the button was pressed this frame.
/// </summary>
public bool IsPressed(KeyCode keycode)
{
return Keys[(int) keycode].IsPressed;
}
/// <summary>
/// True if the button was pressed this frame and the previous frame.
/// </summary>
public bool IsHeld(KeyCode keycode)
{
return Keys[(int) keycode].IsHeld;
}
/// <summary>
/// True if the button was either pressed or continued to be held this frame.
/// </summary>
public bool IsDown(KeyCode keycode)
{
return Keys[(int) keycode].IsDown;
}
/// <summary>
/// True if the button was let go this frame.
/// </summary>
public bool IsReleased(KeyCode keycode)
{
return Keys[(int) keycode].IsReleased;
}
/// <summary>
/// True if the button was not pressed this frame or the previous frame.
/// </summary>
public bool IsIdle(KeyCode keycode)
{
return Keys[(int) keycode].IsIdle;
}
/// <summary>
/// True if the button was either idle or released this frame.
/// </summary>
public bool IsUp(KeyCode keycode)
{
return Keys[(int) keycode].IsUp;
}
/// <summary>
/// Gets a reference to a keyboard button object using a key code.
/// </summary>
public KeyboardButton Button(KeyCode keycode)
{
return Keys[(int) keycode];
}
/// <summary>
/// Gets the state of a keyboard button from a key code.
/// </summary>
public ButtonState ButtonState(KeyCode keycode)
{
return Keys[(int) keycode].State;
}
}
public bool IsReleased(Keycode keycode)
{
return Keys[(int)keycode].IsReleased;
}
}
}

113
src/Input/Keycode.cs Normal file
View File

@ -0,0 +1,113 @@
namespace MoonWorks.Input
{
// Enum values are equivalent to the SDL Scancode value.
public enum Keycode : int
{
Unknown = 0,
A = 4,
B = 5,
C = 6,
D = 7,
E = 8,
F = 9,
G = 10,
H = 11,
I = 12,
J = 13,
K = 14,
L = 15,
M = 16,
N = 17,
O = 18,
P = 19,
Q = 20,
R = 21,
S = 22,
T = 23,
U = 24,
V = 25,
W = 26,
X = 27,
Y = 28,
Z = 29,
D1 = 30,
D2 = 31,
D3 = 32,
D4 = 33,
D5 = 34,
D6 = 35,
D7 = 36,
D8 = 37,
D9 = 38,
D0 = 39,
Return = 40,
Escape = 41,
Backspace = 42,
Tab = 43,
Space = 44,
Minus = 45,
Equals = 46,
LeftBracket = 47,
RightBracket = 48,
Backslash = 49,
NonUSHash = 50,
Semicolon = 51,
Apostrophe = 52,
Grave = 53,
Comma = 54,
Period = 55,
Slash = 56,
CapsLock = 57,
F1 = 58,
F2 = 59,
F3 = 60,
F4 = 61,
F5 = 62,
F6 = 63,
F7 = 64,
F8 = 65,
F9 = 66,
F10 = 67,
F11 = 68,
F12 = 69,
PrintScreen = 70,
ScrollLock = 71,
Pause = 72,
Insert = 73,
Home = 74,
PageUp = 75,
Delete = 76,
End = 77,
PageDown = 78,
Right = 79,
Left = 80,
Down = 81,
Up = 82,
NumLockClear = 83,
KeypadDivide = 84,
KeypadMultiply = 85,
KeypadMinus = 86,
KeypadPlus = 87,
KeypadEnter = 88,
Keypad1 = 89,
Keypad2 = 90,
Keypad3 = 91,
Keypad4 = 92,
Keypad5 = 93,
Keypad6 = 94,
Keypad7 = 95,
Keypad8 = 96,
Keypad9 = 97,
Keypad0 = 98,
KeypadPeriod = 99,
NonUSBackslash = 100,
LeftControl = 224,
LeftShift = 225,
LeftAlt = 226,
LeftMeta = 227, // Windows, Command, Meta
RightControl = 228,
RightShift = 229,
RightAlt = 230,
RightMeta = 231 // Windows, Command, Meta
}
}

View File

@ -1,135 +1,53 @@
using System.Collections.Generic;
using SDL2;
using SDL2;
namespace MoonWorks.Input
{
/// <summary>
/// The mouse input device abstraction.
/// </summary>
public class Mouse
{
public MouseButton LeftButton { get; }
public MouseButton MiddleButton { get; }
public MouseButton RightButton { get; }
public MouseButton X1Button { get; }
public MouseButton X2Button { get; }
public class Mouse
{
public ButtonState LeftButton { get; } = new ButtonState();
public ButtonState MiddleButton { get; } = new ButtonState();
public ButtonState RightButton { get; } = new ButtonState();
public int X { get; private set; }
public int Y { get; private set; }
public int DeltaX { get; private set; }
public int DeltaY { get; private set; }
public int X { get; private set; }
public int Y { get; private set; }
public int DeltaX { get; private set; }
public int DeltaY { get; private set; }
// note that this is a delta value
public int Wheel { get; private set; }
internal int WheelRaw;
private int previousWheelRaw = 0;
public int Wheel { get; internal set; }
/// <summary>
/// True if any button on the keyboard is active. Useful for input remapping.
/// </summary>
public bool AnyPressed { get; private set; }
private bool relativeMode;
public bool RelativeMode
{
get => relativeMode;
set
{
relativeMode = value;
SDL.SDL_SetRelativeMouseMode(
relativeMode ?
SDL.SDL_bool.SDL_TRUE :
SDL.SDL_bool.SDL_FALSE
);
}
}
/// <summary>
/// Contains a reference to an arbitrary MouseButton that was pressed this frame. Useful for input remapping.
/// </summary>
public MouseButton AnyPressedButton { get; private set; }
internal void Update()
{
var buttonMask = SDL.SDL_GetMouseState(out var x, out var y);
var _ = SDL.SDL_GetRelativeMouseState(out var deltaX, out var deltaY);
internal uint ButtonMask { get; private set; }
X = x;
Y = y;
DeltaX = deltaX;
DeltaY = deltaY;
private bool relativeMode;
/// <summary>
/// If set to true, the cursor is hidden, the mouse position is constrained to the window,
/// and relative mouse motion will be reported even if the mouse is at the edge of the window.
/// </summary>
public bool RelativeMode
{
get => relativeMode;
set
{
relativeMode = value;
SDL.SDL_SetRelativeMouseMode(
relativeMode ?
SDL.SDL_bool.SDL_TRUE :
SDL.SDL_bool.SDL_FALSE
);
}
}
LeftButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_LMASK));
MiddleButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_MMASK));
RightButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_RMASK));
}
private bool hidden;
/// <summary>
/// If set to true, the OS cursor will not be shown in your application window.
/// </summary>
public bool Hidden
{
get => hidden;
set
{
hidden = value;
SDL.SDL_ShowCursor(hidden ? SDL.SDL_DISABLE : SDL.SDL_ENABLE);
}
}
private readonly Dictionary<MouseButtonCode, MouseButton> CodeToButton;
internal Mouse()
{
LeftButton = new MouseButton(this, MouseButtonCode.Left, SDL.SDL_BUTTON_LMASK);
MiddleButton = new MouseButton(this, MouseButtonCode.Middle, SDL.SDL_BUTTON_MMASK);
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>
{
{ MouseButtonCode.Left, LeftButton },
{ MouseButtonCode.Right, RightButton },
{ MouseButtonCode.Middle, MiddleButton },
{ MouseButtonCode.X1, X1Button },
{ MouseButtonCode.X2, X2Button }
};
}
internal void Update()
{
AnyPressed = false;
ButtonMask = SDL.SDL_GetMouseState(out var x, out var y);
var _ = SDL.SDL_GetRelativeMouseState(out var deltaX, out var deltaY);
X = x;
Y = y;
DeltaX = deltaX;
DeltaY = deltaY;
Wheel = WheelRaw - previousWheelRaw;
previousWheelRaw = WheelRaw;
foreach (var button in CodeToButton.Values)
{
button.Update();
if (button.IsPressed)
{
AnyPressed = true;
AnyPressedButton = button;
}
}
}
/// <summary>
/// Gets a button from the mouse given a MouseButtonCode.
/// </summary>
public MouseButton Button(MouseButtonCode buttonCode)
{
return CodeToButton[buttonCode];
}
/// <summary>
/// Gets a button state from a mouse button corresponding to the given MouseButtonCode.
/// </summary>
public ButtonState ButtonState(MouseButtonCode buttonCode)
{
return CodeToButton[buttonCode].State;
}
}
private bool IsPressed(uint buttonMask, uint buttonFlag)
{
return (buttonMask & buttonFlag) != 0;
}
}
}

View File

@ -1,14 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// Can be used to determine virtual mouse button state without a direct reference to the button object.
/// </summary>
public enum MouseButtonCode
{
Left,
Right,
Middle,
X1,
X2
}
}

View File

@ -1,40 +0,0 @@
using MoonWorks.Math;
using SDL2;
namespace MoonWorks.Input
{
/// <summary>
/// Represents a trigger input on a gamepad.
/// </summary>
public class Trigger
{
public Gamepad Parent { get; }
public SDL.SDL_GameControllerAxis SDL_Axis;
public TriggerCode Code { get; }
/// <summary>
/// A trigger value between 0 and 1.
/// </summary>
public float Value { get; private set; }
public Trigger(
Gamepad parent,
TriggerCode code,
SDL.SDL_GameControllerAxis sdlAxis
) {
Parent = parent;
Code = code;
SDL_Axis = sdlAxis;
}
internal void Update()
{
Value = MathHelper.Normalize(
SDL.SDL_GameControllerGetAxis(Parent.Handle, SDL_Axis),
0, short.MaxValue,
0, 1
);
}
}
}

View File

@ -1,12 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// Can be used to determine trigger state or trigger virtual button state without direct reference to the trigger object or virtual button object.
/// Enum values correspond to SDL_GameControllerAxis.
/// </summary>
public enum TriggerCode
{
Left = 4,
Right = 5
}
}

View File

@ -1,47 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// VirtualButtons map inputs to binary inputs, like a trigger threshold or joystick axis threshold.
/// </summary>
public abstract class VirtualButton
{
public ButtonState State { get; protected set; }
/// <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>
/// True if the button is pressed or held.
/// </summary>
public bool IsDown => State.IsDown;
/// <summary>
/// True if the button was released this frame.
/// </summary>
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()
{
State = State.Update(CheckPressed());
}
internal abstract bool CheckPressed();
}
}

View File

@ -1,77 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// A virtual button corresponding to a direction on a joystick.
/// If the axis value exceeds the threshold, it will be treated as a press.
/// </summary>
public class AxisButton : VirtualButton
{
public Axis Parent { get; }
public AxisButtonCode Code { get; }
private float threshold = 0.5f;
public float Threshold
{
get => threshold;
set => threshold = System.Math.Clamp(value, 0, 1);
}
private int Sign;
internal AxisButton(Axis parent, bool positive)
{
Parent = parent;
Sign = positive ? 1 : -1;
if (parent.Code == AxisCode.LeftX)
{
if (positive)
{
Code = AxisButtonCode.LeftX_Right;
}
else
{
Code = AxisButtonCode.LeftX_Left;
}
}
else if (parent.Code == AxisCode.LeftY)
{
if (positive)
{
Code = AxisButtonCode.LeftY_Up;
}
else
{
Code = AxisButtonCode.LeftY_Down;
}
}
else if (parent.Code == AxisCode.RightX)
{
if (positive)
{
Code = AxisButtonCode.RightX_Right;
}
else
{
Code = AxisButtonCode.RightX_Left;
}
}
else if (parent.Code == AxisCode.RightY)
{
if (positive)
{
Code = AxisButtonCode.RightY_Up;
}
else
{
Code = AxisButtonCode.RightY_Down;
}
}
}
internal override bool CheckPressed()
{
return Sign * Parent.Value >= threshold;
}
}
}

View File

@ -1,13 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// A dummy button that can never be pressed. Used for the dummy gamepad.
/// </summary>
public class EmptyButton : VirtualButton
{
internal override bool CheckPressed()
{
return false;
}
}
}

View File

@ -1,26 +0,0 @@
using SDL2;
namespace MoonWorks.Input
{
/// <summary>
/// A virtual button corresponding to a gamepad button.
/// </summary>
public class GamepadButton : VirtualButton
{
public Gamepad Parent { get; }
SDL.SDL_GameControllerButton SDL_Button;
public GamepadButtonCode Code { get; }
internal GamepadButton(Gamepad parent, GamepadButtonCode code, SDL.SDL_GameControllerButton sdlButton)
{
Parent = parent;
Code = code;
SDL_Button = sdlButton;
}
internal override bool CheckPressed()
{
return MoonWorks.Conversions.ByteToBool(SDL.SDL_GameControllerGetButton(Parent.Handle, SDL_Button));
}
}
}

View File

@ -1,22 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// A virtual button corresponding to a keyboard button.
/// </summary>
public class KeyboardButton : VirtualButton
{
Keyboard Parent;
public KeyCode KeyCode { get; }
internal KeyboardButton(Keyboard parent, KeyCode keyCode)
{
Parent = parent;
KeyCode = keyCode;
}
internal unsafe override bool CheckPressed()
{
return Conversions.ByteToBool(((byte*) Parent.State)[(int) KeyCode]);
}
}
}

View File

@ -1,25 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// A virtual button corresponding to a mouse button.
/// </summary>
public class MouseButton : VirtualButton
{
Mouse Parent;
uint ButtonMask;
public MouseButtonCode Code { get; private set; }
internal MouseButton(Mouse parent, MouseButtonCode code, uint buttonMask)
{
Parent = parent;
Code = code;
ButtonMask = buttonMask;
}
internal override bool CheckPressed()
{
return (Parent.ButtonMask & ButtonMask) != 0;
}
}
}

View File

@ -1,29 +0,0 @@
namespace MoonWorks.Input
{
/// <summary>
/// A virtual button corresponding to a trigger on a gamepad.
/// If the trigger value exceeds the threshold, it will be treated as a press.
/// </summary>
public class TriggerButton : VirtualButton
{
public Trigger Parent { get; }
public TriggerCode Code => Parent.Code;
private float threshold = 0.7f;
public float Threshold
{
get => threshold;
set => threshold = System.Math.Clamp(value, 0, 1);
}
internal TriggerButton(Trigger parent)
{
Parent = parent;
}
internal override bool CheckPressed()
{
return Parent.Value >= Threshold;
}
}
}

View File

@ -1,80 +1,69 @@
using System;
using System;
using RefreshCS;
namespace MoonWorks
{
public static class Logger
{
public static Action<string> LogInfo = LogInfoDefault;
public static Action<string> LogWarn = LogWarnDefault;
public static Action<string> LogError = LogErrorDefault;
public static class Logger
{
public static Action<string> LogInfo;
public static Action<string> LogWarn;
public static Action<string> LogError;
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
private static RefreshCS.Refresh.Refresh_LogFunc LogErrorFunc = RefreshLogError;
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
private static RefreshCS.Refresh.Refresh_LogFunc LogErrorFunc = RefreshLogError;
internal static void Initialize()
{
Refresh.Refresh_HookLogFunctions(
LogInfoFunc,
LogWarnFunc,
LogErrorFunc
);
}
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;
}
private static void LogInfoDefault(string str)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("INFO: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
}
Refresh.Refresh_HookLogFunctions(
LogInfoFunc,
LogWarnFunc,
LogErrorFunc
);
}
private static void LogWarnDefault(string str)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("WARN: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
}
private static void RefreshLogInfo(IntPtr msg)
{
LogInfo(UTF8_ToManaged(msg));
}
private static void LogErrorDefault(string str)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("ERROR: ");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
}
private static void RefreshLogWarn(IntPtr msg)
{
LogWarn(UTF8_ToManaged(msg));
}
private static void RefreshLogInfo(IntPtr msg)
{
LogInfo(UTF8_ToManaged(msg));
}
private static void RefreshLogError(IntPtr msg)
{
LogError(UTF8_ToManaged(msg));
}
private static void RefreshLogWarn(IntPtr msg)
{
LogWarn(UTF8_ToManaged(msg));
}
private unsafe static string UTF8_ToManaged(IntPtr s)
{
byte* ptr = (byte*) s;
while (*ptr != 0)
{
ptr += 1;
}
private static void RefreshLogError(IntPtr msg)
{
LogError(UTF8_ToManaged(msg));
}
string result = System.Text.Encoding.UTF8.GetString(
(byte*) s,
(int) (ptr - (byte*) s)
);
private unsafe static string UTF8_ToManaged(IntPtr s)
{
byte* ptr = (byte*) s;
while (*ptr != 0)
{
ptr += 1;
}
string result = System.Text.Encoding.UTF8.GetString(
(byte*) s,
(int) (ptr - (byte*) s)
);
return result;
}
}
return result;
}
}
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]
@ -91,23 +91,23 @@ namespace MoonWorks.Math.Float
public ContainmentType Contains(BoundingBox box)
{
// Test if all corner is in the same side of a face by just checking min and max
if (box.Max.X < Min.X ||
if ( box.Max.X < Min.X ||
box.Min.X > Max.X ||
box.Max.Y < Min.Y ||
box.Min.Y > Max.Y ||
box.Max.Z < Min.Z ||
box.Min.Z > Max.Z)
box.Min.Z > Max.Z )
{
return ContainmentType.Disjoint;
}
if (box.Min.X >= Min.X &&
if ( box.Min.X >= Min.X &&
box.Max.X <= Max.X &&
box.Min.Y >= Min.Y &&
box.Max.Y <= Max.Y &&
box.Min.Z >= Min.Z &&
box.Max.Z <= Max.Z)
box.Max.Z <= Max.Z )
{
return ContainmentType.Contains;
}
@ -172,12 +172,12 @@ namespace MoonWorks.Math.Float
public ContainmentType Contains(BoundingSphere sphere)
{
if (sphere.Center.X - Min.X >= sphere.Radius &&
if ( sphere.Center.X - Min.X >= sphere.Radius &&
sphere.Center.Y - Min.Y >= sphere.Radius &&
sphere.Center.Z - Min.Z >= sphere.Radius &&
Max.X - sphere.Center.X >= sphere.Radius &&
Max.Y - sphere.Center.Y >= sphere.Radius &&
Max.Z - sphere.Center.Z >= sphere.Radius)
Max.Z - sphere.Center.Z >= sphere.Radius )
{
return ContainmentType.Contains;
}
@ -261,12 +261,12 @@ namespace MoonWorks.Math.Float
public void Contains(ref Vector3 point, out ContainmentType result)
{
// Determine if point is outside of this box.
if (point.X < this.Min.X ||
if ( point.X < this.Min.X ||
point.X > this.Max.X ||
point.Y < this.Min.Y ||
point.Y > this.Max.Y ||
point.Z < this.Min.Z ||
point.Z > this.Max.Z)
point.Z > this.Max.Z )
{
result = ContainmentType.Disjoint;
}
@ -380,12 +380,12 @@ namespace MoonWorks.Math.Float
public bool Intersects(BoundingSphere sphere)
{
if (sphere.Center.X - Min.X > sphere.Radius &&
if ( sphere.Center.X - Min.X > sphere.Radius &&
sphere.Center.Y - Min.Y > sphere.Radius &&
sphere.Center.Z - Min.Z > sphere.Radius &&
Max.X - sphere.Center.X > sphere.Radius &&
Max.Y - sphere.Center.Y > sphere.Radius &&
Max.Z - sphere.Center.Z > sphere.Radius)
Max.Z - sphere.Center.Z > sphere.Radius )
{
return true;
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
using System.Text;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Defines a viewing frustum for intersection operations.
@ -230,12 +230,12 @@ namespace MoonWorks.Math.Float
box.Intersects(ref this.planes[i], out planeIntersectionType);
switch (planeIntersectionType)
{
case PlaneIntersectionType.Front:
result = ContainmentType.Disjoint;
return;
case PlaneIntersectionType.Intersecting:
intersects = true;
break;
case PlaneIntersectionType.Front:
result = ContainmentType.Disjoint;
return;
case PlaneIntersectionType.Intersecting:
intersects = true;
break;
}
}
result = intersects ? ContainmentType.Intersects : ContainmentType.Contains;
@ -269,12 +269,12 @@ namespace MoonWorks.Math.Float
sphere.Intersects(ref this.planes[i], out planeIntersectionType);
switch (planeIntersectionType)
{
case PlaneIntersectionType.Front:
result = ContainmentType.Disjoint;
return;
case PlaneIntersectionType.Intersecting:
intersects = true;
break;
case PlaneIntersectionType.Front:
result = ContainmentType.Disjoint;
return;
case PlaneIntersectionType.Intersecting:
intersects = true;
break;
}
}
result = intersects ? ContainmentType.Intersects : ContainmentType.Contains;
@ -597,8 +597,7 @@ namespace MoonWorks.Math.Float
ref Plane b,
ref Plane c,
out Vector3 result
)
{
) {
/* Formula used
* d1 ( N2 * N3 ) + d2 ( N3 * N1 ) + d3 ( N1 * N2 )
* P = -------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Describes a sphere in 3D-space for bounding operations.
@ -297,8 +297,8 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(BoundingSphere other)
{
return (Center == other.Center &&
Radius == other.Radius);
return ( Center == other.Center &&
Radius == other.Radius );
}
#endregion
@ -474,8 +474,7 @@ namespace MoonWorks.Math.Float
ref BoundingSphere original,
ref BoundingSphere additional,
out BoundingSphere result
)
{
) {
Vector3 ocenterToaCenter = Vector3.Subtract(additional.Center, original.Center);
float distance = ocenterToaCenter.Length();

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -14,7 +14,7 @@
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Defines how the bounding volumes intersects or contain one another.

View File

@ -1,886 +0,0 @@
using MoonWorks.Math.Fixed;
using System.Collections.Generic;
namespace MoonWorks.Math
{
public static class Easing
{
private const float C1 = 1.70158f;
private const float C2 = C1 * 1.525f;
private const float C3 = C1 + 1;
private const float C4 = (2 * System.MathF.PI) / 3;
private const float C5 = (2 * System.MathF.PI) / 4.5f;
private static readonly Fix64 HALF = Fix64.FromFraction(1, 2);
private static readonly Fix64 FIXED_C1 = Fix64.FromFraction(170158, 100000);
private static readonly Fix64 FIXED_C2 = FIXED_C1 * Fix64.FromFraction(61, 40);
private static readonly Fix64 FIXED_C3 = FIXED_C1 + Fix64.One;
private static readonly Fix64 FIXED_C4 = Fix64.PiTimes2 / new Fix64(3);
private static readonly Fix64 FIXED_C5 = Fix64.PiTimes2 / Fix64.FromFraction(9, 2);
private static readonly Fix64 FIXED_N1 = Fix64.FromFraction(121, 16);
private static readonly Fix64 FIXED_D1 = Fix64.FromFraction(11, 4);
private static float OutIn(
System.Func<float, float> outFunc,
System.Func<float, float> inFunc,
float t
) {
if (t < 0.5f)
{
return outFunc(t);
}
else
{
return inFunc(t);
}
}
private static Fix64 OutIn(
System.Func<Fix64, Fix64> outFunc,
System.Func<Fix64, Fix64> inFunc,
Fix64 t
) {
if (t < HALF)
{
return outFunc(t);
}
else
{
return inFunc(t);
}
}
/* GENERAL-USE FUNCTIONS */
public static float AttackHoldRelease(
float start,
float hold,
float end,
float time,
float attackDuration,
Function.Float attackEasingFunction,
float holdDuration,
float releaseDuration,
Function.Float releaseEasingFunction
) {
if (time < attackDuration)
{
return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction));
}
else if (time >= attackDuration && time < attackDuration + holdDuration)
{
return hold;
}
else // time >= attackDuration + holdDuration
{
return Interp(hold, end, time - holdDuration - attackDuration, releaseDuration, Function.Get(releaseEasingFunction));
}
}
public static Fix64 AttackHoldRelease(
Fix64 start,
Fix64 hold,
Fix64 end,
Fix64 time,
Fix64 attackDuration,
Function.Fixed attackEasingFunction,
Fix64 holdDuration,
Fix64 releaseDuration,
Function.Fixed releaseEasingFunction
) {
if (time < attackDuration)
{
return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction));
}
else if (time >= attackDuration && time < attackDuration + holdDuration)
{
return hold;
}
else // time >= attackDuration + holdDuration
{
return Interp(hold, end, time - holdDuration - attackDuration, releaseDuration, Function.Get(releaseEasingFunction));
}
}
public static float Lerp(float start, float end, float time)
{
return (start + (end - start) * time);
}
public static float Interp(float start, float end, float time, float duration, System.Func<float, float> easingFunc)
{
return Lerp(start, end, easingFunc(time / duration));
}
public static float Interp(float start, float end, float time, float duration, MoonWorks.Math.Easing.Function.Float easingFunc)
{
return Interp(start, end, time, duration, Function.Get(easingFunc));
}
public static Fix64 Lerp(Fix64 start, Fix64 end, Fix64 time)
{
return (start + (end - start) * time);
}
public static Fix64 Interp(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, System.Func<Fix64, Fix64> easingFunc)
{
return Lerp(start, end, easingFunc(time / duration));
}
public static Fix64 Interp(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, MoonWorks.Math.Easing.Function.Fixed easingFunc)
{
return Interp(start, end, time, duration, Function.Get(easingFunc));
}
/* FLOAT EASING FUNCTIONS */
// LINEAR
public static float Linear(float t)
{
return t;
}
// QUADRATIC
public static float InQuad(float t)
{
return t * t;
}
public static float OutQuad(float t)
{
return 1 - (1 - t) * (1 - t);
}
public static float InOutQuad(float t)
{
if (t < 0.5f)
{
return 2 * t * t;
}
else
{
var x = (-2 * t + 2);
return 1 - ((x * x) / 2);
}
}
public static float OutInQuad(float t) => OutIn(OutQuad, InQuad, t);
// CUBIC
public static float InCubic(float t)
{
return t * t * t;
}
public static float OutCubic(float t)
{
var x = 1 - t;
return 1 - (x * x * x);
}
public static float InOutCubic(float t)
{
if (t < 0.5f)
{
return 4 * t * t * t;
}
else
{
var x = -2 * t + 2;
return 1 - ((x * x * x) / 2);
}
}
public static float OutInCubic(float t) => OutIn(OutCubic, InCubic, t);
// QUARTIC
public static float InQuart(float t)
{
return t * t * t * t;
}
public static float OutQuart(float t)
{
var x = 1 - t;
return 1 - (x * x * x * x);
}
public static float InOutQuart(float t)
{
if (t < 0.5f)
{
return 8 * t * t * t * t;
}
else
{
var x = -2 * t + 2;
return 1 - ((x * x * x * x) / 2);
}
}
public static float OutInQuart(float t) => OutIn(OutQuart, InQuart, t);
// QUINTIC
public static float InQuint(float t)
{
return t * t * t * t * t;
}
public static float OutQuint(float t)
{
var x = 1 - t;
return 1 - (x * x * x * x * x);
}
public static float InOutQuint(float t)
{
if (t < 0.5f)
{
return 16 * t * t * t * t * t;
}
else
{
var x = -2 * t + 2;
return 1 - ((x * x * x * x * x) / 2);
}
}
public static float OutInQuint(float t) => OutIn(OutQuint, InQuint, t);
// SINE
public static float InSine(float t)
{
return 1 - System.MathF.Cos((t * System.MathF.PI) / 2);
}
public static float OutSine(float t)
{
return System.MathF.Sin((t * System.MathF.PI) / 2);
}
public static float InOutSine(float t)
{
return -(System.MathF.Cos(System.MathF.PI * t) - 1) / 2;
}
public static float OutInSine(float t) => OutIn(OutSine, InSine, t);
// EXPONENTIAL
public static float InExpo(float t)
{
if (t == 0)
{
return 0;
}
else
{
return System.MathF.Pow(2, 10 * t - 10);
}
}
public static float OutExpo(float t)
{
if (t == 1)
{
return 1;
}
else
{
return 1 - System.MathF.Pow(2, -10 * t);
}
}
public static float InOutExpo(float t)
{
if (t == 0)
{
return 0;
}
else if (t == 1)
{
return 1;
}
else if (t < 0.5f)
{
return System.MathF.Pow(2, 20 * t - 10) / 2;
}
else
{
return (2 - System.MathF.Pow(2, -20 * t + 10)) / 2;
}
}
public static float OutInExpo(float t) => OutIn(OutExpo, InExpo, t);
// CIRCULAR
public static float InCirc(float t)
{
return 1 - System.MathF.Sqrt(1 - (t * t));
}
public static float OutCirc(float t)
{
return System.MathF.Sqrt(1 - ((t - 1) * (t - 1)));
}
public static float InOutCirc(float t)
{
if (t < 0.5f)
{
return (1 - System.MathF.Sqrt(1 - ((2 * t) * (2 * t)))) / 2;
}
else
{
var x = -2 * t + 2;
return (System.MathF.Sqrt(1 - (x * x)) + 1) / 2;
}
}
public static float OutInCirc(float t) => OutIn(OutCirc, InCirc, t);
// BACK
public static float InBack(float t)
{
return C3 * t * t * t - C1 * t * t;
}
public static float OutBack(float t)
{
return 1 + C3 * (t - 1) * (t - 1) * (t - 1) + C1 * (t - 1) * (t - 1);
}
public static float InOutBack(float t)
{
if (t < 0.5f)
{
return ((2 * t) * (2 * t) * ((C2 + 1) * 2 * t - C2)) / 2;
}
else
{
var x = 2 * t - 2;
return ((t * t) * ((C2 + 1) * (x) + C2) + 2) / 2;
}
}
public static float OutInBack(float t) => OutIn(OutBack, InBack, t);
// ELASTIC
public static float InElastic(float t)
{
if (t == 0)
{
return 0;
}
else if (t == 1)
{
return 1;
}
else
{
return -System.MathF.Pow(2, 10 * t - 10) * System.MathF.Sin((t * 10 - 10.75f) * C4);
}
}
public static float OutElastic(float t)
{
if (t == 0)
{
return 0;
}
else if (t == 1)
{
return 1;
}
else
{
return System.MathF.Pow(2, -10 * t) * System.MathF.Sin((t * 10 - 0.75f) * C4) + 1;
}
}
public static float InOutElastic(float t)
{
if (t == 0)
{
return 0;
}
else if (t == 1)
{
return 1;
}
else if (t < 0.5f)
{
return -(System.MathF.Pow(2, 20 * t - 10) * System.MathF.Sin((20 * t - 11.125f) * C5)) / 2;
}
else
{
return (System.MathF.Pow(2, -20 * t + 10) * System.MathF.Sin((20 * t - 11.125f) * C5)) / 2 + 1;
}
}
public static float OutInElastic(float t) => OutIn(OutElastic, InElastic, t);
// BOUNCE
public static float InBounce(float t)
{
return 1 - OutBounce(1 - t);
}
public static float OutBounce(float t)
{
const float N1 = 7.5625f;
const float D1 = 2.75f;
if (t < 1 / D1)
{
return N1 * t * t;
}
else if (t < 2 / D1) {
return N1 * (t -= 1.5f / D1) * t + 0.75f;
}
else if (t < 2.5f / D1)
{
return N1 * (t -= 2.25f / D1) * t + 0.9375f;
}
else
{
return N1 * (t -= 2.625f / D1) * t + 0.984375f;
}
}
public static float InOutBounce(float t)
{
if (t < 0.5f)
{
return (1 - OutBounce(1 - 2 * t)) / 2;
}
else
{
return (1 + OutBounce(2 * t - 1)) / 2;
}
}
public static float OutInBounce(float t) => OutIn(OutBounce, InBounce, t);
/* FIXED EASING FUNCTIONS */
// LINEAR
public static Fix64 Linear(Fix64 t)
{
return t;
}
// QUADRATIC
public static Fix64 InQuad(Fix64 t)
{
return t * t;
}
public static Fix64 OutQuad(Fix64 t)
{
return 1 - (1 - t) * (1 - t);
}
public static Fix64 InOutQuad(Fix64 t)
{
if (t < HALF)
{
return 2 * t * t;
}
else
{
var x = (-2 * t + 2);
return 1 - ((x * x) / 2);
}
}
public static Fix64 OutInQuad(Fix64 t) => OutIn(OutQuad, InQuad, t);
// CUBIC
public static Fix64 InCubic(Fix64 t)
{
return t * t * t;
}
public static Fix64 OutCubic(Fix64 t)
{
var x = 1 - t;
return 1 - (x * x * x);
}
public static Fix64 InOutCubic(Fix64 t)
{
if (t < HALF)
{
return 4 * t * t * t;
}
else
{
var x = -2 * t + 2;
return 1 - ((x * x * x) / 2);
}
}
public static Fix64 OutInCubic(Fix64 t) => OutIn(OutCubic, InCubic, t);
// QUARTIC
public static Fix64 InQuart(Fix64 t)
{
return t * t * t * t;
}
public static Fix64 OutQuart(Fix64 t)
{
var x = 1 - t;
return 1 - (x * x * x * x);
}
public static Fix64 InOutQuart(Fix64 t)
{
if (t < HALF)
{
return 8 * t * t * t * t;
}
else
{
var x = -2 * t + 2;
return 1 - ((x * x * x * x) / 2);
}
}
public static Fix64 OutInQuart(Fix64 t) => OutIn(OutQuart, InQuart, t);
// QUINTIC
public static Fix64 InQuint(Fix64 t)
{
return t * t * t * t * t;
}
public static Fix64 OutQuint(Fix64 t)
{
var x = 1 - t;
return 1 - (x * x * x * x * x);
}
public static Fix64 InOutQuint(Fix64 t)
{
if (t < HALF)
{
return 16 * t * t * t * t * t;
}
else
{
var x = -2 * t + 2;
return 1 - ((x * x * x * x * x) / 2);
}
}
public static Fix64 OutInQuint(Fix64 t) => OutIn(OutQuint, InQuint, t);
// SINE
public static Fix64 InSine(Fix64 t)
{
return 1 - Fix64.Cos((t * Fix64.Pi) / 2);
}
public static Fix64 OutSine(Fix64 t)
{
return Fix64.Sin((t * Fix64.Pi) / 2);
}
public static Fix64 InOutSine(Fix64 t)
{
return -(Fix64.Cos(Fix64.Pi * t) - 1) / 2;
}
public static Fix64 OutInSine(Fix64 t) => OutIn(OutSine, InSine, t);
// CIRCULAR
public static Fix64 InCirc(Fix64 t)
{
return 1 - Fix64.Sqrt(1 - (t * t));
}
public static Fix64 OutCirc(Fix64 t)
{
return Fix64.Sqrt(1 - ((t - 1) * (t - 1)));
}
public static Fix64 InOutCirc(Fix64 t)
{
if (t < HALF)
{
return (1 - Fix64.Sqrt(1 - ((2 * t) * (2 * t)))) / 2;
}
else
{
var x = -2 * t + 2;
return (Fix64.Sqrt(1 - (x * x)) + 1) / 2;
}
}
public static Fix64 OutInCirc(Fix64 t) => OutIn(OutCirc, InCirc, t);
// BACK
public static Fix64 InBack(Fix64 t)
{
return FIXED_C3 * t * t * t - FIXED_C1 * t * t;
}
public static Fix64 OutBack(Fix64 t)
{
return 1 + FIXED_C3 * (t - 1) * (t - 1) * (t - 1) + FIXED_C1 * (t - 1) * (t - 1);
}
public static Fix64 InOutBack(Fix64 t)
{
if (t < HALF)
{
return ((2 * t) * (2 * t) * ((FIXED_C2 + 1) * 2 * t - FIXED_C2)) / 2;
}
else
{
var x = 2 * t - 2;
return ((t * t) * ((FIXED_C2 + 1) * (x) + FIXED_C2) + 2) / 2;
}
}
public static Fix64 OutInBack(Fix64 t) => OutIn(OutBack, InBack, t);
// BOUNCE
public static Fix64 InBounce(Fix64 t)
{
return 1 - OutBounce(1 - t);
}
public static Fix64 OutBounce(Fix64 t)
{
if (t < 1 / FIXED_D1)
{
return FIXED_N1 * t * t;
}
else if (t < 2 / FIXED_D1) {
return FIXED_N1 * (t -= Fix64.FromFraction(3, 2) / FIXED_D1) * t + Fix64.FromFraction(3, 4);
}
else if (t < Fix64.FromFraction(5, 2) / FIXED_D1)
{
return FIXED_N1 * (t -= Fix64.FromFraction(9, 4) / FIXED_D1) * t + Fix64.FromFraction(15, 16);
}
else
{
return FIXED_N1 * (t -= Fix64.FromFraction(181, 80) / FIXED_D1) * t + Fix64.FromFraction(63, 64);
}
}
public static Fix64 InOutBounce(Fix64 t)
{
if (t < HALF)
{
return (1 - OutBounce(1 - 2 * t)) / 2;
}
else
{
return (1 + OutBounce(2 * t - 1)) / 2;
}
}
public static Fix64 OutInBounce(Fix64 t) => OutIn(OutBounce, InBounce, t);
public static class Function
{
public enum Float
{
Linear,
InQuad,
OutQuad,
InOutQuad,
OutInQuad,
InCubic,
OutCubic,
InOutCubic,
OutInCubic,
InQuart,
OutQuart,
InOutQuart,
OutInQuart,
InQuint,
OutQuint,
InOutQuint,
OutInQuint,
InSine,
OutSine,
InOutSine,
OutInSine,
InExpo,
OutExpo,
InOutExpo,
OutInExpo,
InCirc,
OutCirc,
InOutCirc,
OutInCirc,
InElastic,
OutElastic,
InOutElastic,
OutInElastic,
InBack,
OutBack,
InOutBack,
OutInBack,
InBounce,
OutBounce,
InOutBounce,
OutInBounce
}
public enum Fixed
{
Linear,
InQuad,
OutQuad,
InOutQuad,
OutInQuad,
InCubic,
OutCubic,
InOutCubic,
OutInCubic,
InQuart,
OutQuart,
InOutQuart,
OutInQuart,
InQuint,
OutQuint,
InOutQuint,
OutInQuint,
InSine,
OutSine,
InOutSine,
OutInSine,
InCirc,
OutCirc,
InOutCirc,
OutInCirc,
InBack,
OutBack,
InOutBack,
OutInBack,
InBounce,
OutBounce,
InOutBounce,
OutInBounce
}
private static Dictionary<Float, System.Func<float, float>> FloatLookup = new Dictionary<Float, System.Func<float, float>>
{
{ Float.Linear, Linear },
{ Float.InQuad, InQuad },
{ Float.OutQuad, OutQuad },
{ Float.InOutQuad, InOutQuad },
{ Float.OutInQuad, OutInQuad },
{ Float.InCubic, InCubic },
{ Float.OutCubic, OutCubic },
{ Float.InOutCubic, InOutCubic },
{ Float.OutInCubic, OutInCubic },
{ Float.InQuart, InQuart },
{ Float.OutQuart, OutQuart },
{ Float.InOutQuart, InOutQuart },
{ Float.OutInQuart, OutInQuart },
{ Float.InQuint, InQuint },
{ Float.OutQuint, OutQuint },
{ Float.InOutQuint, InOutQuint },
{ Float.OutInQuint, OutInQuint },
{ Float.InSine, InSine },
{ Float.OutSine, OutSine },
{ Float.InOutSine, InOutSine },
{ Float.OutInSine, OutInSine },
{ Float.InExpo, InExpo },
{ Float.OutExpo, OutExpo },
{ Float.InOutExpo, InOutExpo },
{ Float.OutInExpo, OutInExpo },
{ Float.InCirc, InCirc },
{ Float.OutCirc, OutCirc },
{ Float.InOutCirc, InOutCirc },
{ Float.OutInCirc, OutInCirc },
{ Float.InElastic, InElastic },
{ Float.OutElastic, OutElastic },
{ Float.InOutElastic, InOutElastic },
{ Float.OutInElastic, OutInElastic },
{ Float.InBack, InBack },
{ Float.OutBack, OutBack },
{ Float.InOutBack, InOutBack },
{ Float.OutInBack, OutInBack },
{ Float.InBounce, InBounce },
{ Float.OutBounce, OutBounce },
{ Float.InOutBounce, InOutBounce },
{ Float.OutInBounce, OutInBounce }
};
private static Dictionary<Fixed, System.Func<Fix64, Fix64>> FixedLookup = new Dictionary<Fixed, System.Func<Fix64, Fix64>>
{
{ Fixed.Linear, Linear },
{ Fixed.InQuad, InQuad },
{ Fixed.OutQuad, OutQuad },
{ Fixed.InOutQuad, InOutQuad },
{ Fixed.OutInQuad, OutInQuad },
{ Fixed.InCubic, InCubic },
{ Fixed.OutCubic, OutCubic },
{ Fixed.InOutCubic, InOutCubic },
{ Fixed.OutInCubic, OutInCubic },
{ Fixed.InQuart, InQuart },
{ Fixed.OutQuart, OutQuart },
{ Fixed.InOutQuart, InOutQuart },
{ Fixed.OutInQuart, OutInQuart },
{ Fixed.InQuint, InQuint },
{ Fixed.OutQuint, OutQuint },
{ Fixed.InOutQuint, InOutQuint },
{ Fixed.OutInQuint, OutInQuint },
{ Fixed.InSine, InSine },
{ Fixed.OutSine, OutSine },
{ Fixed.InOutSine, InOutSine },
{ Fixed.OutInSine, OutInSine },
{ Fixed.InCirc, InCirc },
{ Fixed.OutCirc, OutCirc },
{ Fixed.InOutCirc, InOutCirc },
{ Fixed.OutInCirc, OutInCirc },
{ Fixed.InBack, InBack },
{ Fixed.OutBack, OutBack },
{ Fixed.InOutBack, InOutBack },
{ Fixed.OutInBack, OutInBack },
{ Fixed.InBounce, InBounce },
{ Fixed.OutBounce, OutBounce },
{ Fixed.InOutBounce, InOutBounce },
{ Fixed.OutInBounce, OutInBounce }
};
public static System.Func<float, float> Get(Float functionEnum)
{
return FloatLookup[functionEnum];
}
public static System.Func<Fix64, Fix64> Get(Fixed functionEnum)
{
return FixedLookup[functionEnum];
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,846 +0,0 @@
/* MoonWorks - Game Development Framework
* Copyright 2022 Evan Hemsley
*/
/* Derived from code by Microsoft.
* Released under the MIT license.
* See microsoft.LICENSE for details.
*/
using System;
using System.Globalization;
namespace MoonWorks.Math.Fixed
{
/// <summary>
/// A structure encapsulating a 3x2 fixed point matrix.
/// </summary>
public struct Matrix3x2 : IEquatable<Matrix3x2>
{
#region Public Fields
/// <summary>
/// The first element of the first row
/// </summary>
public Fix64 M11;
/// <summary>
/// The second element of the first row
/// </summary>
public Fix64 M12;
/// <summary>
/// The first element of the second row
/// </summary>
public Fix64 M21;
/// <summary>
/// The second element of the second row
/// </summary>
public Fix64 M22;
/// <summary>
/// The first element of the third row
/// </summary>
public Fix64 M31;
/// <summary>
/// The second element of the third row
/// </summary>
public Fix64 M32;
#endregion Public Fields
private static readonly Matrix3x2 _identity = new Matrix3x2
(
1, 0,
0, 1,
0, 0
);
private static readonly Fix64 RotationEpsilon = Fix64.FromFraction(1, 1000) * (Fix64.Pi / new Fix64(180));
/// <summary>
/// Returns the multiplicative identity matrix.
/// </summary>
public static Matrix3x2 Identity
{
get { return _identity; }
}
/// <summary>
/// Returns whether the matrix is the identity matrix.
/// </summary>
public bool IsIdentity
{
get
{
return M11 == Fix64.One && M22 == Fix64.One && // Check diagonal element first for early out.
M12 == Fix64.Zero &&
M21 == Fix64.Zero &&
M31 == Fix64.Zero && M32 == Fix64.Zero;
}
}
/// <summary>
/// Gets or sets the translation component of this matrix.
/// </summary>
public Vector2 Translation
{
get
{
return new Vector2(M31, M32);
}
set
{
M31 = value.X;
M32 = value.Y;
}
}
/// <summary>
/// Constructs a FixMatrix3x2 from the given components.
/// </summary>
public Matrix3x2(Fix64 m11, Fix64 m12,
Fix64 m21, Fix64 m22,
Fix64 m31, Fix64 m32)
{
M11 = m11;
M12 = m12;
M21 = m21;
M22 = m22;
M31 = m31;
M32 = m32;
}
public Matrix3x2(int m11, int m12, int m21, int m22, int m31, int m32)
{
M11 = new Fix64(m11);
M12 = new Fix64(m12);
M21 = new Fix64(m21);
M22 = new Fix64(m22);
M31 = new Fix64(m31);
M32 = new Fix64(m32);
}
/// <summary>
/// Creates a translation matrix from the given vector.
/// </summary>
/// <param name="position">The translation position.</param>
/// <returns>A translation matrix.</returns>
public static Matrix3x2 CreateTranslation(Vector2 position)
{
Matrix3x2 result;
result.M11 = Fix64.One;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = Fix64.One;
result.M31 = position.X;
result.M32 = position.Y;
return result;
}
/// <summary>
/// Creates a translation matrix from the given X and Y components.
/// </summary>
/// <param name="xPosition">The X position.</param>
/// <param name="yPosition">The Y position.</param>
/// <returns>A translation matrix.</returns>
public static Matrix3x2 CreateTranslation(Fix64 xPosition, Fix64 yPosition)
{
Matrix3x2 result;
result.M11 = Fix64.One;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = Fix64.One;
result.M31 = xPosition;
result.M32 = yPosition;
return result;
}
/// <summary>
/// Creates a scale matrix from the given X and Y components.
/// </summary>
/// <param name="xScale">Value to scale by on the X-axis.</param>
/// <param name="yScale">Value to scale by on the Y-axis.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Fix64 xScale, Fix64 yScale)
{
Matrix3x2 result;
result.M11 = xScale;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = yScale;
result.M31 = Fix64.Zero;
result.M32 = Fix64.Zero;
return result;
}
/// <summary>
/// Creates a scale matrix that is offset by a given center point.
/// </summary>
/// <param name="xScale">Value to scale by on the X-axis.</param>
/// <param name="yScale">Value to scale by on the Y-axis.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Fix64 xScale, Fix64 yScale, Vector2 centerPoint)
{
Matrix3x2 result;
Fix64 tx = centerPoint.X * (Fix64.One - xScale);
Fix64 ty = centerPoint.Y * (Fix64.One - yScale);
result.M11 = xScale;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = yScale;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a scale matrix from the given vector scale.
/// </summary>
/// <param name="scales">The scale to use.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Vector2 scales)
{
Matrix3x2 result;
result.M11 = scales.X;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = scales.Y;
result.M31 = Fix64.Zero;
result.M32 = Fix64.Zero;
return result;
}
/// <summary>
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
/// </summary>
/// <param name="scales">The scale to use.</param>
/// <param name="centerPoint">The center offset.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Vector2 scales, Vector2 centerPoint)
{
Matrix3x2 result;
Fix64 tx = centerPoint.X * (Fix64.One - scales.X);
Fix64 ty = centerPoint.Y * (Fix64.One - scales.Y);
result.M11 = scales.X;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = scales.Y;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a scale matrix that scales uniformly with the given scale.
/// </summary>
/// <param name="scale">The uniform scale to use.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Fix64 scale)
{
Matrix3x2 result;
result.M11 = scale;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = scale;
result.M31 = Fix64.Zero;
result.M32 = Fix64.Zero;
return result;
}
/// <summary>
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
/// </summary>
/// <param name="scale">The uniform scale to use.</param>
/// <param name="centerPoint">The center offset.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Fix64 scale, Vector2 centerPoint)
{
Matrix3x2 result;
Fix64 tx = centerPoint.X * (Fix64.One - scale);
Fix64 ty = centerPoint.Y * (Fix64.One - scale);
result.M11 = scale;
result.M12 = Fix64.Zero;
result.M21 = Fix64.Zero;
result.M22 = scale;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a skew matrix from the given angles in radians.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>A skew matrix.</returns>
public static Matrix3x2 CreateSkew(Fix64 radiansX, Fix64 radiansY)
{
Matrix3x2 result;
Fix64 xTan = (Fix64) Fix64.Tan(radiansX);
Fix64 yTan = (Fix64) Fix64.Tan(radiansY);
result.M11 = Fix64.One;
result.M12 = yTan;
result.M21 = xTan;
result.M22 = Fix64.One;
result.M31 = Fix64.Zero;
result.M32 = Fix64.Zero;
return result;
}
/// <summary>
/// Creates a skew matrix from the given angles in radians and a center point.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A skew matrix.</returns>
public static Matrix3x2 CreateSkew(Fix64 radiansX, Fix64 radiansY, Vector2 centerPoint)
{
Matrix3x2 result;
Fix64 xTan = (Fix64) Fix64.Tan(radiansX);
Fix64 yTan = (Fix64) Fix64.Tan(radiansY);
Fix64 tx = -centerPoint.Y * xTan;
Fix64 ty = -centerPoint.X * yTan;
result.M11 = Fix64.One;
result.M12 = yTan;
result.M21 = xTan;
result.M22 = Fix64.One;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a rotation matrix using the given rotation in radians.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>A rotation matrix.</returns>
public static Matrix3x2 CreateRotation(Fix64 radians)
{
Matrix3x2 result;
radians = Fix64.IEEERemainder(radians, Fix64.PiTimes2);
Fix64 c, s;
if (radians > -RotationEpsilon && radians < RotationEpsilon)
{
// Exact case for zero rotation.
c = Fix64.One;
s = Fix64.Zero;
}
else if (radians > Fix64.PiOver2 - RotationEpsilon && radians < Fix64.PiOver2 + RotationEpsilon)
{
// Exact case for 90 degree rotation.
c = Fix64.Zero;
s = Fix64.One;
}
else if (radians < -Fix64.Pi + RotationEpsilon || radians > Fix64.Pi - RotationEpsilon)
{
// Exact case for 180 degree rotation.
c = -Fix64.One;
s = Fix64.Zero;
}
else if (radians > -Fix64.PiOver2 - RotationEpsilon && radians < -Fix64.PiOver2 + RotationEpsilon)
{
// Exact case for 270 degree rotation.
c = Fix64.Zero;
s = -Fix64.One;
}
else
{
// Arbitrary rotation.
c = Fix64.Cos(radians);
s = Fix64.Sin(radians);
}
// [ c s ]
// [ -s c ]
// [ 0 0 ]
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = Fix64.Zero;
result.M32 = Fix64.Zero;
return result;
}
/// <summary>
/// Creates a rotation matrix using the given rotation in radians and a center point.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A rotation matrix.</returns>
public static Matrix3x2 CreateRotation(Fix64 radians, Vector2 centerPoint)
{
Matrix3x2 result;
radians = Fix64.IEEERemainder(radians, Fix64.PiTimes2);
Fix64 c, s;
if (radians > -RotationEpsilon && radians < RotationEpsilon)
{
// Exact case for zero rotation.
c = Fix64.One;
s = Fix64.Zero;
}
else if (radians > Fix64.PiOver2 - RotationEpsilon && radians < Fix64.PiOver2 + RotationEpsilon)
{
// Exact case for 90 degree rotation.
c = Fix64.Zero;
s = Fix64.One;
}
else if (radians < -Fix64.Pi + RotationEpsilon || radians > Fix64.Pi - RotationEpsilon)
{
// Exact case for 180 degree rotation.
c = -Fix64.One;
s = Fix64.Zero;
}
else if (radians > -Fix64.PiOver2 - RotationEpsilon && radians < -Fix64.PiOver2 + RotationEpsilon)
{
// Exact case for 270 degree rotation.
c = Fix64.Zero;
s = -Fix64.One;
}
else
{
// Arbitrary rotation.
c = (Fix64) Fix64.Cos(radians);
s = (Fix64) Fix64.Sin(radians);
}
Fix64 x = centerPoint.X * (Fix64.One - c) + centerPoint.Y * s;
Fix64 y = centerPoint.Y * (Fix64.One - c) - centerPoint.X * s;
// [ c s ]
// [ -s c ]
// [ x y ]
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = x;
result.M32 = y;
return result;
}
/// <summary>
/// Calculates the determinant for this matrix.
/// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1).
/// </summary>
/// <returns>The determinant.</returns>
public Fix64 GetDeterminant()
{
// There isn't actually any such thing as a determinant for a non-square matrix,
// but this 3x2 type is really just an optimization of a 3x3 where we happen to
// know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
//
// [ M11, M12, 0 ]
// [ M21, M22, 0 ]
// [ M31, M32, 1 ]
//
// Sum the diagonal products:
// (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
//
// Subtract the opposite diagonal products:
// (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
//
// Collapse out the constants and oh look, this is just a 2x2 determinant!
return (M11 * M22) - (M21 * M12);
}
/// <summary>
/// Attempts to invert the given matrix. If the operation succeeds, the inverted matrix is stored in the result parameter.
/// </summary>
/// <param name="matrix">The source matrix.</param>
/// <param name="result">The output matrix.</param>
/// <returns>True if the operation succeeded, False otherwise.</returns>
public static bool Invert(Matrix3x2 matrix, out Matrix3x2 result)
{
Fix64 det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12);
if (Fix64.Abs(det) == Fix64.Zero)
{
result = new Matrix3x2(Fix64.Zero, Fix64.Zero, Fix64.Zero, Fix64.Zero, Fix64.Zero, Fix64.Zero);
return false;
}
Fix64 invDet = Fix64.One / det;
result.M11 = matrix.M22 * invDet;
result.M12 = -matrix.M12 * invDet;
result.M21 = -matrix.M21 * invDet;
result.M22 = matrix.M11 * invDet;
result.M31 = (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet;
result.M32 = (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet;
return true;
}
/// <summary>
/// Linearly interpolates from matrix1 to matrix2, based on the third parameter.
/// </summary>
/// <param name="matrix1">The first source matrix.</param>
/// <param name="matrix2">The second source matrix.</param>
/// <param name="amount">The relative weighting of matrix2.</param>
/// <returns>The interpolated matrix.</returns>
public static Matrix3x2 Lerp(Matrix3x2 matrix1, Matrix3x2 matrix2, Fix64 amount)
{
Matrix3x2 result;
// First row
result.M11 = matrix1.M11 + (matrix2.M11 - matrix1.M11) * amount;
result.M12 = matrix1.M12 + (matrix2.M12 - matrix1.M12) * amount;
// Second row
result.M21 = matrix1.M21 + (matrix2.M21 - matrix1.M21) * amount;
result.M22 = matrix1.M22 + (matrix2.M22 - matrix1.M22) * amount;
// Third row
result.M31 = matrix1.M31 + (matrix2.M31 - matrix1.M31) * amount;
result.M32 = matrix1.M32 + (matrix2.M32 - matrix1.M32) * amount;
return result;
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static Matrix3x2 Negate(Matrix3x2 value)
{
Matrix3x2 result;
result.M11 = -value.M11;
result.M12 = -value.M12;
result.M21 = -value.M21;
result.M22 = -value.M22;
result.M31 = -value.M31;
result.M32 = -value.M32;
return result;
}
/// <summary>
/// Adds each matrix element in value1 with its corresponding element in value2.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the summed values.</returns>
public static Matrix3x2 Add(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 + value2.M11;
result.M12 = value1.M12 + value2.M12;
result.M21 = value1.M21 + value2.M21;
result.M22 = value1.M22 + value2.M22;
result.M31 = value1.M31 + value2.M31;
result.M32 = value1.M32 + value2.M32;
return result;
}
/// <summary>
/// Subtracts each matrix element in value2 from its corresponding element in value1.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the resulting values.</returns>
public static Matrix3x2 Subtract(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 - value2.M11;
result.M12 = value1.M12 - value2.M12;
result.M21 = value1.M21 - value2.M21;
result.M22 = value1.M22 - value2.M22;
result.M31 = value1.M31 - value2.M31;
result.M32 = value1.M32 - value2.M32;
return result;
}
/// <summary>
/// Multiplies two matrices together and returns the resulting matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The product matrix.</returns>
public static Matrix3x2 Multiply(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
// First row
result.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
result.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
result.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
result.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
// Third row
result.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
result.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
return result;
}
public Matrix4x4 ToMatrix4x4()
{
return new Matrix4x4(
M11, M12, Fix64.Zero, Fix64.Zero,
M21, M22, Fix64.Zero, Fix64.Zero,
Fix64.Zero, Fix64.Zero, Fix64.One, Fix64.Zero,
M31, M32, Fix64.Zero, Fix64.One
);
}
/// <summary>
/// Scales all elements in a matrix by the given scalar factor.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling value to use.</param>
/// <returns>The resulting matrix.</returns>
public static Matrix3x2 Multiply(Matrix3x2 value1, Fix64 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 * value2;
result.M12 = value1.M12 * value2;
result.M21 = value1.M21 * value2;
result.M22 = value1.M22 * value2;
result.M31 = value1.M31 * value2;
result.M32 = value1.M32 * value2;
return result;
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static Matrix3x2 operator -(Matrix3x2 value)
{
Matrix3x2 m;
m.M11 = -value.M11;
m.M12 = -value.M12;
m.M21 = -value.M21;
m.M22 = -value.M22;
m.M31 = -value.M31;
m.M32 = -value.M32;
return m;
}
/// <summary>
/// Adds each matrix element in value1 with its corresponding element in value2.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the summed values.</returns>
public static Matrix3x2 operator +(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 + value2.M11;
m.M12 = value1.M12 + value2.M12;
m.M21 = value1.M21 + value2.M21;
m.M22 = value1.M22 + value2.M22;
m.M31 = value1.M31 + value2.M31;
m.M32 = value1.M32 + value2.M32;
return m;
}
/// <summary>
/// Subtracts each matrix element in value2 from its corresponding element in value1.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the resulting values.</returns>
public static Matrix3x2 operator -(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 - value2.M11;
m.M12 = value1.M12 - value2.M12;
m.M21 = value1.M21 - value2.M21;
m.M22 = value1.M22 - value2.M22;
m.M31 = value1.M31 - value2.M31;
m.M32 = value1.M32 - value2.M32;
return m;
}
/// <summary>
/// Multiplies two matrices together and returns the resulting matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The product matrix.</returns>
public static Matrix3x2 operator *(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
// First row
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
// Third row
m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
return m;
}
/// <summary>
/// Scales all elements in a matrix by the given scalar factor.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling value to use.</param>
/// <returns>The resulting matrix.</returns>
public static Matrix3x2 operator *(Matrix3x2 value1, Fix64 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 * value2;
m.M12 = value1.M12 * value2;
m.M21 = value1.M21 * value2;
m.M22 = value1.M22 * value2;
m.M31 = value1.M31 * value2;
m.M32 = value1.M32 * value2;
return m;
}
/// <summary>
/// Returns a boolean indicating whether the given matrices are equal.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>True if the matrices are equal; False otherwise.</returns>
public static bool operator ==(Matrix3x2 value1, Matrix3x2 value2)
{
return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && // Check diagonal element first for early out.
value1.M12 == value2.M12 &&
value1.M21 == value2.M21 &&
value1.M31 == value2.M31 && value1.M32 == value2.M32);
}
/// <summary>
/// Returns a boolean indicating whether the given matrices are not equal.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>True if the matrices are not equal; False if they are equal.</returns>
public static bool operator !=(Matrix3x2 value1, Matrix3x2 value2)
{
return (value1.M11 != value2.M11 || value1.M12 != value2.M12 ||
value1.M21 != value2.M21 || value1.M22 != value2.M22 ||
value1.M31 != value2.M31 || value1.M32 != value2.M32);
}
/// <summary>
/// Casts to floating point Matrix3x2.
/// </summary>
public static explicit operator Math.Float.Matrix3x2(Matrix3x2 matrix)
{
return new Math.Float.Matrix3x2(
(float) matrix.M11, (float) matrix.M12,
(float) matrix.M21, (float) matrix.M22,
(float) matrix.M31, (float) matrix.M32
);
}
/// <summary>
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
/// </summary>
/// <param name="other">The other matrix to test equality against.</param>
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
public bool Equals(Matrix3x2 other)
{
return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out.
M12 == other.M12 &&
M21 == other.M21 &&
M31 == other.M31 && M32 == other.M32);
}
/// <summary>
/// Returns a boolean indicating whether the given Object is equal to this matrix instance.
/// </summary>
/// <param name="obj">The Object to compare against.</param>
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is Matrix3x2)
{
return Equals((Matrix3x2) obj);
}
return false;
}
/// <summary>
/// Returns a String representing this matrix instance.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
CultureInfo ci = CultureInfo.CurrentCulture;
return String.Format(ci, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}",
M11.ToString(ci), M12.ToString(ci),
M21.ToString(ci), M22.ToString(ci),
M31.ToString(ci), M32.ToString(ci));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return M11.GetHashCode() + M12.GetHashCode() +
M21.GetHashCode() + M22.GetHashCode() +
M31.GetHashCode() + M32.GetHashCode();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,876 +0,0 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2022 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Fixed
{
/// <summary>
/// An efficient mathematical representation for three dimensional fixed point rotations.
/// </summary>
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]
public struct Quaternion : IEquatable<Quaternion>
{
#region Public Static Properties
/// <summary>
/// Returns a quaternion representing no rotation.
/// </summary>
public static Quaternion Identity
{
get
{
return identity;
}
}
#endregion
#region Internal Properties
internal string DebugDisplayString
{
get
{
if (this == Quaternion.Identity)
{
return "Identity";
}
return string.Concat(
X.ToString(), " ",
Y.ToString(), " ",
Z.ToString(), " ",
W.ToString()
);
}
}
#endregion
#region Public Fields
/// <summary>
/// The x coordinate of this <see cref="Quaternion"/>.
/// </summary>
public Fix64 X;
/// <summary>
/// The y coordinate of this <see cref="Quaternion"/>.
/// </summary>
public Fix64 Y;
/// <summary>
/// The z coordinate of this <see cref="Quaternion"/>.
/// </summary>
public Fix64 Z;
/// <summary>
/// The rotation component of this <see cref="Quaternion"/>.
/// </summary>
public Fix64 W;
#endregion
#region Private Static Variables
private static readonly Quaternion identity = new Quaternion(0, 0, 0, 1);
#endregion
#region Public Constructors
/// <summary>
/// Constructs a quaternion with X, Y, Z and W from four values.
/// </summary>
/// <param name="x">The x coordinate in 3d-space.</param>
/// <param name="y">The y coordinate in 3d-space.</param>
/// <param name="z">The z coordinate in 3d-space.</param>
/// <param name="w">The rotation component.</param>
public Quaternion(int x, int y, int z, int w)
{
X = new Fix64(x);
Y = new Fix64(y);
Z = new Fix64(z);
W = new Fix64(w);
}
public Quaternion(Fix64 x, Fix64 y, Fix64 z, Fix64 w)
{
X = x;
Y = y;
Z = z;
W = w;
}
/// <summary>
/// Constructs a quaternion with X, Y, Z from <see cref="Vector3"/> and rotation component from a scalar.
/// </summary>
/// <param name="value">The x, y, z coordinates in 3d-space.</param>
/// <param name="w">The rotation component.</param>
public Quaternion(Vector3 vectorPart, Fix64 scalarPart)
{
X = vectorPart.X;
Y = vectorPart.Y;
Z = vectorPart.Z;
W = scalarPart;
}
#endregion
#region Public Methods
/// <summary>
/// Transforms this quaternion into its conjugated version.
/// </summary>
public void Conjugate()
{
X = -X;
Y = -Y;
Z = -Z;
}
/// <summary>
/// Compares whether current instance is equal to specified <see cref="Object"/>.
/// </summary>
/// <param name="obj">The <see cref="Object"/> to compare.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public override bool Equals(object obj)
{
return (obj is Quaternion) && Equals((Quaternion) obj);
}
/// <summary>
/// Compares whether current instance is equal to specified <see cref="Quaternion"/>.
/// </summary>
/// <param name="other">The <see cref="Quaternion"/> to compare.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Quaternion other)
{
return (X == other.X &&
Y == other.Y &&
Z == other.Z &&
W == other.W);
}
/// <summary>
/// Gets the hash code of this <see cref="Quaternion"/>.
/// </summary>
/// <returns>Hash code of this <see cref="Quaternion"/>.</returns>
public override int GetHashCode()
{
return (
this.X.GetHashCode() +
this.Y.GetHashCode() +
this.Z.GetHashCode() +
this.W.GetHashCode()
);
}
/// <summary>
/// Returns the magnitude of the quaternion components.
/// </summary>
/// <returns>The magnitude of the quaternion components.</returns>
public Fix64 Length()
{
Fix64 num = (
(this.X * this.X) +
(this.Y * this.Y) +
(this.Z * this.Z) +
(this.W * this.W)
);
return (Fix64) Fix64.Sqrt(num);
}
/// <summary>
/// Returns the squared magnitude of the quaternion components.
/// </summary>
/// <returns>The squared magnitude of the quaternion components.</returns>
public Fix64 LengthSquared()
{
return (
(this.X * this.X) +
(this.Y * this.Y) +
(this.Z * this.Z) +
(this.W * this.W)
);
}
/// <summary>
/// Returns a <see cref="String"/> representation of this <see cref="Quaternion"/> in the format:
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>] W:[<see cref="W"/>]}
/// </summary>
/// <returns>A <see cref="String"/> representation of this <see cref="Quaternion"/>.</returns>
public override string ToString()
{
return (
"{X:" + X.ToString() +
" Y:" + Y.ToString() +
" Z:" + Z.ToString() +
" W:" + W.ToString() +
"}"
);
}
#endregion
#region Public Static Methods
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains the sum of two quaternions.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
/// <returns>The result of the quaternion addition.</returns>
public static Quaternion Add(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Add(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains the sum of two quaternions.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
/// <param name="result">The result of the quaternion addition as an output parameter.</param>
public static void Add(
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
result.X = quaternion1.X + quaternion2.X;
result.Y = quaternion1.Y + quaternion2.Y;
result.Z = quaternion1.Z + quaternion2.Z;
result.W = quaternion1.W + quaternion2.W;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains concatenation between two quaternion.
/// </summary>
/// <param name="value1">The first <see cref="Quaternion"/> to concatenate.</param>
/// <param name="value2">The second <see cref="Quaternion"/> to concatenate.</param>
/// <returns>The result of rotation of <paramref name="value1"/> followed by <paramref name="value2"/> rotation.</returns>
public static Quaternion Concatenate(Quaternion value1, Quaternion value2)
{
Quaternion quaternion;
Concatenate(ref value1, ref value2, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains concatenation between two quaternion.
/// </summary>
/// <param name="value1">The first <see cref="Quaternion"/> to concatenate.</param>
/// <param name="value2">The second <see cref="Quaternion"/> to concatenate.</param>
/// <param name="result">The result of rotation of <paramref name="value1"/> followed by <paramref name="value2"/> rotation as an output parameter.</param>
public static void Concatenate(
ref Quaternion value1,
ref Quaternion value2,
out Quaternion result
)
{
Fix64 x1 = value1.X;
Fix64 y1 = value1.Y;
Fix64 z1 = value1.Z;
Fix64 w1 = value1.W;
Fix64 x2 = value2.X;
Fix64 y2 = value2.Y;
Fix64 z2 = value2.Z;
Fix64 w2 = value2.W;
result.X = ((x2 * w1) + (x1 * w2)) + ((y2 * z1) - (z2 * y1));
result.Y = ((y2 * w1) + (y1 * w2)) + ((z2 * x1) - (x2 * z1));
result.Z = ((z2 * w1) + (z1 * w2)) + ((x2 * y1) - (y2 * x1));
result.W = (w2 * w1) - (((x2 * x1) + (y2 * y1)) + (z2 * z1));
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains conjugated version of the specified quaternion.
/// </summary>
/// <param name="value">The quaternion which values will be used to create the conjugated version.</param>
/// <returns>The conjugate version of the specified quaternion.</returns>
public static Quaternion Conjugate(Quaternion value)
{
return new Quaternion(-value.X, -value.Y, -value.Z, value.W);
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains conjugated version of the specified quaternion.
/// </summary>
/// <param name="value">The quaternion which values will be used to create the conjugated version.</param>
/// <param name="result">The conjugated version of the specified quaternion as an output parameter.</param>
public static void Conjugate(ref Quaternion value, out Quaternion result)
{
result.X = -value.X;
result.Y = -value.Y;
result.Z = -value.Z;
result.W = value.W;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> from the specified axis and angle.
/// </summary>
/// <param name="axis">The axis of rotation.</param>
/// <param name="angle">The angle in radians.</param>
/// <returns>The new quaternion builded from axis and angle.</returns>
public static Quaternion CreateFromAxisAngle(Vector3 axis, Fix64 angle)
{
Quaternion quaternion;
CreateFromAxisAngle(ref axis, angle, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> from the specified axis and angle.
/// </summary>
/// <param name="axis">The axis of rotation.</param>
/// <param name="angle">The angle in radians.</param>
/// <param name="result">The new quaternion builded from axis and angle as an output parameter.</param>
public static void CreateFromAxisAngle(
ref Vector3 axis,
Fix64 angle,
out Quaternion result
)
{
Fix64 half = angle / new Fix64(2);
Fix64 sin = Fix64.Sin(half);
Fix64 cos = Fix64.Cos(half);
result.X = axis.X * sin;
result.Y = axis.Y * sin;
result.Z = axis.Z * sin;
result.W = cos;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> from the specified <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="matrix">The rotation matrix.</param>
/// <returns>A quaternion composed from the rotation part of the matrix.</returns>
public static Quaternion CreateFromRotationMatrix(Matrix4x4 matrix)
{
Quaternion quaternion;
CreateFromRotationMatrix(ref matrix, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> from the specified <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="matrix">The rotation matrix.</param>
/// <param name="result">A quaternion composed from the rotation part of the matrix as an output parameter.</param>
public static void CreateFromRotationMatrix(ref Matrix4x4 matrix, out Quaternion result)
{
Fix64 sqrt;
Fix64 half;
Fix64 scale = matrix.M11 + matrix.M22 + matrix.M33;
Fix64 two = new Fix64(2);
if (scale > Fix64.Zero)
{
sqrt = Fix64.Sqrt(scale + Fix64.One);
result.W = sqrt / two;
sqrt = Fix64.One / (sqrt * two);
result.X = (matrix.M23 - matrix.M32) * sqrt;
result.Y = (matrix.M31 - matrix.M13) * sqrt;
result.Z = (matrix.M12 - matrix.M21) * sqrt;
}
else if ((matrix.M11 >= matrix.M22) && (matrix.M11 >= matrix.M33))
{
sqrt = Fix64.Sqrt(Fix64.One + matrix.M11 - matrix.M22 - matrix.M33);
half = Fix64.One / (sqrt * two);
result.X = sqrt / two;
result.Y = (matrix.M12 + matrix.M21) * half;
result.Z = (matrix.M13 + matrix.M31) * half;
result.W = (matrix.M23 - matrix.M32) * half;
}
else if (matrix.M22 > matrix.M33)
{
sqrt = Fix64.Sqrt(Fix64.One + matrix.M22 - matrix.M11 - matrix.M33);
half = Fix64.One / (sqrt * two);
result.X = (matrix.M21 + matrix.M12) * half;
result.Y = sqrt / two;
result.Z = (matrix.M32 + matrix.M23) * half;
result.W = (matrix.M31 - matrix.M13) * half;
}
else
{
sqrt = Fix64.Sqrt(Fix64.One + matrix.M33 - matrix.M11 - matrix.M22);
half = Fix64.One / (sqrt * two);
result.X = (matrix.M31 + matrix.M13) * half;
result.Y = (matrix.M32 + matrix.M23) * half;
result.Z = sqrt / two;
result.W = (matrix.M12 - matrix.M21) * half;
}
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> from the specified yaw, pitch and roll angles.
/// </summary>
/// <param name="yaw">Yaw around the y axis in radians.</param>
/// <param name="pitch">Pitch around the x axis in radians.</param>
/// <param name="roll">Roll around the z axis in radians.</param>
/// <returns>A new quaternion from the concatenated yaw, pitch, and roll angles.</returns>
public static Quaternion CreateFromYawPitchRoll(Fix64 yaw, Fix64 pitch, Fix64 roll)
{
Quaternion quaternion;
CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> from the specified yaw, pitch and roll angles.
/// </summary>
/// <param name="yaw">Yaw around the y axis in radians.</param>
/// <param name="pitch">Pitch around the x axis in radians.</param>
/// <param name="roll">Roll around the z axis in radians.</param>
/// <param name="result">A new quaternion from the concatenated yaw, pitch, and roll angles as an output parameter.</param>
public static void CreateFromYawPitchRoll(
Fix64 yaw,
Fix64 pitch,
Fix64 roll,
out Quaternion result)
{
Fix64 two = new Fix64(2);
Fix64 halfRoll = roll / two;;
Fix64 sinRoll = Fix64.Sin(halfRoll);
Fix64 cosRoll = Fix64.Cos(halfRoll);
Fix64 halfPitch = pitch / two;
Fix64 sinPitch = Fix64.Sin(halfPitch);
Fix64 cosPitch = Fix64.Cos(halfPitch);
Fix64 halfYaw = yaw / two;
Fix64 sinYaw = Fix64.Sin(halfYaw);
Fix64 cosYaw = Fix64.Cos(halfYaw);
result.X = ((cosYaw * sinPitch) * cosRoll) + ((sinYaw * cosPitch) * sinRoll);
result.Y = ((sinYaw * cosPitch) * cosRoll) - ((cosYaw * sinPitch) * sinRoll);
result.Z = ((cosYaw * cosPitch) * sinRoll) - ((sinYaw * sinPitch) * cosRoll);
result.W = ((cosYaw * cosPitch) * cosRoll) + ((sinYaw * sinPitch) * sinRoll);
}
/// <summary>
/// Divides a <see cref="Quaternion"/> by the other <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Divisor <see cref="Quaternion"/>.</param>
/// <returns>The result of dividing the quaternions.</returns>
public static Quaternion Divide(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Divide(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Divides a <see cref="Quaternion"/> by the other <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Divisor <see cref="Quaternion"/>.</param>
/// <param name="result">The result of dividing the quaternions as an output parameter.</param>
public static void Divide(
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
Fix64 x = quaternion1.X;
Fix64 y = quaternion1.Y;
Fix64 z = quaternion1.Z;
Fix64 w = quaternion1.W;
Fix64 num14 = (
(quaternion2.X * quaternion2.X) +
(quaternion2.Y * quaternion2.Y) +
(quaternion2.Z * quaternion2.Z) +
(quaternion2.W * quaternion2.W)
);
Fix64 num5 = Fix64.One / num14;
Fix64 num4 = -quaternion2.X * num5;
Fix64 num3 = -quaternion2.Y * num5;
Fix64 num2 = -quaternion2.Z * num5;
Fix64 num = quaternion2.W * num5;
Fix64 num13 = (y * num2) - (z * num3);
Fix64 num12 = (z * num4) - (x * num2);
Fix64 num11 = (x * num3) - (y * num4);
Fix64 num10 = ((x * num4) + (y * num3)) + (z * num2);
result.X = ((x * num) + (num4 * w)) + num13;
result.Y = ((y * num) + (num3 * w)) + num12;
result.Z = ((z * num) + (num2 * w)) + num11;
result.W = (w * num) - num10;
}
/// <summary>
/// Returns a dot product of two quaternions.
/// </summary>
/// <param name="quaternion1">The first quaternion.</param>
/// <param name="quaternion2">The second quaternion.</param>
/// <returns>The dot product of two quaternions.</returns>
public static Fix64 Dot(Quaternion quaternion1, Quaternion quaternion2)
{
return (
(quaternion1.X * quaternion2.X) +
(quaternion1.Y * quaternion2.Y) +
(quaternion1.Z * quaternion2.Z) +
(quaternion1.W * quaternion2.W)
);
}
/// <summary>
/// Returns a dot product of two quaternions.
/// </summary>
/// <param name="quaternion1">The first quaternion.</param>
/// <param name="quaternion2">The second quaternion.</param>
/// <param name="result">The dot product of two quaternions as an output parameter.</param>
public static void Dot(
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Fix64 result
)
{
result = (
(quaternion1.X * quaternion2.X) +
(quaternion1.Y * quaternion2.Y) +
(quaternion1.Z * quaternion2.Z) +
(quaternion1.W * quaternion2.W)
);
}
/// <summary>
/// Returns the inverse quaternion which represents the opposite rotation.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
/// <returns>The inverse quaternion.</returns>
public static Quaternion Inverse(Quaternion quaternion)
{
Quaternion inverse;
Inverse(ref quaternion, out inverse);
return inverse;
}
/// <summary>
/// Returns the inverse quaternion which represents the opposite rotation.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
/// <param name="result">The inverse quaternion as an output parameter.</param>
public static void Inverse(ref Quaternion quaternion, out Quaternion result)
{
Fix64 num2 = (
(quaternion.X * quaternion.X) +
(quaternion.Y * quaternion.Y) +
(quaternion.Z * quaternion.Z) +
(quaternion.W * quaternion.W)
);
Fix64 num = Fix64.One / num2;
result.X = -quaternion.X * num;
result.Y = -quaternion.Y * num;
result.Z = -quaternion.Z * num;
result.W = quaternion.W * num;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains subtraction of one <see cref="Quaternion"/> from another.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
/// <returns>The result of the quaternion subtraction.</returns>
public static Quaternion Subtract(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Subtract(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains subtraction of one <see cref="Quaternion"/> from another.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
/// <param name="result">The result of the quaternion subtraction as an output parameter.</param>
public static void Subtract(
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
result.X = quaternion1.X - quaternion2.X;
result.Y = quaternion1.Y - quaternion2.Y;
result.Z = quaternion1.Z - quaternion2.Z;
result.W = quaternion1.W - quaternion2.W;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of two quaternions.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
/// <returns>The result of the quaternion multiplication.</returns>
public static Quaternion Multiply(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Multiply(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of <see cref="Quaternion"/> and a scalar.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="scaleFactor">Scalar value.</param>
/// <returns>The result of the quaternion multiplication with a scalar.</returns>
public static Quaternion Multiply(Quaternion quaternion1, Fix64 scaleFactor)
{
Quaternion quaternion;
Multiply(ref quaternion1, scaleFactor, out quaternion);
return quaternion;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of two quaternions.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
/// <param name="result">The result of the quaternion multiplication as an output parameter.</param>
public static void Multiply(
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
Fix64 x = quaternion1.X;
Fix64 y = quaternion1.Y;
Fix64 z = quaternion1.Z;
Fix64 w = quaternion1.W;
Fix64 num4 = quaternion2.X;
Fix64 num3 = quaternion2.Y;
Fix64 num2 = quaternion2.Z;
Fix64 num = quaternion2.W;
Fix64 num12 = (y * num2) - (z * num3);
Fix64 num11 = (z * num4) - (x * num2);
Fix64 num10 = (x * num3) - (y * num4);
Fix64 num9 = ((x * num4) + (y * num3)) + (z * num2);
result.X = ((x * num) + (num4 * w)) + num12;
result.Y = ((y * num) + (num3 * w)) + num11;
result.Z = ((z * num) + (num2 * w)) + num10;
result.W = (w * num) - num9;
}
/// <summary>
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of <see cref="Quaternion"/> and a scalar.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
/// <param name="scaleFactor">Scalar value.</param>
/// <param name="result">The result of the quaternion multiplication with a scalar as an output parameter.</param>
public static void Multiply(
ref Quaternion quaternion1,
Fix64 scaleFactor,
out Quaternion result
)
{
result.X = quaternion1.X * scaleFactor;
result.Y = quaternion1.Y * scaleFactor;
result.Z = quaternion1.Z * scaleFactor;
result.W = quaternion1.W * scaleFactor;
}
/// <summary>
/// Flips the sign of the all the quaternion components.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
/// <returns>The result of the quaternion negation.</returns>
public static Quaternion Negate(Quaternion quaternion)
{
return new Quaternion(
-quaternion.X,
-quaternion.Y,
-quaternion.Z,
-quaternion.W
);
}
/// <summary>
/// Flips the sign of the all the quaternion components.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
/// <param name="result">The result of the quaternion negation as an output parameter.</param>
public static void Negate(ref Quaternion quaternion, out Quaternion result)
{
result.X = -quaternion.X;
result.Y = -quaternion.Y;
result.Z = -quaternion.Z;
result.W = -quaternion.W;
}
/// <summary>
/// Scales the quaternion magnitude to unit length.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
/// <returns>The unit length quaternion.</returns>
public static Quaternion Normalize(Quaternion quaternion)
{
Quaternion quaternion2;
Normalize(ref quaternion, out quaternion2);
return quaternion2;
}
/// <summary>
/// Scales the quaternion magnitude to unit length.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
/// <param name="result">The unit length quaternion an output parameter.</param>
public static void Normalize(ref Quaternion quaternion, out Quaternion result)
{
Fix64 lengthSquared = (quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) +
(quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W);
if (lengthSquared == Fix64.Zero)
{
result = Identity;
return;
}
Fix64 num = Fix64.One / Fix64.Sqrt(lengthSquared);
result.X = quaternion.X * num;
result.Y = quaternion.Y * num;
result.Z = quaternion.Z * num;
result.W = quaternion.W * num;
}
public static Quaternion LookAt(in Vector3 forward, in Vector3 up)
{
Matrix4x4 orientation = Matrix4x4.Identity;
orientation.Forward = forward;
orientation.Right = Vector3.Normalize(Vector3.Cross(forward, up));
orientation.Up = Vector3.Cross(orientation.Right, forward);
return Quaternion.CreateFromRotationMatrix(orientation);
}
#endregion
#region Public Static Operator Overloads
/// <summary>
/// Adds two quaternions.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/> on the left of the add sign.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/> on the right of the add sign.</param>
/// <returns>Sum of the vectors.</returns>
public static Quaternion operator +(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Add(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Divides a <see cref="Quaternion"/> by the other <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/> on the left of the div sign.</param>
/// <param name="quaternion2">Divisor <see cref="Quaternion"/> on the right of the div sign.</param>
/// <returns>The result of dividing the quaternions.</returns>
public static Quaternion operator /(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Divide(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Compares whether two <see cref="Quaternion"/> instances are equal.
/// </summary>
/// <param name="quaternion1"><see cref="Quaternion"/> instance on the left of the equal sign.</param>
/// <param name="quaternion2"><see cref="Quaternion"/> instance on the right of the equal sign.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(Quaternion quaternion1, Quaternion quaternion2)
{
return quaternion1.Equals(quaternion2);
}
/// <summary>
/// Compares whether two <see cref="Quaternion"/> instances are not equal.
/// </summary>
/// <param name="quaternion1"><see cref="Quaternion"/> instance on the left of the not equal sign.</param>
/// <param name="quaternion2"><see cref="Quaternion"/> instance on the right of the not equal sign.</param>
/// <returns><c>true</c> if the instances are not equal; <c>false</c> otherwise.</returns>
public static bool operator !=(Quaternion quaternion1, Quaternion quaternion2)
{
return !quaternion1.Equals(quaternion2);
}
/// <summary>
/// Multiplies two quaternions.
/// </summary>
/// <param name="quaternion1">Source <see cref="Quaternion"/> on the left of the mul sign.</param>
/// <param name="quaternion2">Source <see cref="Quaternion"/> on the right of the mul sign.</param>
/// <returns>Result of the quaternions multiplication.</returns>
public static Quaternion operator *(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Multiply(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Multiplies the components of quaternion by a scalar.
/// </summary>
/// <param name="quaternion1">Source <see cref="Vector3"/> on the left of the mul sign.</param>
/// <param name="scaleFactor">Scalar value on the right of the mul sign.</param>
/// <returns>Result of the quaternion multiplication with a scalar.</returns>
public static Quaternion operator *(Quaternion quaternion1, Fix64 scaleFactor)
{
Quaternion quaternion;
Multiply(ref quaternion1, scaleFactor, out quaternion);
return quaternion;
}
/// <summary>
/// Subtracts a <see cref="Quaternion"/> from a <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion1">Source <see cref="Vector3"/> on the left of the sub sign.</param>
/// <param name="quaternion2">Source <see cref="Vector3"/> on the right of the sub sign.</param>
/// <returns>Result of the quaternion subtraction.</returns>
public static Quaternion operator -(Quaternion quaternion1, Quaternion quaternion2)
{
Quaternion quaternion;
Subtract(ref quaternion1, ref quaternion2, out quaternion);
return quaternion;
}
/// <summary>
/// Flips the sign of the all the quaternion components.
/// </summary>
/// <param name="quaternion">Source <see cref="Quaternion"/> on the right of the sub sign.</param>
/// <returns>The result of the quaternion negation.</returns>
public static Quaternion operator -(Quaternion quaternion)
{
Quaternion quaternion2;
Negate(ref quaternion, out quaternion2);
return quaternion2;
}
#endregion
}
}

View File

@ -1,835 +0,0 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2022 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math.Fixed
{
/// <summary>
/// Describes a fixed point 2D-vector.
/// </summary>
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]
[StructLayout(LayoutKind.Explicit)]
public struct Vector2 : IEquatable<Vector2>
{
#region Public Static Properties
/// <summary>
/// Returns a <see cref="Vector2"/> with components 0, 0.
/// </summary>
public static Vector2 Zero
{
get
{
return zeroVector;
}
}
/// <summary>
/// Returns a <see cref="Vector2"/> with components 1, 1.
/// </summary>
public static Vector2 One
{
get
{
return unitVector;
}
}
/// <summary>
/// Returns a <see cref="Vector2"/> with components 1, 0.
/// </summary>
public static Vector2 UnitX
{
get
{
return unitXVector;
}
}
/// <summary>
/// Returns a <see cref="Vector2"/> with components 0, 1.
/// </summary>
public static Vector2 UnitY
{
get
{
return unitYVector;
}
}
#endregion
#region Internal Properties
internal string DebugDisplayString
{
get
{
return string.Concat(
X.ToString(), " ",
Y.ToString()
);
}
}
#endregion
#region Public Fields
/// <summary>
/// The x coordinate of this <see cref="Vector2"/>.
/// </summary>
[FieldOffset(0)]
public Fix64 X;
/// <summary>
/// The y coordinate of this <see cref="Vector2"/>.
/// </summary>
[FieldOffset(8)]
public Fix64 Y;
#endregion
#region Private Static Fields
private static readonly Vector2 zeroVector = new Vector2(0, 0);
private static readonly Vector2 unitVector = new Vector2(1, 1);
private static readonly Vector2 unitXVector = new Vector2(1, 0);
private static readonly Vector2 unitYVector = new Vector2(0, 1);
#endregion
#region Public Constructors
/// <summary>
/// Constructs a 2d vector with X and Y from two values.
/// </summary>
/// <param name="x">The x coordinate in 2d-space.</param>
/// <param name="y">The y coordinate in 2d-space.</param>
public Vector2(Fix64 x, Fix64 y)
{
this.X = x;
this.Y = y;
}
/// <summary>
/// Constructs a 2d vector with X and Y set to the same value.
/// </summary>
/// <param name="value">The x and y coordinates in 2d-space.</param>
public Vector2(Fix64 value)
{
this.X = value;
this.Y = value;
}
public Vector2(int x, int y)
{
this.X = new Fix64(x);
this.Y = new Fix64(y);
}
#endregion
#region Public Methods
/// <summary>
/// Compares whether current instance is equal to specified <see cref="Object"/>.
/// </summary>
/// <param name="obj">The <see cref="Object"/> to compare.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public override bool Equals(object obj)
{
return obj is Vector2 fixVector && Equals(fixVector);
}
/// <summary>
/// Compares whether current instance is equal to specified <see cref="Vector2"/>.
/// </summary>
/// <param name="other">The <see cref="Vector2"/> to compare.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Vector2 other)
{
return (X == other.X &&
Y == other.Y);
}
/// <summary>
/// Gets the hash code of this <see cref="Vector2"/>.
/// </summary>
/// <returns>Hash code of this <see cref="Vector2"/>.</returns>
public override int GetHashCode()
{
return X.GetHashCode() + Y.GetHashCode();
}
/// <summary>
/// Returns the length of this <see cref="Vector2"/>.
/// </summary>
/// <returns>The length of this <see cref="Vector2"/>.</returns>
public Fix64 Length()
{
return Fix64.Sqrt((X * X) + (Y * Y));
}
/// <summary>
/// Returns the squared length of this <see cref="Vector2"/>.
/// </summary>
/// <returns>The squared length of this <see cref="Vector2"/>.</returns>
public Fix64 LengthSquared()
{
return (X * X) + (Y * Y);
}
/// <summary>
/// Turns this <see cref="Vector2"/> to an angle in radians.
/// </summary>
public Fix64 Angle()
{
return Fix64.Atan2(Y, X);
}
/// <summary>
/// Returns this Vector2 with the fractional components cut off.
/// </summary>
public Vector2 Truncated()
{
return new Vector2((int) X, (int) Y);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains linear interpolation of the specified vectors.
/// </summary>
/// <param name="value1">The first vector.</param>
/// <param name="value2">The second vector.</param>
/// <param name="amount">Weighting value(between 0.0 and 1.0).</param>
/// <returns>The result of linear interpolation of the specified vectors.</returns>
public static Vector2 Lerp(Vector2 value1, Vector2 value2, Fix64 amount)
{
return new Vector2(
Fix64.Lerp(value1.X, value2.X, amount),
Fix64.Lerp(value1.Y, value2.Y, amount)
);
}
/// <summary>
/// Returns a <see cref="String"/> representation of this <see cref="Vector2"/> in the format:
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>]}
/// </summary>
/// <returns>A <see cref="String"/> representation of this <see cref="Vector2"/>.</returns>
public override string ToString()
{
return (
"{X:" + X.ToString() +
" Y:" + Y.ToString() +
"}"
);
}
#endregion
#region Public Static Methods
/// <summary>
/// Performs vector addition on <paramref name="value1"/> and <paramref name="value2"/>.
/// </summary>
/// <param name="value1">The first vector to add.</param>
/// <param name="value2">The second vector to add.</param>
/// <returns>The result of the vector addition.</returns>
public static Vector2 Add(Vector2 value1, Vector2 value2)
{
value1.X += value2.X;
value1.Y += value2.Y;
return value1;
}
/// <summary>
/// Clamps the specified value within a range.
/// </summary>
/// <param name="value1">The value to clamp.</param>
/// <param name="min">The min value.</param>
/// <param name="max">The max value.</param>
/// <returns>The clamped value.</returns>
public static Vector2 Clamp(Vector2 value1, Vector2 min, Vector2 max)
{
return new Vector2(
Fix64.Clamp(value1.X, min.X, max.X),
Fix64.Clamp(value1.Y, min.Y, max.Y)
);
}
/// <summary>
/// Returns the distance between two vectors.
/// </summary>
/// <param name="value1">The first vector.</param>
/// <param name="value2">The second vector.</param>
/// <returns>The distance between two vectors.</returns>
public static Fix64 Distance(Vector2 value1, Vector2 value2)
{
Fix64 v1 = value1.X - value2.X, v2 = value1.Y - value2.Y;
return Fix64.Sqrt((v1 * v1) + (v2 * v2));
}
/// <summary>
/// Returns the squared distance between two vectors.
/// </summary>
/// <param name="value1">The first vector.</param>
/// <param name="value2">The second vector.</param>
/// <returns>The squared distance between two vectors.</returns>
public static Fix64 DistanceSquared(Vector2 value1, Vector2 value2)
{
Fix64 v1 = value1.X - value2.X, v2 = value1.Y - value2.Y;
return (v1 * v1) + (v2 * v2);
}
/// <summary>
/// Divides the components of a <see cref="Vector2"/> by the components of another <see cref="Vector2"/>.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/>.</param>
/// <param name="value2">Divisor <see cref="Vector2"/>.</param>
/// <returns>The result of dividing the vectors.</returns>
public static Vector2 Divide(Vector2 value1, Vector2 value2)
{
value1.X /= value2.X;
value1.Y /= value2.Y;
return value1;
}
/// <summary>
/// Divides the components of a <see cref="Vector2"/> by a scalar.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/>.</param>
/// <param name="divider">Divisor scalar.</param>
/// <returns>The result of dividing a vector by a scalar.</returns>
public static Vector2 Divide(Vector2 value1, Fix64 divider)
{
Fix64 factor = Fix64.One / divider;
value1.X *= factor;
value1.Y *= factor;
return value1;
}
/// <summary>
/// Returns a dot product of two vectors.
/// </summary>
/// <param name="value1">The first vector.</param>
/// <param name="value2">The second vector.</param>
/// <returns>The dot product of two vectors.</returns>
public static Fix64 Dot(Vector2 value1, Vector2 value2)
{
return (value1.X * value2.X) + (value1.Y * value2.Y);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a maximal values from the two vectors.
/// </summary>
/// <param name="value1">The first vector.</param>
/// <param name="value2">The second vector.</param>
/// <returns>The <see cref="Vector2"/> with maximal values from the two vectors.</returns>
public static Vector2 Max(Vector2 value1, Vector2 value2)
{
return new Vector2(
value1.X > value2.X ? value1.X : value2.X,
value1.Y > value2.Y ? value1.Y : value2.Y
);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a minimal values from the two vectors.
/// </summary>
/// <param name="value1">The first vector.</param>
/// <param name="value2">The second vector.</param>
/// <returns>The <see cref="Vector2"/> with minimal values from the two vectors.</returns>
public static Vector2 Min(Vector2 value1, Vector2 value2)
{
return new Vector2(
value1.X < value2.X ? value1.X : value2.X,
value1.Y < value2.Y ? value1.Y : value2.Y
);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a multiplication of two vectors.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/>.</param>
/// <param name="value2">Source <see cref="Vector2"/>.</param>
/// <returns>The result of the vector multiplication.</returns>
public static Vector2 Multiply(Vector2 value1, Vector2 value2)
{
value1.X *= value2.X;
value1.Y *= value2.Y;
return value1;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a multiplication of <see cref="Vector2"/> and a scalar.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/>.</param>
/// <param name="scaleFactor">Scalar value.</param>
/// <returns>The result of the vector multiplication with a scalar.</returns>
public static Vector2 Multiply(Vector2 value1, Fix64 scaleFactor)
{
value1.X *= scaleFactor;
value1.Y *= scaleFactor;
return value1;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains the specified vector inversion.
/// direction of <paramref name="value"/>.
/// </summary>
/// <param name="value">Source <see cref="Vector2"/>.</param>
/// <returns>The result of the vector inversion.</returns>
public static Vector2 Negate(Vector2 value)
{
value.X = -value.X;
value.Y = -value.Y;
return value;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a normalized values from another vector.
/// </summary>
/// <param name="value">Source <see cref="Vector2"/>.</param>
/// <returns>Unit vector.</returns>
public static Vector2 Normalize(Vector2 value)
{
Fix64 lengthSquared = (value.X * value.X) + (value.Y * value.Y);
if (lengthSquared == Fix64.Zero)
{
return Zero;
}
Fix64 val = Fix64.One / Fix64.Sqrt(lengthSquared);
value.X *= val;
value.Y *= val;
return value;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains reflect vector of the given vector and normal.
/// </summary>
/// <param name="vector">Source <see cref="Vector2"/>.</param>
/// <param name="normal">Reflection normal.</param>
/// <returns>Reflected vector.</returns>
public static Vector2 Reflect(Vector2 vector, Vector2 normal)
{
Vector2 result;
Fix64 val = new Fix64(2) * ((vector.X * normal.X) + (vector.Y * normal.Y));
result.X = vector.X - (normal.X * val);
result.Y = vector.Y - (normal.Y * val);
return result;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains subtraction of on <see cref="Vector2"/> from a another.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/>.</param>
/// <param name="value2">Source <see cref="Vector2"/>.</param>
/// <returns>The result of the vector subtraction.</returns>
public static Vector2 Subtract(Vector2 value1, Vector2 value2)
{
value1.X -= value2.X;
value1.Y -= value2.Y;
return value1;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="position">Source <see cref="Vector2"/>.</param>
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
/// <returns>Transformed <see cref="Vector2"/>.</returns>
public static Vector2 Transform(Vector2 position, Matrix4x4 matrix)
{
return new Vector2(
(position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M41,
(position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M42
);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Quaternion"/>, representing the rotation.
/// </summary>
/// <param name="value">Source <see cref="Vector2"/>.</param>
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
/// <returns>Transformed <see cref="Vector2"/>.</returns>
public static Vector2 Transform(Vector2 value, Quaternion rotation)
{
Transform(ref value, ref rotation, out value);
return value;
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Quaternion"/>, representing the rotation.
/// </summary>
/// <param name="value">Source <see cref="Vector2"/>.</param>
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
/// <param name="result">Transformed <see cref="Vector2"/> as an output parameter.</param>
public static void Transform(
ref Vector2 value,
ref Quaternion rotation,
out Vector2 result
)
{
Fix64 two = new Fix64(2);
Fix64 x = two * -(rotation.Z * value.Y);
Fix64 y = two * (rotation.Z * value.X);
Fix64 z = two * (rotation.X * value.Y - rotation.Y * value.X);
result.X = value.X + x * rotation.W + (rotation.Y * z - rotation.Z * y);
result.Y = value.Y + y * rotation.W + (rotation.Z * x - rotation.X * z);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Math.Matrix3x2"/>.
/// </summary>
/// <param name="position">Source <see cref="Vector2"/>.</param>
/// <param name="matrix">The transformation <see cref="Math.Matrix3x2"/>.</param>
/// <returns>Transformed <see cref="Vector2"/>.</returns>
public static Vector2 Transform(Vector2 position, Matrix3x2 matrix)
{
return new Vector2(
(position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M31,
(position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M32
);
}
/// <summary>
/// Apply transformation on all vectors within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
/// </summary>
/// <param name="sourceArray">Source array.</param>
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
/// <param name="destinationArray">Destination array.</param>
public static void Transform(
Vector2[] sourceArray,
ref Matrix4x4 matrix,
Vector2[] destinationArray
)
{
Transform(sourceArray, 0, ref matrix, destinationArray, 0, sourceArray.Length);
}
/// <summary>
/// Apply transformation on vectors within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
/// </summary>
/// <param name="sourceArray">Source array.</param>
/// <param name="sourceIndex">The starting index of transformation in the source array.</param>
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
/// <param name="destinationArray">Destination array.</param>
/// <param name="destinationIndex">The starting index in the destination array, where the first <see cref="Vector2"/> should be written.</param>
/// <param name="length">The number of vectors to be transformed.</param>
public static void Transform(
Vector2[] sourceArray,
int sourceIndex,
ref Matrix4x4 matrix,
Vector2[] destinationArray,
int destinationIndex,
int length
)
{
for (int x = 0; x < length; x += 1)
{
Vector2 position = sourceArray[sourceIndex + x];
Vector2 destination = destinationArray[destinationIndex + x];
destination.X = (position.X * matrix.M11) + (position.Y * matrix.M21)
+ matrix.M41;
destination.Y = (position.X * matrix.M12) + (position.Y * matrix.M22)
+ matrix.M42;
destinationArray[destinationIndex + x] = destination;
}
}
/// <summary>
/// Apply transformation on all vectors within array of <see cref="Vector2"/> by the specified <see cref="Quaternion"/> and places the results in an another array.
/// </summary>
/// <param name="sourceArray">Source array.</param>
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
/// <param name="destinationArray">Destination array.</param>
public static void Transform(
Vector2[] sourceArray,
ref Quaternion rotation,
Vector2[] destinationArray
)
{
Transform(
sourceArray,
0,
ref rotation,
destinationArray,
0,
sourceArray.Length
);
}
/// <summary>
/// Apply transformation on vectors within array of <see cref="Vector2"/> by the specified <see cref="Quaternion"/> and places the results in an another array.
/// </summary>
/// <param name="sourceArray">Source array.</param>
/// <param name="sourceIndex">The starting index of transformation in the source array.</param>
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
/// <param name="destinationArray">Destination array.</param>
/// <param name="destinationIndex">The starting index in the destination array, where the first <see cref="Vector2"/> should be written.</param>
/// <param name="length">The number of vectors to be transformed.</param>
public static void Transform(
Vector2[] sourceArray,
int sourceIndex,
ref Quaternion rotation,
Vector2[] destinationArray,
int destinationIndex,
int length
)
{
for (int i = 0; i < length; i += 1)
{
Vector2 position = sourceArray[sourceIndex + i];
Vector2 v;
Transform(ref position, ref rotation, out v);
destinationArray[destinationIndex + i] = v;
}
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix4x4"/>.
/// </summary>
/// <param name="normal">Source <see cref="Vector2"/> which represents a normal vector.</param>
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
/// <returns>Transformed normal.</returns>
public static Vector2 TransformNormal(Vector2 normal, Matrix4x4 matrix)
{
return new Vector2(
(normal.X * matrix.M11) + (normal.Y * matrix.M21),
(normal.X * matrix.M12) + (normal.Y * matrix.M22)
);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Math.Matrix3x2"/>.
/// </summary>
/// <param name="normal">Source <see cref="Vector2"/> which represents a normal vector.</param>
/// <param name="matrix">The transformation <see cref="Math.Matrix3x2"/>.</param>
/// <returns>Transformed normal.</returns>
public static Vector2 TransformNormal(Vector2 normal, Matrix3x2 matrix)
{
return new Vector2(
normal.X * matrix.M11 + normal.Y * matrix.M21,
normal.X * matrix.M12 + normal.Y * matrix.M22);
}
/// <summary>
/// Apply transformation on all normals within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
/// </summary>
/// <param name="sourceArray">Source array.</param>
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
/// <param name="destinationArray">Destination array.</param>
public static void TransformNormal(
Vector2[] sourceArray,
ref Matrix4x4 matrix,
Vector2[] destinationArray
)
{
TransformNormal(
sourceArray,
0,
ref matrix,
destinationArray,
0,
sourceArray.Length
);
}
/// <summary>
/// Apply transformation on normals within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
/// </summary>
/// <param name="sourceArray">Source array.</param>
/// <param name="sourceIndex">The starting index of transformation in the source array.</param>
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
/// <param name="destinationArray">Destination array.</param>
/// <param name="destinationIndex">The starting index in the destination array, where the first <see cref="Vector2"/> should be written.</param>
/// <param name="length">The number of normals to be transformed.</param>
public static void TransformNormal(
Vector2[] sourceArray,
int sourceIndex,
ref Matrix4x4 matrix,
Vector2[] destinationArray,
int destinationIndex,
int length
)
{
for (int i = 0; i < length; i += 1)
{
Vector2 position = sourceArray[sourceIndex + i];
Vector2 result;
result.X = (position.X * matrix.M11) + (position.Y * matrix.M21);
result.Y = (position.X * matrix.M12) + (position.Y * matrix.M22);
destinationArray[destinationIndex + i] = result;
}
}
/// <summary>
/// Rotates a Vector2 by an angle.
/// </summary>
/// <param name="vector">The vector to rotate.</param>
/// <param name="angle">The angle in radians.</param>
public static Vector2 Rotate(Vector2 vector, Fix64 angle)
{
return new Vector2(
vector.X * Fix64.Cos(angle) - vector.Y * Fix64.Sin(angle),
vector.X * Fix64.Sin(angle) + vector.Y * Fix64.Cos(angle)
);
}
#endregion
#region Public Static Operators
/// <summary>
/// Inverts values in the specified <see cref="Vector2"/>.
/// </summary>
/// <param name="value">Source <see cref="Vector2"/> on the right of the sub sign.</param>
/// <returns>Result of the inversion.</returns>
public static Vector2 operator -(Vector2 value)
{
value.X = -value.X;
value.Y = -value.Y;
return value;
}
/// <summary>
/// Compares whether two <see cref="Vector2"/> instances are equal.
/// </summary>
/// <param name="value1"><see cref="Vector2"/> instance on the left of the equal sign.</param>
/// <param name="value2"><see cref="Vector2"/> instance on the right of the equal sign.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(Vector2 value1, Vector2 value2)
{
return (value1.X == value2.X &&
value1.Y == value2.Y);
}
/// <summary>
/// Compares whether two <see cref="Vector2"/> instances are equal.
/// </summary>
/// <param name="value1"><see cref="Vector2"/> instance on the left of the equal sign.</param>
/// <param name="value2"><see cref="Vector2"/> instance on the right of the equal sign.</param>
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator !=(Vector2 value1, Vector2 value2)
{
return !(value1 == value2);
}
/// <summary>
/// Adds two vectors.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/> on the left of the add sign.</param>
/// <param name="value2">Source <see cref="Vector2"/> on the right of the add sign.</param>
/// <returns>Sum of the vectors.</returns>
public static Vector2 operator +(Vector2 value1, Vector2 value2)
{
value1.X += value2.X;
value1.Y += value2.Y;
return value1;
}
/// <summary>
/// Subtracts a <see cref="Vector2"/> from a <see cref="Vector2"/>.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/> on the left of the sub sign.</param>
/// <param name="value2">Source <see cref="Vector2"/> on the right of the sub sign.</param>
/// <returns>Result of the vector subtraction.</returns>
public static Vector2 operator -(Vector2 value1, Vector2 value2)
{
value1.X -= value2.X;
value1.Y -= value2.Y;
return value1;
}
/// <summary>
/// Multiplies the components of two vectors by each other.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/> on the left of the mul sign.</param>
/// <param name="value2">Source <see cref="Vector2"/> on the right of the mul sign.</param>
/// <returns>Result of the vector multiplication.</returns>
public static Vector2 operator *(Vector2 value1, Vector2 value2)
{
value1.X *= value2.X;
value1.Y *= value2.Y;
return value1;
}
/// <summary>
/// Multiplies the components of vector by a scalar.
/// </summary>
/// <param name="value">Source <see cref="Vector2"/> on the left of the mul sign.</param>
/// <param name="scaleFactor">Scalar value on the right of the mul sign.</param>
/// <returns>Result of the vector multiplication with a scalar.</returns>
public static Vector2 operator *(Vector2 value, Fix64 scaleFactor)
{
value.X *= scaleFactor;
value.Y *= scaleFactor;
return value;
}
/// <summary>
/// Multiplies the components of vector by a scalar.
/// </summary>
/// <param name="scaleFactor">Scalar value on the left of the mul sign.</param>
/// <param name="value">Source <see cref="Vector2"/> on the right of the mul sign.</param>
/// <returns>Result of the vector multiplication with a scalar.</returns>
public static Vector2 operator *(Fix64 scaleFactor, Vector2 value)
{
value.X *= scaleFactor;
value.Y *= scaleFactor;
return value;
}
/// <summary>
/// Divides the components of a <see cref="Vector2"/> by the components of another <see cref="Vector2"/>.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/> on the left of the div sign.</param>
/// <param name="value2">Divisor <see cref="Vector2"/> on the right of the div sign.</param>
/// <returns>The result of dividing the vectors.</returns>
public static Vector2 operator /(Vector2 value1, Vector2 value2)
{
value1.X /= value2.X;
value1.Y /= value2.Y;
return value1;
}
/// <summary>
/// Divides the components of a <see cref="Vector2"/> by a scalar.
/// </summary>
/// <param name="value1">Source <see cref="Vector2"/> on the left of the div sign.</param>
/// <param name="divider">Divisor scalar on the right of the div sign.</param>
/// <returns>The result of dividing a vector by a scalar.</returns>
public static Vector2 operator /(Vector2 value1, Fix64 divider)
{
Fix64 factor = Fix64.One / divider;
value1.X *= factor;
value1.Y *= factor;
return value1;
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,826 +0,0 @@
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Microsoft.
* Released under the MIT license.
* See microsoft.LICENSE for details.
*/
using System;
using System.Globalization;
namespace MoonWorks.Math.Float
{
/// <summary>
/// A structure encapsulating a 3x2 matrix.
/// </summary>
public struct Matrix3x2 : IEquatable<Matrix3x2>
{
#region Public Fields
/// <summary>
/// The first element of the first row
/// </summary>
public float M11;
/// <summary>
/// The second element of the first row
/// </summary>
public float M12;
/// <summary>
/// The first element of the second row
/// </summary>
public float M21;
/// <summary>
/// The second element of the second row
/// </summary>
public float M22;
/// <summary>
/// The first element of the third row
/// </summary>
public float M31;
/// <summary>
/// The second element of the third row
/// </summary>
public float M32;
#endregion Public Fields
private static readonly Matrix3x2 _identity = new Matrix3x2
(
1f, 0f,
0f, 1f,
0f, 0f
);
/// <summary>
/// Returns the multiplicative identity matrix.
/// </summary>
public static Matrix3x2 Identity
{
get { return _identity; }
}
/// <summary>
/// Returns whether the matrix is the identity matrix.
/// </summary>
public bool IsIdentity
{
get
{
return M11 == 1f && M22 == 1f && // Check diagonal element first for early out.
M12 == 0f &&
M21 == 0f &&
M31 == 0f && M32 == 0f;
}
}
/// <summary>
/// Gets or sets the translation component of this matrix.
/// </summary>
public Vector2 Translation
{
get
{
return new Vector2(M31, M32);
}
set
{
M31 = value.X;
M32 = value.Y;
}
}
/// <summary>
/// Constructs a Matrix3x2 from the given components.
/// </summary>
public Matrix3x2(float m11, float m12,
float m21, float m22,
float m31, float m32)
{
this.M11 = m11;
this.M12 = m12;
this.M21 = m21;
this.M22 = m22;
this.M31 = m31;
this.M32 = m32;
}
/// <summary>
/// Creates a translation matrix from the given vector.
/// </summary>
/// <param name="position">The translation position.</param>
/// <returns>A translation matrix.</returns>
public static Matrix3x2 CreateTranslation(Vector2 position)
{
Matrix3x2 result;
result.M11 = 1.0f;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = 1.0f;
result.M31 = position.X;
result.M32 = position.Y;
return result;
}
/// <summary>
/// Creates a translation matrix from the given X and Y components.
/// </summary>
/// <param name="xPosition">The X position.</param>
/// <param name="yPosition">The Y position.</param>
/// <returns>A translation matrix.</returns>
public static Matrix3x2 CreateTranslation(float xPosition, float yPosition)
{
Matrix3x2 result;
result.M11 = 1.0f;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = 1.0f;
result.M31 = xPosition;
result.M32 = yPosition;
return result;
}
/// <summary>
/// Creates a scale matrix from the given X and Y components.
/// </summary>
/// <param name="xScale">Value to scale by on the X-axis.</param>
/// <param name="yScale">Value to scale by on the Y-axis.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float xScale, float yScale)
{
Matrix3x2 result;
result.M11 = xScale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = yScale;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a scale matrix that is offset by a given center point.
/// </summary>
/// <param name="xScale">Value to scale by on the X-axis.</param>
/// <param name="yScale">Value to scale by on the Y-axis.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float xScale, float yScale, Vector2 centerPoint)
{
Matrix3x2 result;
float tx = centerPoint.X * (1 - xScale);
float ty = centerPoint.Y * (1 - yScale);
result.M11 = xScale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = yScale;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a scale matrix from the given vector scale.
/// </summary>
/// <param name="scales">The scale to use.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Vector2 scales)
{
Matrix3x2 result;
result.M11 = scales.X;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scales.Y;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
/// </summary>
/// <param name="scales">The scale to use.</param>
/// <param name="centerPoint">The center offset.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Vector2 scales, Vector2 centerPoint)
{
Matrix3x2 result;
float tx = centerPoint.X * (1 - scales.X);
float ty = centerPoint.Y * (1 - scales.Y);
result.M11 = scales.X;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scales.Y;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a scale matrix that scales uniformly with the given scale.
/// </summary>
/// <param name="scale">The uniform scale to use.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float scale)
{
Matrix3x2 result;
result.M11 = scale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scale;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
/// </summary>
/// <param name="scale">The uniform scale to use.</param>
/// <param name="centerPoint">The center offset.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float scale, Vector2 centerPoint)
{
Matrix3x2 result;
float tx = centerPoint.X * (1 - scale);
float ty = centerPoint.Y * (1 - scale);
result.M11 = scale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scale;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a skew matrix from the given angles in radians.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>A skew matrix.</returns>
public static Matrix3x2 CreateSkew(float radiansX, float radiansY)
{
Matrix3x2 result;
float xTan = (float) System.Math.Tan(radiansX);
float yTan = (float) System.Math.Tan(radiansY);
result.M11 = 1.0f;
result.M12 = yTan;
result.M21 = xTan;
result.M22 = 1.0f;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a skew matrix from the given angles in radians and a center point.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A skew matrix.</returns>
public static Matrix3x2 CreateSkew(float radiansX, float radiansY, Vector2 centerPoint)
{
Matrix3x2 result;
float xTan = (float) System.Math.Tan(radiansX);
float yTan = (float) System.Math.Tan(radiansY);
float tx = -centerPoint.Y * xTan;
float ty = -centerPoint.X * yTan;
result.M11 = 1.0f;
result.M12 = yTan;
result.M21 = xTan;
result.M22 = 1.0f;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a rotation matrix using the given rotation in radians.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>A rotation matrix.</returns>
public static Matrix3x2 CreateRotation(float radians)
{
Matrix3x2 result;
radians = (float) System.Math.IEEERemainder(radians, System.Math.PI * 2);
float c, s;
const float epsilon = 0.001f * (float) System.Math.PI / 180f; // 0.1% of a degree
if (radians > -epsilon && radians < epsilon)
{
// Exact case for zero rotation.
c = 1;
s = 0;
}
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
{
// Exact case for 90 degree rotation.
c = 0;
s = 1;
}
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
{
// Exact case for 180 degree rotation.
c = -1;
s = 0;
}
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
{
// Exact case for 270 degree rotation.
c = 0;
s = -1;
}
else
{
// Arbitrary rotation.
c = (float) System.Math.Cos(radians);
s = (float) System.Math.Sin(radians);
}
// [ c s ]
// [ -s c ]
// [ 0 0 ]
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a rotation matrix using the given rotation in radians and a center point.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A rotation matrix.</returns>
public static Matrix3x2 CreateRotation(float radians, Vector2 centerPoint)
{
Matrix3x2 result;
radians = (float) System.Math.IEEERemainder(radians, System.Math.PI * 2);
float c, s;
const float epsilon = 0.001f * (float) System.Math.PI / 180f; // 0.1% of a degree
if (radians > -epsilon && radians < epsilon)
{
// Exact case for zero rotation.
c = 1;
s = 0;
}
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
{
// Exact case for 90 degree rotation.
c = 0;
s = 1;
}
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
{
// Exact case for 180 degree rotation.
c = -1;
s = 0;
}
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
{
// Exact case for 270 degree rotation.
c = 0;
s = -1;
}
else
{
// Arbitrary rotation.
c = (float) System.Math.Cos(radians);
s = (float) System.Math.Sin(radians);
}
float x = centerPoint.X * (1 - c) + centerPoint.Y * s;
float y = centerPoint.Y * (1 - c) - centerPoint.X * s;
// [ c s ]
// [ -s c ]
// [ x y ]
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = x;
result.M32 = y;
return result;
}
/// <summary>
/// Calculates the determinant for this matrix.
/// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1).
/// </summary>
/// <returns>The determinant.</returns>
public float GetDeterminant()
{
// There isn't actually any such thing as a determinant for a non-square matrix,
// but this 3x2 type is really just an optimization of a 3x3 where we happen to
// know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
//
// [ M11, M12, 0 ]
// [ M21, M22, 0 ]
// [ M31, M32, 1 ]
//
// Sum the diagonal products:
// (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
//
// Subtract the opposite diagonal products:
// (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
//
// Collapse out the constants and oh look, this is just a 2x2 determinant!
return (M11 * M22) - (M21 * M12);
}
/// <summary>
/// Attempts to invert the given matrix. If the operation succeeds, the inverted matrix is stored in the result parameter.
/// </summary>
/// <param name="matrix">The source matrix.</param>
/// <param name="result">The output matrix.</param>
/// <returns>True if the operation succeeded, False otherwise.</returns>
public static bool Invert(Matrix3x2 matrix, out Matrix3x2 result)
{
float det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12);
if (System.Math.Abs(det) < float.Epsilon)
{
result = new Matrix3x2(float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN);
return false;
}
float invDet = 1.0f / det;
result.M11 = matrix.M22 * invDet;
result.M12 = -matrix.M12 * invDet;
result.M21 = -matrix.M21 * invDet;
result.M22 = matrix.M11 * invDet;
result.M31 = (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet;
result.M32 = (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet;
return true;
}
/// <summary>
/// Linearly interpolates from matrix1 to matrix2, based on the third parameter.
/// </summary>
/// <param name="matrix1">The first source matrix.</param>
/// <param name="matrix2">The second source matrix.</param>
/// <param name="amount">The relative weighting of matrix2.</param>
/// <returns>The interpolated matrix.</returns>
public static Matrix3x2 Lerp(Matrix3x2 matrix1, Matrix3x2 matrix2, float amount)
{
Matrix3x2 result;
// First row
result.M11 = matrix1.M11 + (matrix2.M11 - matrix1.M11) * amount;
result.M12 = matrix1.M12 + (matrix2.M12 - matrix1.M12) * amount;
// Second row
result.M21 = matrix1.M21 + (matrix2.M21 - matrix1.M21) * amount;
result.M22 = matrix1.M22 + (matrix2.M22 - matrix1.M22) * amount;
// Third row
result.M31 = matrix1.M31 + (matrix2.M31 - matrix1.M31) * amount;
result.M32 = matrix1.M32 + (matrix2.M32 - matrix1.M32) * amount;
return result;
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static Matrix3x2 Negate(Matrix3x2 value)
{
Matrix3x2 result;
result.M11 = -value.M11;
result.M12 = -value.M12;
result.M21 = -value.M21;
result.M22 = -value.M22;
result.M31 = -value.M31;
result.M32 = -value.M32;
return result;
}
/// <summary>
/// Adds each matrix element in value1 with its corresponding element in value2.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the summed values.</returns>
public static Matrix3x2 Add(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 + value2.M11;
result.M12 = value1.M12 + value2.M12;
result.M21 = value1.M21 + value2.M21;
result.M22 = value1.M22 + value2.M22;
result.M31 = value1.M31 + value2.M31;
result.M32 = value1.M32 + value2.M32;
return result;
}
/// <summary>
/// Subtracts each matrix element in value2 from its corresponding element in value1.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the resulting values.</returns>
public static Matrix3x2 Subtract(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 - value2.M11;
result.M12 = value1.M12 - value2.M12;
result.M21 = value1.M21 - value2.M21;
result.M22 = value1.M22 - value2.M22;
result.M31 = value1.M31 - value2.M31;
result.M32 = value1.M32 - value2.M32;
return result;
}
/// <summary>
/// Multiplies two matrices together and returns the resulting matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The product matrix.</returns>
public static Matrix3x2 Multiply(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
// First row
result.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
result.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
result.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
result.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
// Third row
result.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
result.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
return result;
}
public Matrix4x4 ToMatrix4x4()
{
return new Matrix4x4(
M11, M12, 0, 0,
M21, M22, 0, 0,
0, 0, 1, 0,
M31, M32, 0, 1
);
}
/// <summary>
/// Scales all elements in a matrix by the given scalar factor.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling value to use.</param>
/// <returns>The resulting matrix.</returns>
public static Matrix3x2 Multiply(Matrix3x2 value1, float value2)
{
Matrix3x2 result;
result.M11 = value1.M11 * value2;
result.M12 = value1.M12 * value2;
result.M21 = value1.M21 * value2;
result.M22 = value1.M22 * value2;
result.M31 = value1.M31 * value2;
result.M32 = value1.M32 * value2;
return result;
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static Matrix3x2 operator -(Matrix3x2 value)
{
Matrix3x2 m;
m.M11 = -value.M11;
m.M12 = -value.M12;
m.M21 = -value.M21;
m.M22 = -value.M22;
m.M31 = -value.M31;
m.M32 = -value.M32;
return m;
}
/// <summary>
/// Adds each matrix element in value1 with its corresponding element in value2.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the summed values.</returns>
public static Matrix3x2 operator +(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 + value2.M11;
m.M12 = value1.M12 + value2.M12;
m.M21 = value1.M21 + value2.M21;
m.M22 = value1.M22 + value2.M22;
m.M31 = value1.M31 + value2.M31;
m.M32 = value1.M32 + value2.M32;
return m;
}
/// <summary>
/// Subtracts each matrix element in value2 from its corresponding element in value1.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the resulting values.</returns>
public static Matrix3x2 operator -(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 - value2.M11;
m.M12 = value1.M12 - value2.M12;
m.M21 = value1.M21 - value2.M21;
m.M22 = value1.M22 - value2.M22;
m.M31 = value1.M31 - value2.M31;
m.M32 = value1.M32 - value2.M32;
return m;
}
/// <summary>
/// Multiplies two matrices together and returns the resulting matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The product matrix.</returns>
public static Matrix3x2 operator *(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
// First row
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
// Third row
m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
return m;
}
/// <summary>
/// Scales all elements in a matrix by the given scalar factor.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling value to use.</param>
/// <returns>The resulting matrix.</returns>
public static Matrix3x2 operator *(Matrix3x2 value1, float value2)
{
Matrix3x2 m;
m.M11 = value1.M11 * value2;
m.M12 = value1.M12 * value2;
m.M21 = value1.M21 * value2;
m.M22 = value1.M22 * value2;
m.M31 = value1.M31 * value2;
m.M32 = value1.M32 * value2;
return m;
}
/// <summary>
/// Returns a boolean indicating whether the given matrices are equal.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>True if the matrices are equal; False otherwise.</returns>
public static bool operator ==(Matrix3x2 value1, Matrix3x2 value2)
{
return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && // Check diagonal element first for early out.
value1.M12 == value2.M12 &&
value1.M21 == value2.M21 &&
value1.M31 == value2.M31 && value1.M32 == value2.M32);
}
/// <summary>
/// Returns a boolean indicating whether the given matrices are not equal.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>True if the matrices are not equal; False if they are equal.</returns>
public static bool operator !=(Matrix3x2 value1, Matrix3x2 value2)
{
return (value1.M11 != value2.M11 || value1.M12 != value2.M12 ||
value1.M21 != value2.M21 || value1.M22 != value2.M22 ||
value1.M31 != value2.M31 || value1.M32 != value2.M32);
}
/// <summary>
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
/// </summary>
/// <param name="other">The other matrix to test equality against.</param>
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
public bool Equals(Matrix3x2 other)
{
return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out.
M12 == other.M12 &&
M21 == other.M21 &&
M31 == other.M31 && M32 == other.M32);
}
/// <summary>
/// Returns a boolean indicating whether the given Object is equal to this matrix instance.
/// </summary>
/// <param name="obj">The Object to compare against.</param>
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is Matrix3x2)
{
return Equals((Matrix3x2) obj);
}
return false;
}
/// <summary>
/// Returns a String representing this matrix instance.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
CultureInfo ci = CultureInfo.CurrentCulture;
return String.Format(ci, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}",
M11.ToString(ci), M12.ToString(ci),
M21.ToString(ci), M22.ToString(ci),
M31.ToString(ci), M32.ToString(ci));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return M11.GetHashCode() + M12.GetHashCode() +
M21.GetHashCode() + M22.GetHashCode() +
M31.GetHashCode() + M32.GetHashCode();
}
}
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -98,8 +98,7 @@ namespace MoonWorks.Math
float value3,
float amount1,
float amount2
)
{
) {
return value1 + (value2 - value1) * amount1 + (value3 - value1) * amount2;
}
@ -118,8 +117,7 @@ namespace MoonWorks.Math
float value3,
float value4,
float amount
)
{
) {
/* Using formula from http://www.mvps.org/directx/articles/catmull/
* Internally using doubles not to lose precision.
*/
@ -160,26 +158,6 @@ namespace MoonWorks.Math
return value;
}
/// <summary>
/// Restricts a value to be within a specified range.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">
/// The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c>
/// will be returned.
/// </param>
/// <param name="max">
/// The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c>
/// will be returned.
/// </param>
/// <returns>The clamped value.</returns>
public static int Clamp(int value, int min, int max)
{
value = (value > max) ? max : value;
value = (value < min) ? min : value;
return value;
}
/// <summary>
/// Calculates the absolute value of the difference of two values.
/// </summary>
@ -206,8 +184,7 @@ namespace MoonWorks.Math
float value2,
float tangent2,
float amount
)
{
) {
/* All transformed to double not to lose precision
* Otherwise, for high numbers of param:amount the result is NaN instead
* of Infinity.
@ -300,16 +277,6 @@ namespace MoonWorks.Math
return result;
}
public static float Quantize(float value, float step)
{
return System.MathF.Round(value / step) * step;
}
public static Fixed.Fix64 Quantize(Fixed.Fix64 value, Fixed.Fix64 step)
{
return Fixed.Fix64.Round(value / step) * step;
}
/// <summary>
/// Converts radians to degrees.
/// </summary>
@ -361,65 +328,31 @@ namespace MoonWorks.Math
return angle;
}
/// <summary>
/// Rescales a value within a given range to a new range.
/// </summary>
public static float Normalize(short value, short min, short max, short newMin, short newMax)
{
return ((float) (value - min) * (newMax - newMin)) / (max - min) + newMin;
}
/// <summary>
/// Rescales a value within a given range to a new range.
/// </summary>
public static float Normalize(float value, float min, float max, float newMin, float newMax)
{
return ((value - min) * (newMax - newMin)) / (max - min) + newMin;
}
/// <summary>
/// Step from start towards end by change.
/// </summary>
/// <param name="start">Start value.</param>
/// <param name="end">End value.</param>
/// <param name="change">Change value.</param>
public static float Approach(float start, float end, float change)
{
return start < end ?
System.Math.Min(start + change, end) :
System.Math.Max(start - change, end);
}
/// <summary>
/// Step from start towards end by change.
/// </summary>
/// <param name="start">Start value.</param>
/// <param name="end">End value.</param>
/// <param name="change">Change value.</param>
public static int Approach(int start, int end, int change)
{
return start < end ?
System.Math.Min(start + change, end) :
System.Math.Max(start - change, end);
}
/// <summary>
/// Step from start towards end by change.
/// </summary>
/// <param name="start">Start value.</param>
/// <param name="end">End value.</param>
/// <param name="change">Change value.</param>
public static Fixed.Fix64 Approach(Fixed.Fix64 start, Fixed.Fix64 end, Fixed.Fix64 change)
{
return start < end ?
Fixed.Fix64.Min(start + change, end) :
Fixed.Fix64.Max(start - change, end);
}
#endregion
#region Internal Static Methods
// FIXME: This could be an extension! ClampIntEXT? -flibit
/// <summary>
/// Restricts a value to be within a specified range.
/// </summary>
/// <param name="value">The value to clamp.</param>
/// <param name="min">
/// The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c>
/// will be returned.
/// </param>
/// <param name="max">
/// The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c>
/// will be returned.
/// </param>
/// <returns>The clamped value.</returns>
internal static int Clamp(int value, int min, int max)
{
value = (value > max) ? max : value;
value = (value < min) ? min : value;
return value;
}
internal static bool WithinEpsilon(float floatA, float floatB)
{
return System.Math.Abs(floatA - floatB) < MachineEpsilonFloat;

826
src/Math/Matrix3x2.cs Normal file
View File

@ -0,0 +1,826 @@
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Microsoft.
* Released under the MIT license.
* See microsoft.LICENSE for details.
*/
using System;
using System.Globalization;
namespace MoonWorks.Math
{
/// <summary>
/// A structure encapsulating a 3x2 matrix.
/// </summary>
public struct Matrix3x2 : IEquatable<Matrix3x2>
{
#region Public Fields
/// <summary>
/// The first element of the first row
/// </summary>
public float M11;
/// <summary>
/// The second element of the first row
/// </summary>
public float M12;
/// <summary>
/// The first element of the second row
/// </summary>
public float M21;
/// <summary>
/// The second element of the second row
/// </summary>
public float M22;
/// <summary>
/// The first element of the third row
/// </summary>
public float M31;
/// <summary>
/// The second element of the third row
/// </summary>
public float M32;
#endregion Public Fields
private static readonly Matrix3x2 _identity = new Matrix3x2
(
1f, 0f,
0f, 1f,
0f, 0f
);
/// <summary>
/// Returns the multiplicative identity matrix.
/// </summary>
public static Matrix3x2 Identity
{
get { return _identity; }
}
/// <summary>
/// Returns whether the matrix is the identity matrix.
/// </summary>
public bool IsIdentity
{
get
{
return M11 == 1f && M22 == 1f && // Check diagonal element first for early out.
M12 == 0f &&
M21 == 0f &&
M31 == 0f && M32 == 0f;
}
}
/// <summary>
/// Gets or sets the translation component of this matrix.
/// </summary>
public Vector2 Translation
{
get
{
return new Vector2(M31, M32);
}
set
{
M31 = value.X;
M32 = value.Y;
}
}
/// <summary>
/// Constructs a Matrix3x2 from the given components.
/// </summary>
public Matrix3x2(float m11, float m12,
float m21, float m22,
float m31, float m32)
{
this.M11 = m11;
this.M12 = m12;
this.M21 = m21;
this.M22 = m22;
this.M31 = m31;
this.M32 = m32;
}
/// <summary>
/// Creates a translation matrix from the given vector.
/// </summary>
/// <param name="position">The translation position.</param>
/// <returns>A translation matrix.</returns>
public static Matrix3x2 CreateTranslation(Vector2 position)
{
Matrix3x2 result;
result.M11 = 1.0f;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = 1.0f;
result.M31 = position.X;
result.M32 = position.Y;
return result;
}
/// <summary>
/// Creates a translation matrix from the given X and Y components.
/// </summary>
/// <param name="xPosition">The X position.</param>
/// <param name="yPosition">The Y position.</param>
/// <returns>A translation matrix.</returns>
public static Matrix3x2 CreateTranslation(float xPosition, float yPosition)
{
Matrix3x2 result;
result.M11 = 1.0f;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = 1.0f;
result.M31 = xPosition;
result.M32 = yPosition;
return result;
}
/// <summary>
/// Creates a scale matrix from the given X and Y components.
/// </summary>
/// <param name="xScale">Value to scale by on the X-axis.</param>
/// <param name="yScale">Value to scale by on the Y-axis.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float xScale, float yScale)
{
Matrix3x2 result;
result.M11 = xScale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = yScale;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a scale matrix that is offset by a given center point.
/// </summary>
/// <param name="xScale">Value to scale by on the X-axis.</param>
/// <param name="yScale">Value to scale by on the Y-axis.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float xScale, float yScale, Vector2 centerPoint)
{
Matrix3x2 result;
float tx = centerPoint.X * (1 - xScale);
float ty = centerPoint.Y * (1 - yScale);
result.M11 = xScale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = yScale;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a scale matrix from the given vector scale.
/// </summary>
/// <param name="scales">The scale to use.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Vector2 scales)
{
Matrix3x2 result;
result.M11 = scales.X;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scales.Y;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
/// </summary>
/// <param name="scales">The scale to use.</param>
/// <param name="centerPoint">The center offset.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(Vector2 scales, Vector2 centerPoint)
{
Matrix3x2 result;
float tx = centerPoint.X * (1 - scales.X);
float ty = centerPoint.Y * (1 - scales.Y);
result.M11 = scales.X;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scales.Y;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a scale matrix that scales uniformly with the given scale.
/// </summary>
/// <param name="scale">The uniform scale to use.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float scale)
{
Matrix3x2 result;
result.M11 = scale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scale;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
/// </summary>
/// <param name="scale">The uniform scale to use.</param>
/// <param name="centerPoint">The center offset.</param>
/// <returns>A scaling matrix.</returns>
public static Matrix3x2 CreateScale(float scale, Vector2 centerPoint)
{
Matrix3x2 result;
float tx = centerPoint.X * (1 - scale);
float ty = centerPoint.Y * (1 - scale);
result.M11 = scale;
result.M12 = 0.0f;
result.M21 = 0.0f;
result.M22 = scale;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a skew matrix from the given angles in radians.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>A skew matrix.</returns>
public static Matrix3x2 CreateSkew(float radiansX, float radiansY)
{
Matrix3x2 result;
float xTan = (float)System.Math.Tan(radiansX);
float yTan = (float)System.Math.Tan(radiansY);
result.M11 = 1.0f;
result.M12 = yTan;
result.M21 = xTan;
result.M22 = 1.0f;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a skew matrix from the given angles in radians and a center point.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A skew matrix.</returns>
public static Matrix3x2 CreateSkew(float radiansX, float radiansY, Vector2 centerPoint)
{
Matrix3x2 result;
float xTan = (float)System.Math.Tan(radiansX);
float yTan = (float)System.Math.Tan(radiansY);
float tx = -centerPoint.Y * xTan;
float ty = -centerPoint.X * yTan;
result.M11 = 1.0f;
result.M12 = yTan;
result.M21 = xTan;
result.M22 = 1.0f;
result.M31 = tx;
result.M32 = ty;
return result;
}
/// <summary>
/// Creates a rotation matrix using the given rotation in radians.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>A rotation matrix.</returns>
public static Matrix3x2 CreateRotation(float radians)
{
Matrix3x2 result;
radians = (float)System.Math.IEEERemainder(radians, System.Math.PI * 2);
float c, s;
const float epsilon = 0.001f * (float)System.Math.PI / 180f; // 0.1% of a degree
if (radians > -epsilon && radians < epsilon)
{
// Exact case for zero rotation.
c = 1;
s = 0;
}
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
{
// Exact case for 90 degree rotation.
c = 0;
s = 1;
}
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
{
// Exact case for 180 degree rotation.
c = -1;
s = 0;
}
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
{
// Exact case for 270 degree rotation.
c = 0;
s = -1;
}
else
{
// Arbitrary rotation.
c = (float)System.Math.Cos(radians);
s = (float)System.Math.Sin(radians);
}
// [ c s ]
// [ -s c ]
// [ 0 0 ]
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = 0.0f;
result.M32 = 0.0f;
return result;
}
/// <summary>
/// Creates a rotation matrix using the given rotation in radians and a center point.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="centerPoint">The center point.</param>
/// <returns>A rotation matrix.</returns>
public static Matrix3x2 CreateRotation(float radians, Vector2 centerPoint)
{
Matrix3x2 result;
radians = (float)System.Math.IEEERemainder(radians, System.Math.PI * 2);
float c, s;
const float epsilon = 0.001f * (float)System.Math.PI / 180f; // 0.1% of a degree
if (radians > -epsilon && radians < epsilon)
{
// Exact case for zero rotation.
c = 1;
s = 0;
}
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
{
// Exact case for 90 degree rotation.
c = 0;
s = 1;
}
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
{
// Exact case for 180 degree rotation.
c = -1;
s = 0;
}
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
{
// Exact case for 270 degree rotation.
c = 0;
s = -1;
}
else
{
// Arbitrary rotation.
c = (float)System.Math.Cos(radians);
s = (float)System.Math.Sin(radians);
}
float x = centerPoint.X * (1 - c) + centerPoint.Y * s;
float y = centerPoint.Y * (1 - c) - centerPoint.X * s;
// [ c s ]
// [ -s c ]
// [ x y ]
result.M11 = c;
result.M12 = s;
result.M21 = -s;
result.M22 = c;
result.M31 = x;
result.M32 = y;
return result;
}
/// <summary>
/// Calculates the determinant for this matrix.
/// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1).
/// </summary>
/// <returns>The determinant.</returns>
public float GetDeterminant()
{
// There isn't actually any such thing as a determinant for a non-square matrix,
// but this 3x2 type is really just an optimization of a 3x3 where we happen to
// know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
//
// [ M11, M12, 0 ]
// [ M21, M22, 0 ]
// [ M31, M32, 1 ]
//
// Sum the diagonal products:
// (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
//
// Subtract the opposite diagonal products:
// (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
//
// Collapse out the constants and oh look, this is just a 2x2 determinant!
return (M11 * M22) - (M21 * M12);
}
/// <summary>
/// Attempts to invert the given matrix. If the operation succeeds, the inverted matrix is stored in the result parameter.
/// </summary>
/// <param name="matrix">The source matrix.</param>
/// <param name="result">The output matrix.</param>
/// <returns>True if the operation succeeded, False otherwise.</returns>
public static bool Invert(Matrix3x2 matrix, out Matrix3x2 result)
{
float det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12);
if (System.Math.Abs(det) < float.Epsilon)
{
result = new Matrix3x2(float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN);
return false;
}
float invDet = 1.0f / det;
result.M11 = matrix.M22 * invDet;
result.M12 = -matrix.M12 * invDet;
result.M21 = -matrix.M21 * invDet;
result.M22 = matrix.M11 * invDet;
result.M31 = (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet;
result.M32 = (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet;
return true;
}
/// <summary>
/// Linearly interpolates from matrix1 to matrix2, based on the third parameter.
/// </summary>
/// <param name="matrix1">The first source matrix.</param>
/// <param name="matrix2">The second source matrix.</param>
/// <param name="amount">The relative weighting of matrix2.</param>
/// <returns>The interpolated matrix.</returns>
public static Matrix3x2 Lerp(Matrix3x2 matrix1, Matrix3x2 matrix2, float amount)
{
Matrix3x2 result;
// First row
result.M11 = matrix1.M11 + (matrix2.M11 - matrix1.M11) * amount;
result.M12 = matrix1.M12 + (matrix2.M12 - matrix1.M12) * amount;
// Second row
result.M21 = matrix1.M21 + (matrix2.M21 - matrix1.M21) * amount;
result.M22 = matrix1.M22 + (matrix2.M22 - matrix1.M22) * amount;
// Third row
result.M31 = matrix1.M31 + (matrix2.M31 - matrix1.M31) * amount;
result.M32 = matrix1.M32 + (matrix2.M32 - matrix1.M32) * amount;
return result;
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static Matrix3x2 Negate(Matrix3x2 value)
{
Matrix3x2 result;
result.M11 = -value.M11;
result.M12 = -value.M12;
result.M21 = -value.M21;
result.M22 = -value.M22;
result.M31 = -value.M31;
result.M32 = -value.M32;
return result;
}
/// <summary>
/// Adds each matrix element in value1 with its corresponding element in value2.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the summed values.</returns>
public static Matrix3x2 Add(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 + value2.M11;
result.M12 = value1.M12 + value2.M12;
result.M21 = value1.M21 + value2.M21;
result.M22 = value1.M22 + value2.M22;
result.M31 = value1.M31 + value2.M31;
result.M32 = value1.M32 + value2.M32;
return result;
}
/// <summary>
/// Subtracts each matrix element in value2 from its corresponding element in value1.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the resulting values.</returns>
public static Matrix3x2 Subtract(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
result.M11 = value1.M11 - value2.M11;
result.M12 = value1.M12 - value2.M12;
result.M21 = value1.M21 - value2.M21;
result.M22 = value1.M22 - value2.M22;
result.M31 = value1.M31 - value2.M31;
result.M32 = value1.M32 - value2.M32;
return result;
}
/// <summary>
/// Multiplies two matrices together and returns the resulting matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The product matrix.</returns>
public static Matrix3x2 Multiply(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 result;
// First row
result.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
result.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
result.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
result.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
// Third row
result.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
result.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
return result;
}
public Matrix4x4 ToMatrix4x4()
{
return new Matrix4x4(
M11, M12, 0, 0,
M21, M22, 0, 0,
0, 0, 1, 0,
M31, M32, 0, 1
);
}
/// <summary>
/// Scales all elements in a matrix by the given scalar factor.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling value to use.</param>
/// <returns>The resulting matrix.</returns>
public static Matrix3x2 Multiply(Matrix3x2 value1, float value2)
{
Matrix3x2 result;
result.M11 = value1.M11 * value2;
result.M12 = value1.M12 * value2;
result.M21 = value1.M21 * value2;
result.M22 = value1.M22 * value2;
result.M31 = value1.M31 * value2;
result.M32 = value1.M32 * value2;
return result;
}
/// <summary>
/// Negates the given matrix by multiplying all values by -1.
/// </summary>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static Matrix3x2 operator -(Matrix3x2 value)
{
Matrix3x2 m;
m.M11 = -value.M11;
m.M12 = -value.M12;
m.M21 = -value.M21;
m.M22 = -value.M22;
m.M31 = -value.M31;
m.M32 = -value.M32;
return m;
}
/// <summary>
/// Adds each matrix element in value1 with its corresponding element in value2.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the summed values.</returns>
public static Matrix3x2 operator +(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 + value2.M11;
m.M12 = value1.M12 + value2.M12;
m.M21 = value1.M21 + value2.M21;
m.M22 = value1.M22 + value2.M22;
m.M31 = value1.M31 + value2.M31;
m.M32 = value1.M32 + value2.M32;
return m;
}
/// <summary>
/// Subtracts each matrix element in value2 from its corresponding element in value1.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The matrix containing the resulting values.</returns>
public static Matrix3x2 operator -(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
m.M11 = value1.M11 - value2.M11;
m.M12 = value1.M12 - value2.M12;
m.M21 = value1.M21 - value2.M21;
m.M22 = value1.M22 - value2.M22;
m.M31 = value1.M31 - value2.M31;
m.M32 = value1.M32 - value2.M32;
return m;
}
/// <summary>
/// Multiplies two matrices together and returns the resulting matrix.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>The product matrix.</returns>
public static Matrix3x2 operator *(Matrix3x2 value1, Matrix3x2 value2)
{
Matrix3x2 m;
// First row
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
// Second row
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
// Third row
m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
return m;
}
/// <summary>
/// Scales all elements in a matrix by the given scalar factor.
/// </summary>
/// <param name="value1">The source matrix.</param>
/// <param name="value2">The scaling value to use.</param>
/// <returns>The resulting matrix.</returns>
public static Matrix3x2 operator *(Matrix3x2 value1, float value2)
{
Matrix3x2 m;
m.M11 = value1.M11 * value2;
m.M12 = value1.M12 * value2;
m.M21 = value1.M21 * value2;
m.M22 = value1.M22 * value2;
m.M31 = value1.M31 * value2;
m.M32 = value1.M32 * value2;
return m;
}
/// <summary>
/// Returns a boolean indicating whether the given matrices are equal.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>True if the matrices are equal; False otherwise.</returns>
public static bool operator ==(Matrix3x2 value1, Matrix3x2 value2)
{
return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && // Check diagonal element first for early out.
value1.M12 == value2.M12 &&
value1.M21 == value2.M21 &&
value1.M31 == value2.M31 && value1.M32 == value2.M32);
}
/// <summary>
/// Returns a boolean indicating whether the given matrices are not equal.
/// </summary>
/// <param name="value1">The first source matrix.</param>
/// <param name="value2">The second source matrix.</param>
/// <returns>True if the matrices are not equal; False if they are equal.</returns>
public static bool operator !=(Matrix3x2 value1, Matrix3x2 value2)
{
return (value1.M11 != value2.M11 || value1.M12 != value2.M12 ||
value1.M21 != value2.M21 || value1.M22 != value2.M22 ||
value1.M31 != value2.M31 || value1.M32 != value2.M32);
}
/// <summary>
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
/// </summary>
/// <param name="other">The other matrix to test equality against.</param>
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
public bool Equals(Matrix3x2 other)
{
return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out.
M12 == other.M12 &&
M21 == other.M21 &&
M31 == other.M31 && M32 == other.M32);
}
/// <summary>
/// Returns a boolean indicating whether the given Object is equal to this matrix instance.
/// </summary>
/// <param name="obj">The Object to compare against.</param>
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is Matrix3x2)
{
return Equals((Matrix3x2)obj);
}
return false;
}
/// <summary>
/// Returns a String representing this matrix instance.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
CultureInfo ci = CultureInfo.CurrentCulture;
return String.Format(ci, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}",
M11.ToString(ci), M12.ToString(ci),
M21.ToString(ci), M22.ToString(ci),
M31.ToString(ci), M32.ToString(ci));
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return M11.GetHashCode() + M12.GetHashCode() +
M21.GetHashCode() + M22.GetHashCode() +
M31.GetHashCode() + M32.GetHashCode();
}
}
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Represents the right-handed 4x4 floating point matrix, which can store translation, scale and rotation information.
@ -316,8 +316,7 @@ namespace MoonWorks.Math.Float
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44
)
{
) {
M11 = m11;
M12 = m12;
M13 = m13;
@ -351,8 +350,7 @@ namespace MoonWorks.Math.Float
out Vector3 scale,
out Quaternion rotation,
out Vector3 translation
)
{
) {
translation.X = M41;
translation.Y = M42;
translation.Z = M43;
@ -365,9 +363,9 @@ namespace MoonWorks.Math.Float
scale.Y = ys * (float) System.Math.Sqrt(M21 * M21 + M22 * M22 + M23 * M23);
scale.Z = zs * (float) System.Math.Sqrt(M31 * M31 + M32 * M32 + M33 * M33);
if (MathHelper.WithinEpsilon(scale.X, 0.0f) ||
if ( MathHelper.WithinEpsilon(scale.X, 0.0f) ||
MathHelper.WithinEpsilon(scale.Y, 0.0f) ||
MathHelper.WithinEpsilon(scale.Z, 0.0f))
MathHelper.WithinEpsilon(scale.Z, 0.0f) )
{
rotation = Quaternion.Identity;
return false;
@ -415,7 +413,7 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Matrix4x4 other)
{
return (M11 == other.M11 &&
return ( M11 == other.M11 &&
M12 == other.M12 &&
M13 == other.M13 &&
M14 == other.M14 &&
@ -430,7 +428,7 @@ namespace MoonWorks.Math.Float
M41 == other.M41 &&
M42 == other.M42 &&
M43 == other.M43 &&
M44 == other.M44);
M44 == other.M44 );
}
/// <summary>
@ -557,8 +555,7 @@ namespace MoonWorks.Math.Float
Vector3 cameraPosition,
Vector3 cameraUpVector,
Nullable<Vector3> cameraForwardVector
)
{
) {
Matrix4x4 result;
// Delegate to the other overload of the function to do the work
@ -587,8 +584,7 @@ namespace MoonWorks.Math.Float
ref Vector3 cameraUpVector,
Vector3? cameraForwardVector,
out Matrix4x4 result
)
{
) {
Vector3 vector;
Vector3 vector2;
Vector3 vector3;
@ -611,7 +607,7 @@ namespace MoonWorks.Math.Float
);
}
Vector3.Cross(ref cameraUpVector, ref vector, out vector3);
vector3 = Vector3.Normalize(vector3);
vector3.Normalize();
Vector3.Cross(ref vector, ref vector3, out vector2);
result.M11 = vector3.X;
result.M12 = vector3.Y;
@ -646,8 +642,7 @@ namespace MoonWorks.Math.Float
Vector3 rotateAxis,
Nullable<Vector3> cameraForwardVector,
Nullable<Vector3> objectForwardVector
)
{
) {
Matrix4x4 result;
CreateConstrainedBillboard(
ref objectPosition,
@ -676,8 +671,7 @@ namespace MoonWorks.Math.Float
Vector3? cameraForwardVector,
Vector3? objectForwardVector,
out Matrix4x4 result
)
{
) {
float num;
Vector3 vector;
Vector3 vector2;
@ -730,16 +724,16 @@ namespace MoonWorks.Math.Float
Vector3.Forward;
}
Vector3.Cross(ref rotateAxis, ref vector, out vector3);
vector3 = Vector3.Normalize(vector3);
vector3.Normalize();
Vector3.Cross(ref vector3, ref rotateAxis, out vector);
vector = Vector3.Normalize(vector);
vector.Normalize();
}
else
{
Vector3.Cross(ref rotateAxis, ref vector2, out vector3);
vector3 = Vector3.Normalize(vector3);
vector3.Normalize();
Vector3.Cross(ref vector3, ref vector4, out vector);
vector = Vector3.Normalize(vector);
vector.Normalize();
}
result.M11 = vector3.X;
@ -783,8 +777,7 @@ namespace MoonWorks.Math.Float
ref Vector3 axis,
float angle,
out Matrix4x4 result
)
{
) {
float x = axis.X;
float y = axis.Y;
float z = axis.Z;
@ -890,8 +883,7 @@ namespace MoonWorks.Math.Float
float pitch,
float roll,
out Matrix4x4 result
)
{
) {
Quaternion quaternion;
Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
CreateFromQuaternion(ref quaternion, out result);
@ -908,8 +900,7 @@ namespace MoonWorks.Math.Float
Vector3 cameraPosition,
Vector3 cameraTarget,
Vector3 cameraUpVector
)
{
) {
Matrix4x4 matrix;
CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector, out matrix);
return matrix;
@ -927,8 +918,7 @@ namespace MoonWorks.Math.Float
ref Vector3 cameraTarget,
ref Vector3 cameraUpVector,
out Matrix4x4 result
)
{
) {
Vector3 vectorA = Vector3.Normalize(cameraPosition - cameraTarget);
Vector3 vectorB = Vector3.Normalize(Vector3.Cross(cameraUpVector, vectorA));
Vector3 vectorC = Vector3.Cross(vectorA, vectorB);
@ -963,8 +953,7 @@ namespace MoonWorks.Math.Float
float height,
float zNearPlane,
float zFarPlane
)
{
) {
Matrix4x4 matrix;
CreateOrthographic(width, height, zNearPlane, zFarPlane, out matrix);
return matrix;
@ -984,8 +973,7 @@ namespace MoonWorks.Math.Float
float zNearPlane,
float zFarPlane,
out Matrix4x4 result
)
{
) {
result.M11 = 2f / width;
result.M12 = result.M13 = result.M14 = 0f;
result.M22 = 2f / height;
@ -1014,8 +1002,7 @@ namespace MoonWorks.Math.Float
float top,
float zNearPlane,
float zFarPlane
)
{
) {
Matrix4x4 matrix;
CreateOrthographicOffCenter(
left,
@ -1047,8 +1034,7 @@ namespace MoonWorks.Math.Float
float zNearPlane,
float zFarPlane,
out Matrix4x4 result
)
{
) {
result.M11 = (float) (2.0 / ((double) right - (double) left));
result.M12 = 0.0f;
result.M13 = 0.0f;
@ -1089,8 +1075,7 @@ namespace MoonWorks.Math.Float
float height,
float nearPlaneDistance,
float farPlaneDistance
)
{
) {
Matrix4x4 matrix;
CreatePerspective(width, height, nearPlaneDistance, farPlaneDistance, out matrix);
return matrix;
@ -1110,8 +1095,7 @@ namespace MoonWorks.Math.Float
float nearPlaneDistance,
float farPlaneDistance,
out Matrix4x4 result
)
{
) {
if (nearPlaneDistance <= 0f)
{
throw new ArgumentException("nearPlaneDistance <= 0");
@ -1151,8 +1135,7 @@ namespace MoonWorks.Math.Float
float aspectRatio,
float nearPlaneDistance,
float farPlaneDistance
)
{
) {
Matrix4x4 result;
CreatePerspectiveFieldOfView(
fieldOfView,
@ -1178,8 +1161,7 @@ namespace MoonWorks.Math.Float
float nearPlaneDistance,
float farPlaneDistance,
out Matrix4x4 result
)
{
) {
if ((fieldOfView <= 0f) || (fieldOfView >= 3.141593f))
{
throw new ArgumentException("fieldOfView <= 0 or >= PI");
@ -1228,8 +1210,7 @@ namespace MoonWorks.Math.Float
float top,
float nearPlaneDistance,
float farPlaneDistance
)
{
) {
Matrix4x4 result;
CreatePerspectiveOffCenter(
left,
@ -1261,8 +1242,7 @@ namespace MoonWorks.Math.Float
float nearPlaneDistance,
float farPlaneDistance,
out Matrix4x4 result
)
{
) {
if (nearPlaneDistance <= 0f)
{
throw new ArgumentException("nearPlaneDistance <= 0");
@ -1428,8 +1408,7 @@ namespace MoonWorks.Math.Float
float yScale,
float zScale,
out Matrix4x4 result
)
{
) {
result.M11 = xScale;
result.M12 = 0;
result.M13 = 0;
@ -1548,8 +1527,7 @@ namespace MoonWorks.Math.Float
float xPosition,
float yPosition,
float zPosition
)
{
) {
Matrix4x4 result;
CreateTranslation(xPosition, yPosition, zPosition, out result);
return result;
@ -1604,8 +1582,7 @@ namespace MoonWorks.Math.Float
float yPosition,
float zPosition,
out Matrix4x4 result
)
{
) {
result.M11 = 1;
result.M12 = 0;
result.M13 = 0;
@ -1695,14 +1672,13 @@ namespace MoonWorks.Math.Float
ref Vector3 forward,
ref Vector3 up,
out Matrix4x4 result
)
{
) {
Vector3 x, y, z;
Vector3.Normalize(ref forward, out z);
Vector3.Cross(ref forward, ref up, out x);
Vector3.Cross(ref x, ref forward, out y);
x = Vector3.Normalize(x);
y = Vector3.Normalize(y);
x.Normalize();
y.Normalize();
result = new Matrix4x4();
result.Right = x;
@ -2093,8 +2069,7 @@ namespace MoonWorks.Math.Float
ref Matrix4x4 matrix2,
float amount,
out Matrix4x4 result
)
{
) {
result.M11 = matrix1.M11 + ((matrix2.M11 - matrix1.M11) * amount);
result.M12 = matrix1.M12 + ((matrix2.M12 - matrix1.M12) * amount);
result.M13 = matrix1.M13 + ((matrix2.M13 - matrix1.M13) * amount);
@ -2122,8 +2097,7 @@ namespace MoonWorks.Math.Float
public static Matrix4x4 Multiply(
Matrix4x4 matrix1,
Matrix4x4 matrix2
)
{
) {
float m11 = (
(matrix1.M11 * matrix2.M11) +
(matrix1.M12 * matrix2.M21) +
@ -2574,8 +2548,7 @@ namespace MoonWorks.Math.Float
ref Matrix4x4 value,
ref Quaternion rotation,
out Matrix4x4 result
)
{
) {
Matrix4x4 rotMatrix = CreateFromQuaternion(rotation);
Multiply(ref value, ref rotMatrix, out result);
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]
@ -233,8 +233,7 @@ namespace MoonWorks.Math.Float
ref Plane plane,
ref Matrix4x4 matrix,
out Plane result
)
{
) {
/* See "Transforming Normals" in
* http://www.glprogramming.com/red/appendixf.html
* for an explanation of how this works.
@ -278,8 +277,7 @@ namespace MoonWorks.Math.Float
ref Plane plane,
ref Quaternion rotation,
out Plane result
)
{
) {
Vector3.Transform(
ref plane.Normal,
ref rotation,

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -14,7 +14,7 @@
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Defines the intersection between a <see cref="Plane"/> and a bounding volume.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Describes a 2D-point.

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// An efficient mathematical representation for three dimensional rotations.
@ -157,10 +157,10 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Quaternion other)
{
return (X == other.X &&
return ( X == other.X &&
Y == other.Y &&
Z == other.Z &&
W == other.W);
W == other.W );
}
/// <summary>
@ -266,8 +266,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
) {
result.X = quaternion1.X + quaternion2.X;
result.Y = quaternion1.Y + quaternion2.Y;
result.Z = quaternion1.Z + quaternion2.Z;
@ -297,8 +296,7 @@ namespace MoonWorks.Math.Float
ref Quaternion value1,
ref Quaternion value2,
out Quaternion result
)
{
) {
float x1 = value1.X;
float y1 = value1.Y;
float z1 = value1.Z;
@ -361,8 +359,7 @@ namespace MoonWorks.Math.Float
ref Vector3 axis,
float angle,
out Quaternion result
)
{
) {
float half = angle * 0.5f;
float sin = (float) System.Math.Sin((double) half);
float cos = (float) System.Math.Cos((double) half);
@ -418,12 +415,12 @@ namespace MoonWorks.Math.Float
else if (matrix.M22 > matrix.M33)
{
sqrt = (float) System.Math.Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33);
half = 0.5f / sqrt;
half = 0.5f/sqrt;
result.X = (matrix.M21 + matrix.M12) * half;
result.Y = 0.5f * sqrt;
result.Z = (matrix.M32 + matrix.M23) * half;
result.W = (matrix.M31 - matrix.M13) * half;
result.X = (matrix.M21 + matrix.M12)*half;
result.Y = 0.5f*sqrt;
result.Z = (matrix.M32 + matrix.M23)*half;
result.W = (matrix.M31 - matrix.M13)*half;
}
else
{
@ -502,8 +499,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
) {
float x = quaternion1.X;
float y = quaternion1.Y;
float z = quaternion1.Z;
@ -555,8 +551,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out float result
)
{
) {
result = (
(quaternion1.X * quaternion2.X) +
(quaternion1.Y * quaternion2.Y) +
@ -608,8 +603,7 @@ namespace MoonWorks.Math.Float
Quaternion quaternion1,
Quaternion quaternion2,
float amount
)
{
) {
Quaternion quaternion;
Lerp(ref quaternion1, ref quaternion2, amount, out quaternion);
return quaternion;
@ -627,8 +621,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion2,
float amount,
out Quaternion result
)
{
) {
float num = amount;
float num2 = 1f - num;
float num5 = (
@ -675,8 +668,7 @@ namespace MoonWorks.Math.Float
Quaternion quaternion1,
Quaternion quaternion2,
float amount
)
{
) {
Quaternion quaternion;
Slerp(ref quaternion1, ref quaternion2, amount, out quaternion);
return quaternion;
@ -694,8 +686,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion2,
float amount,
out Quaternion result
)
{
) {
float num2;
float num3;
float num = amount;
@ -752,8 +743,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
) {
result.X = quaternion1.X - quaternion2.X;
result.Y = quaternion1.Y - quaternion2.Y;
result.Z = quaternion1.Z - quaternion2.Z;
@ -796,8 +786,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion1,
ref Quaternion quaternion2,
out Quaternion result
)
{
) {
float x = quaternion1.X;
float y = quaternion1.Y;
float z = quaternion1.Z;
@ -826,8 +815,7 @@ namespace MoonWorks.Math.Float
ref Quaternion quaternion1,
float scaleFactor,
out Quaternion result
)
{
) {
result.X = quaternion1.X * scaleFactor;
result.Y = quaternion1.Y * scaleFactor;
result.Z = quaternion1.Z * scaleFactor;
@ -893,15 +881,15 @@ namespace MoonWorks.Math.Float
result.W = quaternion.W * num;
}
public static Quaternion LookAt(in Vector3 forward, in Vector3 up)
{
Matrix4x4 orientation = Matrix4x4.Identity;
orientation.Forward = forward;
orientation.Right = Vector3.Normalize(Vector3.Cross(forward, up));
orientation.Up = Vector3.Cross(orientation.Right, forward);
public static Quaternion LookAt(in Vector3 forward, in Vector3 up)
{
Matrix4x4 orientation = Matrix4x4.Identity;
orientation.Forward = forward;
orientation.Right = Vector3.Normalize(Vector3.Cross(forward, up));
orientation.Up = Vector3.Cross(orientation.Right, forward);
return Quaternion.CreateFromRotationMatrix(orientation);
}
return Quaternion.CreateFromRotationMatrix(orientation);
}
#endregion

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]
@ -70,8 +70,8 @@ namespace MoonWorks.Math.Float
public bool Equals(Ray other)
{
return (this.Position.Equals(other.Position) &&
this.Direction.Equals(other.Direction));
return ( this.Position.Equals(other.Position) &&
this.Direction.Equals(other.Direction) );
}
@ -124,8 +124,8 @@ namespace MoonWorks.Math.Float
tMaxY = temp;
}
if ((tMin.HasValue && tMin > tMaxY) ||
(tMax.HasValue && tMinY > tMax))
if ( (tMin.HasValue && tMin > tMaxY) ||
(tMax.HasValue && tMinY > tMax) )
{
return null;
}
@ -153,8 +153,8 @@ namespace MoonWorks.Math.Float
tMaxZ = temp;
}
if ((tMin.HasValue && tMin > tMaxZ) ||
(tMax.HasValue && tMinZ > tMax))
if ( (tMin.HasValue && tMin > tMaxZ) ||
(tMax.HasValue && tMinZ > tMax) )
{
return null;
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Describes a 2D-rectangle.
@ -117,10 +117,10 @@ namespace MoonWorks.Math.Float
{
get
{
return ((Width == 0) &&
return ( (Width == 0) &&
(Height == 0) &&
(X == 0) &&
(Y == 0));
(Y == 0) );
}
}
@ -218,10 +218,10 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the provided coordinates lie inside this <see cref="Rectangle"/>. <c>false</c> otherwise.</returns>
public bool Contains(int x, int y)
{
return ((this.X <= x) &&
return ( (this.X <= x) &&
(x < (this.X + this.Width)) &&
(this.Y <= y) &&
(y < (this.Y + this.Height)));
(y < (this.Y + this.Height)) );
}
/// <summary>
@ -231,10 +231,10 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the provided <see cref="Point"/> lies inside this <see cref="Rectangle"/>. <c>false</c> otherwise.</returns>
public bool Contains(Point value)
{
return ((this.X <= value.X) &&
return ( (this.X <= value.X) &&
(value.X < (this.X + this.Width)) &&
(this.Y <= value.Y) &&
(value.Y < (this.Y + this.Height)));
(value.Y < (this.Y + this.Height)) );
}
/// <summary>
@ -244,26 +244,26 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the provided <see cref="Rectangle"/>'s bounds lie entirely inside this <see cref="Rectangle"/>. <c>false</c> otherwise.</returns>
public bool Contains(Rectangle value)
{
return ((this.X <= value.X) &&
return ( (this.X <= value.X) &&
((value.X + value.Width) <= (this.X + this.Width)) &&
(this.Y <= value.Y) &&
((value.Y + value.Height) <= (this.Y + this.Height)));
((value.Y + value.Height) <= (this.Y + this.Height)) );
}
public void Contains(ref Point value, out bool result)
{
result = ((this.X <= value.X) &&
result = ( (this.X <= value.X) &&
(value.X < (this.X + this.Width)) &&
(this.Y <= value.Y) &&
(value.Y < (this.Y + this.Height)));
(value.Y < (this.Y + this.Height)) );
}
public void Contains(ref Rectangle value, out bool result)
{
result = ((this.X <= value.X) &&
result = ( (this.X <= value.X) &&
((value.X + value.Width) <= (this.X + this.Width)) &&
(this.Y <= value.Y) &&
((value.Y + value.Height) <= (this.Y + this.Height)));
((value.Y + value.Height) <= (this.Y + this.Height)) );
}
/// <summary>
@ -349,10 +349,10 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if other <see cref="Rectangle"/> intersects with this rectangle; <c>false</c> otherwise.</returns>
public bool Intersects(Rectangle value)
{
return (value.Left < Right &&
return ( value.Left < Right &&
Left < value.Right &&
value.Top < Bottom &&
Top < value.Bottom);
Top < value.Bottom );
}
/// <summary>
@ -362,10 +362,10 @@ namespace MoonWorks.Math.Float
/// <param name="result"><c>true</c> if other <see cref="Rectangle"/> intersects with this rectangle; <c>false</c> otherwise. As an output parameter.</param>
public void Intersects(ref Rectangle value, out bool result)
{
result = (value.Left < Right &&
result = ( value.Left < Right &&
Left < value.Right &&
value.Top < Bottom &&
Top < value.Bottom);
Top < value.Bottom );
}
#endregion
@ -374,10 +374,10 @@ namespace MoonWorks.Math.Float
public static bool operator ==(Rectangle a, Rectangle b)
{
return ((a.X == b.X) &&
return ( (a.X == b.X) &&
(a.Y == b.Y) &&
(a.Width == b.Width) &&
(a.Height == b.Height));
(a.Height == b.Height) );
}
public static bool operator !=(Rectangle a, Rectangle b)
@ -396,8 +396,7 @@ namespace MoonWorks.Math.Float
ref Rectangle value1,
ref Rectangle value2,
out Rectangle result
)
{
) {
if (value1.Intersects(value2))
{
int right_side = System.Math.Min(

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Describes a 2D-vector.
@ -163,8 +163,8 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Vector2 other)
{
return (X == other.X &&
Y == other.Y);
return ( X == other.X &&
Y == other.Y );
}
/// <summary>
@ -195,11 +195,13 @@ namespace MoonWorks.Math.Float
}
/// <summary>
/// Turns this <see cref="Vector2"/> to an angle in radians.
/// Turns this <see cref="Vector2"/> to a unit vector with the same direction.
/// </summary>
public float Angle()
public void Normalize()
{
return MathF.Atan2(Y, X);
float val = 1.0f / (float) System.Math.Sqrt((X * X) + (Y * Y));
X *= val;
Y *= val;
}
/// <summary>
@ -262,8 +264,7 @@ namespace MoonWorks.Math.Float
Vector2 value3,
float amount1,
float amount2
)
{
) {
return new Vector2(
MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2),
MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2)
@ -286,8 +287,7 @@ namespace MoonWorks.Math.Float
float amount1,
float amount2,
out Vector2 result
)
{
) {
result.X = MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2);
result.Y = MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2);
}
@ -307,8 +307,7 @@ namespace MoonWorks.Math.Float
Vector2 value3,
Vector2 value4,
float amount
)
{
) {
return new Vector2(
MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount),
MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount)
@ -331,8 +330,7 @@ namespace MoonWorks.Math.Float
ref Vector2 value4,
float amount,
out Vector2 result
)
{
) {
result.X = MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount);
result.Y = MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount);
}
@ -364,8 +362,7 @@ namespace MoonWorks.Math.Float
ref Vector2 min,
ref Vector2 max,
out Vector2 result
)
{
) {
result.X = MathHelper.Clamp(value1.X, min.X, max.X);
result.Y = MathHelper.Clamp(value1.Y, min.Y, max.Y);
}
@ -416,8 +413,7 @@ namespace MoonWorks.Math.Float
ref Vector2 value1,
ref Vector2 value2,
out float result
)
{
) {
float v1 = value1.X - value2.X, v2 = value1.Y - value2.Y;
result = (v1 * v1) + (v2 * v2);
}
@ -511,8 +507,7 @@ namespace MoonWorks.Math.Float
Vector2 value2,
Vector2 tangent2,
float amount
)
{
) {
Vector2 result = new Vector2();
Hermite(ref value1, ref tangent1, ref value2, ref tangent2, amount, out result);
return result;
@ -534,8 +529,7 @@ namespace MoonWorks.Math.Float
ref Vector2 tangent2,
float amount,
out Vector2 result
)
{
) {
result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount);
result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount);
}
@ -567,8 +561,7 @@ namespace MoonWorks.Math.Float
ref Vector2 value2,
float amount,
out Vector2 result
)
{
) {
result.X = MathHelper.Lerp(value1.X, value2.X, amount);
result.Y = MathHelper.Lerp(value1.Y, value2.Y, amount);
}
@ -707,14 +700,7 @@ namespace MoonWorks.Math.Float
/// <returns>Unit vector.</returns>
public static Vector2 Normalize(Vector2 value)
{
float lengthSquared = (value.X * value.X) + (value.Y * value.Y);
if (lengthSquared == 0)
{
return Zero;
}
float val = 1.0f / System.MathF.Sqrt(lengthSquared);
float val = 1.0f / (float) System.Math.Sqrt((value.X * value.X) + (value.Y * value.Y));
value.X *= val;
value.Y *= val;
return value;
@ -787,8 +773,7 @@ namespace MoonWorks.Math.Float
ref Vector2 value2,
float amount,
out Vector2 result
)
{
) {
result.X = MathHelper.SmoothStep(value1.X, value2.X, amount);
result.Y = MathHelper.SmoothStep(value1.Y, value2.Y, amount);
}
@ -842,8 +827,7 @@ namespace MoonWorks.Math.Float
ref Vector2 position,
ref Matrix4x4 matrix,
out Vector2 result
)
{
) {
float x = (position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M41;
float y = (position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M42;
result.X = x;
@ -872,8 +856,7 @@ namespace MoonWorks.Math.Float
ref Vector2 value,
ref Quaternion rotation,
out Vector2 result
)
{
) {
float x = 2 * -(rotation.Z * value.Y);
float y = 2 * (rotation.Z * value.X);
float z = 2 * (rotation.X * value.Y - rotation.Y * value.X);
@ -882,20 +865,6 @@ namespace MoonWorks.Math.Float
result.Y = value.Y + y * rotation.W + (rotation.Z * x - rotation.X * z);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Matrix3x2"/>.
/// </summary>
/// <param name="position">Source <see cref="Vector2"/>.</param>
/// <param name="matrix">The transformation <see cref="Matrix3x2"/>.</param>
/// <returns>Transformed <see cref="Vector2"/>.</returns>
public static Vector2 Transform(Vector2 position, Matrix3x2 matrix)
{
return new Vector2(
(position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M31,
(position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M32
);
}
/// <summary>
/// Apply transformation on all vectors within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
/// </summary>
@ -906,8 +875,7 @@ namespace MoonWorks.Math.Float
Vector2[] sourceArray,
ref Matrix4x4 matrix,
Vector2[] destinationArray
)
{
) {
Transform(sourceArray, 0, ref matrix, destinationArray, 0, sourceArray.Length);
}
@ -927,8 +895,7 @@ namespace MoonWorks.Math.Float
Vector2[] destinationArray,
int destinationIndex,
int length
)
{
) {
for (int x = 0; x < length; x += 1)
{
Vector2 position = sourceArray[sourceIndex + x];
@ -951,8 +918,7 @@ namespace MoonWorks.Math.Float
Vector2[] sourceArray,
ref Quaternion rotation,
Vector2[] destinationArray
)
{
) {
Transform(
sourceArray,
0,
@ -979,8 +945,7 @@ namespace MoonWorks.Math.Float
Vector2[] destinationArray,
int destinationIndex,
int length
)
{
) {
for (int i = 0; i < length; i += 1)
{
Vector2 position = sourceArray[sourceIndex + i];
@ -1004,19 +969,6 @@ namespace MoonWorks.Math.Float
);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix3x2"/>.
/// </summary>
/// <param name="normal">Source <see cref="Vector2"/> which represents a normal vector.</param>
/// <param name="matrix">The transformation <see cref="Matrix3x2"/>.</param>
/// <returns>Transformed normal.</returns>
public static Vector2 TransformNormal(Vector2 normal, Matrix3x2 matrix)
{
return new Vector2(
normal.X * matrix.M11 + normal.Y * matrix.M21,
normal.X * matrix.M12 + normal.Y * matrix.M22);
}
/// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix4x4"/>.
/// </summary>
@ -1027,8 +979,7 @@ namespace MoonWorks.Math.Float
ref Vector2 normal,
ref Matrix4x4 matrix,
out Vector2 result
)
{
) {
float x = (normal.X * matrix.M11) + (normal.Y * matrix.M21);
float y = (normal.X * matrix.M12) + (normal.Y * matrix.M22);
result.X = x;
@ -1045,8 +996,7 @@ namespace MoonWorks.Math.Float
Vector2[] sourceArray,
ref Matrix4x4 matrix,
Vector2[] destinationArray
)
{
) {
TransformNormal(
sourceArray,
0,
@ -1073,8 +1023,7 @@ namespace MoonWorks.Math.Float
Vector2[] destinationArray,
int destinationIndex,
int length
)
{
) {
for (int i = 0; i < length; i += 1)
{
Vector2 position = sourceArray[sourceIndex + i];
@ -1085,19 +1034,6 @@ namespace MoonWorks.Math.Float
}
}
/// <summary>
/// Rotates a Vector2 by an angle.
/// </summary>
/// <param name="vector">The vector to rotate.</param>
/// <param name="angle">The angle in radians.</param>
public static Vector2 Rotate(Vector2 vector, float angle)
{
return new Vector2(
vector.X * (float) System.Math.Cos(angle) - vector.Y * (float) System.Math.Sin(angle),
vector.X * (float) System.Math.Sin(angle) + vector.Y * (float) System.Math.Cos(angle)
);
}
#endregion
#region Public Static Operators
@ -1122,8 +1058,8 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(Vector2 value1, Vector2 value2)
{
return (value1.X == value2.X &&
value1.Y == value2.Y);
return ( value1.X == value2.X &&
value1.Y == value2.Y );
}
/// <summary>

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -22,7 +22,7 @@ using System.Text;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Describes a 3D-vector.
@ -270,9 +270,9 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Vector3 other)
{
return (X == other.X &&
return ( X == other.X &&
Y == other.Y &&
Z == other.Z);
Z == other.Z );
}
/// <summary>
@ -302,6 +302,21 @@ namespace MoonWorks.Math.Float
return (X * X) + (Y * Y) + (Z * Z);
}
/// <summary>
/// Turns this <see cref="Vector3"/> to a unit vector with the same direction.
/// </summary>
public void Normalize()
{
float factor = 1.0f / (float) System.Math.Sqrt(
(X * X) +
(Y * Y) +
(Z * Z)
);
X *= factor;
Y *= factor;
Z *= factor;
}
/// <summary>
/// Returns a <see cref="String"/> representation of this <see cref="Vector3"/> in the format:
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>]}
@ -368,8 +383,7 @@ namespace MoonWorks.Math.Float
Vector3 value3,
float amount1,
float amount2
)
{
) {
return new Vector3(
MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2),
MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2),
@ -393,8 +407,7 @@ namespace MoonWorks.Math.Float
float amount1,
float amount2,
out Vector3 result
)
{
) {
result.X = MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2);
result.Y = MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2);
result.Z = MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2);
@ -415,8 +428,7 @@ namespace MoonWorks.Math.Float
Vector3 value3,
Vector3 value4,
float amount
)
{
) {
return new Vector3(
MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount),
MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount),
@ -440,8 +452,7 @@ namespace MoonWorks.Math.Float
ref Vector3 value4,
float amount,
out Vector3 result
)
{
) {
result.X = MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount);
result.Y = MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount);
result.Z = MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount);
@ -475,26 +486,24 @@ namespace MoonWorks.Math.Float
ref Vector3 min,
ref Vector3 max,
out Vector3 result
)
{
) {
result.X = MathHelper.Clamp(value1.X, min.X, max.X);
result.Y = MathHelper.Clamp(value1.Y, min.Y, max.Y);
result.Z = MathHelper.Clamp(value1.Z, min.Z, max.Z);
}
/// <summary>
/// Clamps the magnitude of the specified vector.
/// </summary>
/// <param name="value">The vector to clamp.</param>
/// <param name="maxLength">The maximum length of the vector.</param>
/// <returns></returns>
/// Clamps the magnitude of the specified vector.
/// </summary>
/// <param name="value">The vector to clamp.</param>
/// <param name="maxLength">The maximum length of the vector.</param>
/// <returns></returns>
public static Vector3 ClampMagnitude(
Vector3 value,
float maxLength
)
{
return (value.LengthSquared() > maxLength * maxLength) ? (Vector3.Normalize(value) * maxLength) : value;
}
) {
return (value.LengthSquared() > maxLength * maxLength) ? (Vector3.Normalize(value) * maxLength) : value;
}
/// <summary>
/// Computes the cross product of two vectors.
@ -574,8 +583,7 @@ namespace MoonWorks.Math.Float
ref Vector3 value1,
ref Vector3 value2,
out float result
)
{
) {
result = (
(value1.X - value2.X) * (value1.X - value2.X) +
(value1.Y - value2.Y) * (value1.Y - value2.Y) +
@ -680,8 +688,7 @@ namespace MoonWorks.Math.Float
Vector3 value2,
Vector3 tangent2,
float amount
)
{
) {
Vector3 result = new Vector3();
Hermite(ref value1, ref tangent1, ref value2, ref tangent2, amount, out result);
return result;
@ -703,8 +710,7 @@ namespace MoonWorks.Math.Float
ref Vector3 tangent2,
float amount,
out Vector3 result
)
{
) {
result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount);
result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount);
result.Z = MathHelper.Hermite(value1.Z, tangent1.Z, value2.Z, tangent2.Z, amount);
@ -738,8 +744,7 @@ namespace MoonWorks.Math.Float
ref Vector3 value2,
float amount,
out Vector3 result
)
{
) {
result.X = MathHelper.Lerp(value1.X, value2.X, amount);
result.Y = MathHelper.Lerp(value1.Y, value2.Y, amount);
result.Z = MathHelper.Lerp(value1.Z, value2.Z, amount);
@ -885,14 +890,11 @@ namespace MoonWorks.Math.Float
/// <returns>Unit vector.</returns>
public static Vector3 Normalize(Vector3 value)
{
float lengthSquared = (value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z);
if (lengthSquared == 0f)
{
return Zero;
}
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
float factor = 1.0f / (float) System.Math.Sqrt(
(value.X * value.X) +
(value.Y * value.Y) +
(value.Z * value.Z)
);
return new Vector3(
value.X * factor,
value.Y * factor,
@ -990,8 +992,7 @@ namespace MoonWorks.Math.Float
ref Vector3 value2,
float amount,
out Vector3 result
)
{
) {
result.X = MathHelper.SmoothStep(value1.X, value2.X, amount);
result.Y = MathHelper.SmoothStep(value1.Y, value2.Y, amount);
result.Z = MathHelper.SmoothStep(value1.Z, value2.Z, amount);
@ -1046,8 +1047,7 @@ namespace MoonWorks.Math.Float
ref Vector3 position,
ref Matrix4x4 matrix,
out Vector3 result
)
{
) {
float x = (
(position.X * matrix.M11) +
(position.Y * matrix.M21) +
@ -1081,8 +1081,7 @@ namespace MoonWorks.Math.Float
Vector3[] sourceArray,
ref Matrix4x4 matrix,
Vector3[] destinationArray
)
{
) {
Debug.Assert(
destinationArray.Length >= sourceArray.Length,
"The destination array is smaller than the source array."
@ -1122,8 +1121,7 @@ namespace MoonWorks.Math.Float
Vector3[] destinationArray,
int destinationIndex,
int length
)
{
) {
Debug.Assert(
sourceArray.Length - sourceIndex >= length,
"The source array is too small for the given sourceIndex and length."
@ -1175,8 +1173,7 @@ namespace MoonWorks.Math.Float
ref Vector3 value,
ref Quaternion rotation,
out Vector3 result
)
{
) {
float x = 2 * (rotation.Y * value.Z - rotation.Z * value.Y);
float y = 2 * (rotation.Z * value.X - rotation.X * value.Z);
float z = 2 * (rotation.X * value.Y - rotation.Y * value.X);
@ -1196,8 +1193,7 @@ namespace MoonWorks.Math.Float
Vector3[] sourceArray,
ref Quaternion rotation,
Vector3[] destinationArray
)
{
) {
Debug.Assert(
destinationArray.Length >= sourceArray.Length,
"The destination array is smaller than the source array."
@ -1240,8 +1236,7 @@ namespace MoonWorks.Math.Float
Vector3[] destinationArray,
int destinationIndex,
int length
)
{
) {
Debug.Assert(
sourceArray.Length - sourceIndex >= length,
"The source array is too small for the given sourceIndex and length."
@ -1294,8 +1289,7 @@ namespace MoonWorks.Math.Float
ref Vector3 normal,
ref Matrix4x4 matrix,
out Vector3 result
)
{
) {
float x = (normal.X * matrix.M11) + (normal.Y * matrix.M21) + (normal.Z * matrix.M31);
float y = (normal.X * matrix.M12) + (normal.Y * matrix.M22) + (normal.Z * matrix.M32);
float z = (normal.X * matrix.M13) + (normal.Y * matrix.M23) + (normal.Z * matrix.M33);
@ -1314,8 +1308,7 @@ namespace MoonWorks.Math.Float
Vector3[] sourceArray,
ref Matrix4x4 matrix,
Vector3[] destinationArray
)
{
) {
Debug.Assert(
destinationArray.Length >= sourceArray.Length,
"The destination array is smaller than the source array."
@ -1346,8 +1339,7 @@ namespace MoonWorks.Math.Float
Vector3[] destinationArray,
int destinationIndex,
int length
)
{
) {
if (sourceArray == null)
{
throw new ArgumentNullException("sourceArray");
@ -1404,9 +1396,9 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public static bool operator ==(Vector3 value1, Vector3 value2)
{
return (value1.X == value2.X &&
return ( value1.X == value2.X &&
value1.Y == value2.Y &&
value1.Z == value2.Z);
value1.Z == value2.Z );
}
/// <summary>

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math.Float
namespace MoonWorks.Math
{
/// <summary>
/// Describes a 4D-vector.
@ -234,10 +234,10 @@ namespace MoonWorks.Math.Float
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
public bool Equals(Vector4 other)
{
return (X == other.X &&
return ( X == other.X &&
Y == other.Y &&
Z == other.Z &&
W == other.W);
W == other.W );
}
/// <summary>
@ -267,6 +267,23 @@ namespace MoonWorks.Math.Float
return (X * X) + (Y * Y) + (Z * Z) + (W * W);
}
/// <summary>
/// Turns this <see cref="Vector4"/> to a unit vector with the same direction.
/// </summary>
public void Normalize()
{
float factor = 1.0f / (float) System.Math.Sqrt(
(X * X) +
(Y * Y) +
(Z * Z) +
(W * W)
);
X *= factor;
Y *= factor;
Z *= factor;
W *= factor;
}
public override string ToString()
{
return (
@ -327,8 +344,7 @@ namespace MoonWorks.Math.Float
Vector4 value3,
float amount1,
float amount2
)
{
) {
return new Vector4(
MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2),
MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2),
@ -353,8 +369,7 @@ namespace MoonWorks.Math.Float
float amount1,
float amount2,
out Vector4 result
)
{
) {
result.X = MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2);
result.Y = MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2);
result.Z = MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2);
@ -376,8 +391,7 @@ namespace MoonWorks.Math.Float
Vector4 value3,
Vector4 value4,
float amount
)
{
) {
return new Vector4(
MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount),
MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount),
@ -402,8 +416,7 @@ namespace MoonWorks.Math.Float
ref Vector4 value4,
float amount,
out Vector4 result
)
{
) {
result.X = MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount);
result.Y = MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount);
result.Z = MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount);
@ -439,8 +452,7 @@ namespace MoonWorks.Math.Float
ref Vector4 min,
ref Vector4 max,
out Vector4 result
)
{
) {
result.X = MathHelper.Clamp(value1.X, min.X, max.X);
result.Y = MathHelper.Clamp(value1.Y, min.Y, max.Y);
result.Z = MathHelper.Clamp(value1.Z, min.Z, max.Z);
@ -495,8 +507,7 @@ namespace MoonWorks.Math.Float
ref Vector4 value1,
ref Vector4 value2,
out float result
)
{
) {
result = (
(value1.W - value2.W) * (value1.W - value2.W) +
(value1.X - value2.X) * (value1.X - value2.X) +
@ -561,8 +572,7 @@ namespace MoonWorks.Math.Float
ref Vector4 value1,
ref Vector4 value2,
out Vector4 result
)
{
) {
result.W = value1.W / value2.W;
result.X = value1.X / value2.X;
result.Y = value1.Y / value2.Y;
@ -616,8 +626,7 @@ namespace MoonWorks.Math.Float
Vector4 value2,
Vector4 tangent2,
float amount
)
{
) {
return new Vector4(
MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount),
MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount),
@ -642,8 +651,7 @@ namespace MoonWorks.Math.Float
ref Vector4 tangent2,
float amount,
out Vector4 result
)
{
) {
result.W = MathHelper.Hermite(value1.W, tangent1.W, value2.W, tangent2.W, amount);
result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount);
result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount);
@ -679,8 +687,7 @@ namespace MoonWorks.Math.Float
ref Vector4 value2,
float amount,
out Vector4 result
)
{
) {
result.X = MathHelper.Lerp(value1.X, value2.X, amount);
result.Y = MathHelper.Lerp(value1.Y, value2.Y, amount);
result.Z = MathHelper.Lerp(value1.Z, value2.Z, amount);
@ -836,15 +843,12 @@ namespace MoonWorks.Math.Float
/// <returns>Unit vector.</returns>
public static Vector4 Normalize(Vector4 vector)
{
var lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
(vector.Z * vector.Z) + (vector.W * vector.W);
if (lengthSquared == 0)
{
return Zero;
}
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
float factor = 1.0f / (float) System.Math.Sqrt(
(vector.X * vector.X) +
(vector.Y * vector.Y) +
(vector.Z * vector.Z) +
(vector.W * vector.W)
);
return new Vector4(
vector.X * factor,
vector.Y * factor,
@ -856,20 +860,16 @@ namespace MoonWorks.Math.Float
/// <summary>
/// Creates a new <see cref="Vector4"/> that contains a normalized values from another vector.
/// </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>
public static void Normalize(ref Vector4 vector, out Vector4 result)
{
float lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
(vector.Z * vector.Z) + (vector.W * vector.W);
if (lengthSquared == 0)
{
result = Zero;
return;
}
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
float factor = 1.0f / (float) System.Math.Sqrt(
(vector.X * vector.X) +
(vector.Y * vector.Y) +
(vector.Z * vector.Z) +
(vector.W * vector.W)
);
result.X = vector.X * factor;
result.Y = vector.Y * factor;
result.Z = vector.Z * factor;
@ -905,8 +905,7 @@ namespace MoonWorks.Math.Float
ref Vector4 value2,
float amount,
out Vector4 result
)
{
) {
result.X = MathHelper.SmoothStep(value1.X, value2.X, amount);
result.Y = MathHelper.SmoothStep(value1.Y, value2.Y, amount);
result.Z = MathHelper.SmoothStep(value1.Z, value2.Z, amount);
@ -1082,8 +1081,7 @@ namespace MoonWorks.Math.Float
Vector4[] sourceArray,
ref Matrix4x4 matrix,
Vector4[] destinationArray
)
{
) {
if (sourceArray == null)
{
throw new ArgumentNullException("sourceArray");
@ -1124,8 +1122,7 @@ namespace MoonWorks.Math.Float
Vector4[] destinationArray,
int destinationIndex,
int length
)
{
) {
if (sourceArray == null)
{
throw new ArgumentNullException("sourceArray");
@ -1205,8 +1202,7 @@ namespace MoonWorks.Math.Float
ref Vector2 value,
ref Quaternion rotation,
out Vector4 result
)
{
) {
double xx = rotation.X + rotation.X;
double yy = rotation.Y + rotation.Y;
double zz = rotation.Z + rotation.Z;
@ -1244,8 +1240,7 @@ namespace MoonWorks.Math.Float
ref Vector3 value,
ref Quaternion rotation,
out Vector4 result
)
{
) {
double xx = rotation.X + rotation.X;
double yy = rotation.Y + rotation.Y;
double zz = rotation.Z + rotation.Z;
@ -1286,8 +1281,7 @@ namespace MoonWorks.Math.Float
ref Vector4 value,
ref Quaternion rotation,
out Vector4 result
)
{
) {
double xx = rotation.X + rotation.X;
double yy = rotation.Y + rotation.Y;
double zz = rotation.Z + rotation.Z;
@ -1328,8 +1322,7 @@ namespace MoonWorks.Math.Float
Vector4[] sourceArray,
ref Quaternion rotation,
Vector4[] destinationArray
)
{
) {
if (sourceArray == null)
{
throw new ArgumentException("sourceArray");
@ -1370,8 +1363,7 @@ namespace MoonWorks.Math.Float
Vector4[] destinationArray,
int destinationIndex,
int length
)
{
) {
if (sourceArray == null)
{
throw new ArgumentException("sourceArray");
@ -1413,10 +1405,10 @@ namespace MoonWorks.Math.Float
public static bool operator ==(Vector4 value1, Vector4 value2)
{
return (value1.X == value2.X &&
return ( value1.X == value2.X &&
value1.Y == value2.Y &&
value1.Z == value2.Z &&
value1.W == value2.W);
value1.W == value2.W );
}
public static bool operator !=(Vector4 value1, Vector4 value2)

View File

@ -1,202 +0,0 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Xml;
#endregion
namespace MoonWorks
{
internal static class MoonWorksDllMap
{
#region Private Static Variables
private static Dictionary<string, string> mapDictionary
= new Dictionary<string, string>();
#endregion
#region Private Static Methods
private static string GetPlatformName()
{
if (OperatingSystem.IsWindows())
{
return "windows";
}
else if (OperatingSystem.IsMacOS())
{
return "osx";
}
else if (OperatingSystem.IsLinux())
{
return "linux";
}
else if (OperatingSystem.IsFreeBSD())
{
return "freebsd";
}
else
{
// Maybe this platform statically links?
return "unknown";
}
}
#endregion
#region DllImportResolver Callback Method
private static IntPtr MapAndLoad(
string libraryName,
Assembly assembly,
DllImportSearchPath? dllImportSearchPath
)
{
string mappedName;
if (!mapDictionary.TryGetValue(libraryName, out mappedName))
{
mappedName = libraryName;
}
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
}
#endregion
#region Module Initializer
[ModuleInitializer]
public static void Init()
{
// Ignore NativeAOT platforms since they don't perform dynamic loading.
if (!RuntimeFeature.IsDynamicCodeSupported)
{
return;
}
// Get the platform and architecture
string os = GetPlatformName();
string cpu = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
string wordsize = (IntPtr.Size * 8).ToString();
// Get the path to the assembly
Assembly assembly = Assembly.GetExecutingAssembly();
string assemblyPath = System.AppContext.BaseDirectory;
// Locate the config file
string xmlPath = Path.Combine(
assemblyPath,
"MoonWorks.dll.config"
);
if (!File.Exists(xmlPath))
{
// Let's hope for the best...
return;
}
// Load the XML
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlPath);
// The NativeLibrary API cannot remap function names. :(
if (xmlDoc.GetElementsByTagName("dllentry").Count > 0)
{
string msg = "Function remapping is not supported by .NET Core. Ignoring dllentry elements...";
Console.WriteLine(msg);
// Log it in the debugger for non-console apps.
if (Debugger.IsAttached)
{
Debug.WriteLine(msg);
}
}
// Parse the XML into a mapping dictionary
foreach (XmlNode node in xmlDoc.GetElementsByTagName("dllmap"))
{
XmlAttribute attribute;
// Check the OS
attribute = node.Attributes["os"];
if (attribute != null)
{
bool containsOS = attribute.Value.Contains(os);
bool invert = attribute.Value.StartsWith("!");
if ((!containsOS && !invert) || (containsOS && invert))
{
continue;
}
}
// Check the CPU
attribute = node.Attributes["cpu"];
if (attribute != null)
{
bool containsCPU = attribute.Value.Contains(cpu);
bool invert = attribute.Value.StartsWith("!");
if ((!containsCPU && !invert) || (containsCPU && invert))
{
continue;
}
}
// Check the word size
attribute = node.Attributes["wordsize"];
if (attribute != null)
{
bool containsWordsize = attribute.Value.Contains(wordsize);
bool invert = attribute.Value.StartsWith("!");
if ((!containsWordsize && !invert) || (containsWordsize && invert))
{
continue;
}
}
// Check for the existence of 'dll' and 'target' attributes
XmlAttribute dllAttribute = node.Attributes["dll"];
XmlAttribute targetAttribute = node.Attributes["target"];
if (dllAttribute == null || targetAttribute == null)
{
continue;
}
// Get the actual library names
string oldLib = dllAttribute.Value;
string newLib = targetAttribute.Value;
if (string.IsNullOrWhiteSpace(oldLib) || string.IsNullOrWhiteSpace(newLib))
{
continue;
}
// Don't allow duplicates
if (mapDictionary.ContainsKey(oldLib))
{
continue;
}
mapDictionary.Add(oldLib, newLib);
}
// Set the resolver callback for our native assemblies
NativeLibrary.SetDllImportResolver(assembly, MapAndLoad);
}
#endregion
}
}

View File

@ -1,9 +0,0 @@
namespace MoonWorks
{
public enum ScreenMode
{
Fullscreen,
BorderlessFullscreen,
Windowed
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,330 +0,0 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using MoonWorks.Graphics;
namespace MoonWorks.Video
{
/// <summary>
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
/// </summary>
public unsafe class VideoPlayer : GraphicsResource
{
public Texture RenderTexture { get; private set; } = null;
public VideoState State { get; private set; } = VideoState.Stopped;
public bool Loop { get; set; }
public float PlaybackSpeed { get; set; } = 1;
private VideoAV1 Video = null;
private VideoAV1Stream CurrentStream = null;
private Task ReadNextFrameTask;
private Task ResetStreamATask;
private Task ResetStreamBTask;
private GraphicsDevice GraphicsDevice;
private Texture yTexture = null;
private Texture uTexture = null;
private Texture vTexture = null;
private Sampler LinearSampler;
private int currentFrame;
private Stopwatch timer;
private double lastTimestamp;
private double timeElapsed;
public VideoPlayer(GraphicsDevice device) : base(device)
{
GraphicsDevice = device;
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
timer = new Stopwatch();
}
/// <summary>
/// Prepares a VideoAV1 for decoding and rendering.
/// </summary>
/// <param name="video"></param>
public void Load(VideoAV1 video)
{
if (Video != video)
{
Stop();
if (RenderTexture == null)
{
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height);
}
if (yTexture == null)
{
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height);
}
if (uTexture == null)
{
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
}
if (vTexture == null)
{
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
}
if (video.Width != RenderTexture.Width || video.Height != RenderTexture.Height)
{
RenderTexture.Dispose();
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height);
}
if (video.Width != yTexture.Width || video.Height != yTexture.Height)
{
yTexture.Dispose();
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height);
}
if (video.UVWidth != uTexture.Width || video.UVHeight != uTexture.Height)
{
uTexture.Dispose();
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
}
if (video.UVWidth != vTexture.Width || video.UVHeight != vTexture.Height)
{
vTexture.Dispose();
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
}
Video = video;
InitializeDav1dStream();
}
}
/// <summary>
/// Starts playing back and decoding the loaded video.
/// </summary>
public void Play()
{
if (Video == null) { return; }
if (State == VideoState.Playing)
{
return;
}
timer.Start();
State = VideoState.Playing;
}
/// <summary>
/// Pauses playback and decoding of the currently playing video.
/// </summary>
public void Pause()
{
if (Video == null) { return; }
if (State != VideoState.Playing)
{
return;
}
timer.Stop();
State = VideoState.Paused;
}
/// <summary>
/// Stops and resets decoding of the currently playing video.
/// </summary>
public void Stop()
{
if (Video == null) { return; }
if (State == VideoState.Stopped)
{
return;
}
timer.Stop();
timer.Reset();
lastTimestamp = 0;
timeElapsed = 0;
InitializeDav1dStream();
State = VideoState.Stopped;
}
/// <summary>
/// Unloads the currently playing video.
/// </summary>
public void Unload()
{
Stop();
ResetStreamATask?.Wait();
ResetStreamBTask?.Wait();
Video = null;
}
/// <summary>
/// Renders the video data into RenderTexture.
/// </summary>
public void Render()
{
if (Video == null || State == VideoState.Stopped)
{
return;
}
timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed;
lastTimestamp = timer.Elapsed.TotalMilliseconds;
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
if (thisFrame > currentFrame)
{
if (CurrentStream.FrameDataUpdated)
{
UpdateRenderTexture();
CurrentStream.FrameDataUpdated = false;
}
currentFrame = thisFrame;
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
}
if (CurrentStream.Ended)
{
timer.Stop();
timer.Reset();
var task = Task.Run(CurrentStream.Reset);
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
if (CurrentStream == Video.StreamA)
{
ResetStreamATask = task;
}
else
{
ResetStreamBTask = task;
}
if (Loop)
{
// Start over on the next stream!
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
currentFrame = -1;
timer.Start();
}
else
{
State = VideoState.Stopped;
}
}
}
private void UpdateRenderTexture()
{
lock (CurrentStream)
{
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
commandBuffer.SetTextureDataYUV(
yTexture,
uTexture,
vTexture,
CurrentStream.yDataHandle,
CurrentStream.uDataHandle,
CurrentStream.vDataHandle,
CurrentStream.yDataLength,
CurrentStream.uvDataLength,
CurrentStream.yStride,
CurrentStream.uvStride
);
commandBuffer.BeginRenderPass(
new ColorAttachmentInfo(RenderTexture, Color.Black)
);
commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
commandBuffer.BindFragmentSamplers(
new TextureSamplerBinding(yTexture, LinearSampler),
new TextureSamplerBinding(uTexture, LinearSampler),
new TextureSamplerBinding(vTexture, LinearSampler)
);
commandBuffer.DrawPrimitives(0, 1, 0, 0);
commandBuffer.EndRenderPass();
GraphicsDevice.Submit(commandBuffer);
}
}
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
{
return Texture.CreateTexture2D(
graphicsDevice,
(uint) width,
(uint) height,
TextureFormat.R8G8B8A8,
TextureUsageFlags.ColorTarget | TextureUsageFlags.Sampler
);
}
private static Texture CreateSubTexture(GraphicsDevice graphicsDevice, int width, int height)
{
return Texture.CreateTexture2D(
graphicsDevice,
(uint) width,
(uint) height,
TextureFormat.R8,
TextureUsageFlags.Sampler
);
}
private void InitializeDav1dStream()
{
ReadNextFrameTask?.Wait();
ResetStreamATask = Task.Run(Video.StreamA.Reset);
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
CurrentStream = Video.StreamA;
currentFrame = -1;
}
private static void HandleTaskException(Task task)
{
if (task.Exception.InnerException is not TaskCanceledException)
{
throw task.Exception;
}
}
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Unload();
RenderTexture?.Dispose();
yTexture?.Dispose();
uTexture?.Dispose();
vTexture?.Dispose();
}
}
base.Dispose(disposing);
}
}
}

View File

@ -1,9 +0,0 @@
namespace MoonWorks.Video
{
public enum VideoState
{
Playing,
Paused,
Stopped
}
}

View File

@ -1,174 +0,0 @@
using System;
using System.Collections.Generic;
using MoonWorks.Graphics;
using SDL2;
namespace MoonWorks
{
/// <summary>
/// Represents a window in the client operating system. <br/>
/// Every Game has a MainWindow automatically. <br/>
/// You can create additional Windows if you desire. They must be Claimed by the GraphicsDevice to be rendered to.
/// </summary>
public class Window : IDisposable
{
internal IntPtr Handle { get; }
public ScreenMode ScreenMode { get; private set; }
public uint Width { get; private set; }
public uint Height { get; private set; }
internal Texture SwapchainTexture { get; set; }
public bool Claimed { get; internal set; }
public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; }
private bool IsDisposed;
private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>();
private System.Action<uint, uint> SizeChangeCallback = null;
public Window(WindowCreateInfo windowCreateInfo, SDL.SDL_WindowFlags flags)
{
if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
{
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessFullscreen)
{
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
}
if (windowCreateInfo.SystemResizable)
{
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
}
if (windowCreateInfo.StartMaximized)
{
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED;
}
ScreenMode = windowCreateInfo.ScreenMode;
SDL.SDL_GetDesktopDisplayMode(0, out var displayMode);
Handle = SDL.SDL_CreateWindow(
windowCreateInfo.WindowTitle,
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowWidth : displayMode.w,
windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowHeight : displayMode.h,
flags
);
/* Requested size might be different in fullscreen, so let's just get the area */
SDL.SDL_GetWindowSize(Handle, out var width, out var height);
Width = (uint) width;
Height = (uint) height;
idLookup.Add(SDL.SDL_GetWindowID(Handle), this);
}
/// <summary>
/// Changes the ScreenMode of this window.
/// </summary>
public void SetScreenMode(ScreenMode screenMode)
{
SDL.SDL_WindowFlags windowFlag = 0;
if (screenMode == ScreenMode.Fullscreen)
{
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (screenMode == ScreenMode.BorderlessFullscreen)
{
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;
}
/// <summary>
/// Resizes the window. <br/>
/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public void SetWindowSize(uint width, uint height)
{
SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
Width = width;
Height = height;
if (ScreenMode == ScreenMode.Windowed)
{
SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
}
}
internal static Window Lookup(uint windowID)
{
return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null;
}
internal void Show()
{
SDL.SDL_ShowWindow(Handle);
}
internal void HandleSizeChange(uint width, uint height)
{
Width = width;
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)
{
if (!IsDisposed)
{
if (disposing)
{
// dispose managed state (managed objects)
}
idLookup.Remove(SDL.SDL_GetWindowID(Handle));
SDL.SDL_DestroyWindow(Handle);
IsDisposed = true;
}
}
~Window()
{
// 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);
}
}
}

63
src/Window/OSWindow.cs Normal file
View File

@ -0,0 +1,63 @@
using System;
using SDL2;
namespace MoonWorks.Window
{
public class OSWindow
{
internal IntPtr Handle { get; }
public ScreenMode ScreenMode { get; }
public OSWindow(WindowCreateInfo windowCreateInfo)
{
var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
{
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessWindow)
{
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
}
ScreenMode = windowCreateInfo.ScreenMode;
Handle = SDL.SDL_CreateWindow(
windowCreateInfo.WindowTitle,
SDL.SDL_WINDOWPOS_UNDEFINED,
SDL.SDL_WINDOWPOS_UNDEFINED,
(int)windowCreateInfo.WindowWidth,
(int)windowCreateInfo.WindowHeight,
windowFlags
);
}
public void ChangeScreenMode(ScreenMode screenMode)
{
SDL.SDL_WindowFlags windowFlag = 0;
if (screenMode == ScreenMode.Fullscreen)
{
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (screenMode == ScreenMode.BorderlessWindow)
{
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
}
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
}
/// <summary>
/// Resizes the window.
/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
/// </summary>
/// <param name="width"></param>
/// <param name="height"></param>
public void SetWindowSize(uint width, uint height)
{
SDL.SDL_SetWindowSize(Handle, (int)width, (int)height);
}
}
}

9
src/Window/ScreenMode.cs Normal file
View File

@ -0,0 +1,9 @@
namespace MoonWorks.Window
{
public enum ScreenMode
{
Fullscreen,
BorderlessWindow,
Windowed
}
}

View File

@ -0,0 +1,10 @@
namespace MoonWorks.Window
{
public struct WindowCreateInfo
{
public string WindowTitle;
public uint WindowWidth;
public uint WindowHeight;
public ScreenMode ScreenMode;
}
}

View File

@ -1,55 +0,0 @@
namespace MoonWorks
{
/// <summary>
/// All the information required for window creation.
/// </summary>
public struct WindowCreateInfo
{
/// <summary>
/// The name of the window that will be displayed in the operating system.
/// </summary>
public string WindowTitle;
/// <summary>
/// The width of the window.
/// </summary>
public uint WindowWidth;
/// <summary>
/// The height of the window.
/// </summary>
public uint WindowHeight;
/// <summary>
/// Specifies if the window will be created in windowed mode or a fullscreen mode.
/// </summary>
public ScreenMode ScreenMode;
/// <summary>
/// Specifies the presentation mode for the window. Roughly equivalent to V-Sync.
/// </summary>
public PresentMode PresentMode;
/// <summary>
/// Whether the window can be resized using the operating system's window dragging feature.
/// </summary>
public bool SystemResizable;
/// <summary>
/// Specifies if the window will open at the maximum desktop resolution.
/// </summary>
public bool StartMaximized;
public WindowCreateInfo(
string windowTitle,
uint windowWidth,
uint windowHeight,
ScreenMode screenMode,
PresentMode presentMode,
bool systemResizable = false,
bool startMaximized = false
) {
WindowTitle = windowTitle;
WindowWidth = windowWidth;
WindowHeight = windowHeight;
ScreenMode = screenMode;
PresentMode = presentMode;
SystemResizable = systemResizable;
StartMaximized = startMaximized;
}
}
}