forked from MoonsideGames/MoonWorks
Compare commits
113 Commits
e5213da686
...
c649b24dad
Author | SHA1 | Date |
---|---|---|
cosmonaut | c649b24dad | |
cosmonaut | f06ad263b7 | |
cosmonaut | 28e3479444 | |
cosmonaut | 72ad850ab4 | |
cosmonaut | 9cd6e6cc7b | |
cosmonaut | 9c8ec7ad5a | |
cosmonaut | 0b0a6feff5 | |
cosmonaut | 1e6455d070 | |
cosmonaut | efb9893aef | |
cosmonaut | 5a5fbc0c77 | |
cosmonaut | 40a2b31e90 | |
cosmonaut | 424f410688 | |
cosmonaut | d6bd11d63f | |
cosmonaut | f6fc80804e | |
cosmonaut | 646e5e9283 | |
cosmonaut | ab619192a6 | |
cosmonaut | d07a722fb1 | |
cosmonaut | 3543f074f4 | |
cosmonaut | d3e124cea1 | |
cosmonaut | 1cf3a13541 | |
cosmonaut | db5ca97726 | |
cosmonaut | d190df55b2 | |
cosmonaut | 1aa522ffa1 | |
cosmonaut | a9b3b53ce9 | |
cosmonaut | d32bbcd537 | |
cosmonaut | b23792acd0 | |
cosmonaut | 97fad21c0c | |
cosmonaut | 2f5d25b458 | |
cosmonaut | ba662d7c3e | |
cosmonaut | c1085db9c5 | |
cosmonaut | bb0b6daa91 | |
cosmonaut | 13519c3150 | |
cosmonaut | 6ab7a2f722 | |
cosmonaut | 436affe5de | |
darkerbit | ff544140e0 | |
cosmonaut | a10f18b4eb | |
cosmonaut | 26e7e5c809 | |
cosmonaut | d2a51ce524 | |
cosmonaut | 5a9709c843 | |
cosmonaut | 0e8188682e | |
cosmonaut | 9862bfd0a0 | |
cosmonaut | 4756fe2b14 | |
cosmonaut | e5c72c6f46 | |
cosmonaut | 318ca22021 | |
cosmonaut | 547f7a388e | |
cosmonaut | dd3fb75905 | |
cosmonaut | 2bd88e1dc1 | |
cosmonaut | 66d363459b | |
cosmonaut | b22d3bed30 | |
cosmonaut | 0d93207ae9 | |
cosmonaut | 5e2368bc7d | |
cosmonaut | a0082bcec6 | |
cosmonaut | 810f270a41 | |
cosmonaut | 27e0404ec0 | |
cosmonaut | be4b5cf2c7 | |
cosmonaut | 83eb268a4a | |
cosmonaut | 985e096a7b | |
cosmonaut | dccd81e029 | |
cosmonaut | 65568ea234 | |
cosmonaut | b49dc3720a | |
cosmonaut | 61a6d0bdc0 | |
cosmonaut | 72c9dd4bda | |
cosmonaut | 412f0ca179 | |
cosmonaut | b252d0eb92 | |
cosmonaut | ba66ed4225 | |
cosmonaut | 5e2b8de2d3 | |
cosmonaut | 35ded250ed | |
cosmonaut | f8146b799a | |
cosmonaut | 379bdcdcb1 | |
cosmonaut | 4b4abaab01 | |
cosmonaut | 6a1fa004d6 | |
cosmonaut | b1b6b84809 | |
cosmonaut | 08a3c01f66 | |
cosmonaut | ec5160c060 | |
cosmonaut | c96c7a0d90 | |
cosmonaut | f0d3dfccf9 | |
cosmonaut | 6ea3e24a91 | |
cosmonaut | 9f4b69e6aa | |
cosmonaut | 80c34c6b16 | |
cosmonaut | cc876b2132 | |
cosmonaut | 71d9f8f4fe | |
cosmonaut | 5050a9369d | |
cosmonaut | 3623e6b07c | |
cosmonaut | 1b5221f2c7 | |
cosmonaut | 0fb7e98cb5 | |
cosmonaut | 5424d05d63 | |
cosmonaut | e7addb953f | |
cosmonaut | 2a9286f31e | |
cosmonaut | 8f9aaf6d61 | |
cosmonaut | cf2d8473a1 | |
cosmonaut | b5b0f35b50 | |
cosmonaut | 527f47436a | |
cosmonaut | 40d9cdd33a | |
cosmonaut | c4d2e3b8ee | |
cosmonaut | a413863cf9 | |
cosmonaut | 17333cfb67 | |
cosmonaut | 42754ef80d | |
cosmonaut | 111df04c0f | |
cosmonaut | 9c83423c79 | |
cosmonaut | 7d3a7901b2 | |
cosmonaut | 278db7d55b | |
cosmonaut | 774028a013 | |
cosmonaut | f6369b6bce | |
cosmonaut | c34f74a99d | |
cosmonaut | d6606d90f6 | |
cosmonaut | ef10be4e9d | |
cosmonaut | 1fa73f0275 | |
cosmonaut | 81c882bd48 | |
cosmonaut | 7328cbc13d | |
cosmonaut | 32b269526f | |
cosmonaut | 9028a8b1a0 | |
cosmonaut | edd21ec573 | |
Caleb Cornett | 2469cf530a |
|
@ -7,3 +7,9 @@
|
|||
[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/Theorafile"]
|
||||
path = lib/Theorafile
|
||||
url = https://github.com/FNA-XNA/Theorafile.git
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
@ -14,6 +14,8 @@
|
|||
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
|
||||
<ProjectReference Include=".\lib\RefreshCS\RefreshCS.csproj" />
|
||||
<ProjectReference Include=".\lib\FAudio\csharp\FAudio-CS.Core.csproj" />
|
||||
<ProjectReference Include=".\lib\WellspringCS\WellspringCS.csproj" />
|
||||
<ProjectReference Include=".\lib\Theorafile\csharp\Theorafile-CS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -21,4 +23,13 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="src\Video\Shaders\Compiled\FullscreenVert.spv">
|
||||
<LogicalName>MoonWorks.Shaders.FullscreenVert.spv</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Video\Shaders\Compiled\YUV2RGBAFrag.spv">
|
||||
<LogicalName>MoonWorks.Shaders.YUV2RGBAFrag.spv</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -11,4 +11,12 @@
|
|||
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
|
||||
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
|
||||
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
||||
|
||||
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
||||
<dllmap dll="Wellspring" os="osx" target="libWellspring.0.dylib"/>
|
||||
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.0"/>
|
||||
|
||||
<dllmap dll="Theorafile" os="windows" target="libtheorafile.dll"/>
|
||||
<dllmap dll="Theorafile" os="osx" target="libtheorafile.dylib"/>
|
||||
<dllmap dll="Theorafile" os="linux,freebsd,netbsd" target="libtheorafile.so"/>
|
||||
</configuration>
|
||||
|
|
|
@ -14,6 +14,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudi
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\RefreshCS\RefreshCS.csproj", "{AD7C94E4-0AFA-44CA-889C-110142369893}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{69D3788D-6C57-44F7-A912-B201AE6D7C04}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WellspringCS", "lib\WellspringCS\WellspringCS.csproj", "{0DD7B866-773C-4A86-8580-F436DAA28989}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
|
@ -36,6 +40,10 @@ Global
|
|||
{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
|
||||
{0DD7B866-773C-4A86-8580-F436DAA28989}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0DD7B866-773C-4A86-8580-F436DAA28989}.Debug|x64.Build.0 = Debug|x64
|
||||
{0DD7B866-773C-4A86-8580-F436DAA28989}.Release|x64.ActiveCfg = Release|x64
|
||||
{0DD7B866-773C-4A86-8580-F436DAA28989}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -43,4 +51,7 @@ Global
|
|||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3D68FAA-3165-43C7-95B3-D845F0DAA918}
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{0DD7B866-773C-4A86-8580-F436DAA28989} = {69D3788D-6C57-44F7-A912-B201AE6D7C04}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
10
README.md
10
README.md
|
@ -10,11 +10,21 @@ 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
|
||||
|
||||
High-level documentation is provided here: http://moonside.games/docs/moonworks/
|
||||
|
||||
For an actual API reference, the source is documented in doc comments that your preferred IDE can read.
|
||||
|
||||
## 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
|
||||
* [Theorafile](https://github.com/FNA-XNA/Theorafile) - Compressed Video
|
||||
|
||||
Prebuilt dependencies can be obtained here: http://moonside.games/files/moonlibs.tar.bz2
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit de0c1f833c12a992af5c7daebe1705cd2c72f743
|
||||
Subproject commit 0b6d5dabbf428633482fe3a956fbdb53228fcf35
|
|
@ -1 +1 @@
|
|||
Subproject commit 5a411e482ebe619a7e85c44584faa5bd71b7ee3b
|
||||
Subproject commit 98c590ae77c3b6a64a370bac439f20728959a8b6
|
|
@ -1 +1 @@
|
|||
Subproject commit 4e9088b49de46ea8b4285948cfe69875ac4c2290
|
||||
Subproject commit b35aaa494e44d08242788ff0ba2cb7a508f4d8f0
|
|
@ -0,0 +1 @@
|
|||
Subproject commit dd8c7fa69e678b6182cdaa71458ad08dd31c65da
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97
|
|
@ -107,13 +107,13 @@ namespace MoonWorks.Audio
|
|||
|
||||
IntPtr chainPtr;
|
||||
chainPtr = Marshal.AllocHGlobal(
|
||||
Marshal.SizeOf<FAudio.FAudioEffectChain>()
|
||||
sizeof(FAudio.FAudioEffectChain)
|
||||
);
|
||||
|
||||
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
|
||||
reverbChain->EffectCount = 1;
|
||||
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
|
||||
Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
|
||||
sizeof(FAudio.FAudioEffectDescriptor)
|
||||
);
|
||||
|
||||
FAudio.FAudioEffectDescriptor* reverbDescriptor =
|
||||
|
@ -146,7 +146,7 @@ namespace MoonWorks.Audio
|
|||
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
|
||||
|
||||
IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
|
||||
Marshal.SizeOf<FAudio.FAudioFXReverbParameters>()
|
||||
sizeof(FAudio.FAudioFXReverbParameters)
|
||||
);
|
||||
|
||||
FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
|
||||
|
@ -176,7 +176,7 @@ namespace MoonWorks.Audio
|
|||
ReverbVoice,
|
||||
0,
|
||||
reverbParamsPtr,
|
||||
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
|
||||
(uint) sizeof(FAudio.FAudioFXReverbParameters),
|
||||
0
|
||||
);
|
||||
Marshal.FreeHGlobal(reverbParamsPtr);
|
||||
|
@ -187,7 +187,7 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
SendCount = 2,
|
||||
pSends = Marshal.AllocHGlobal(
|
||||
2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()
|
||||
2 * sizeof(FAudio.FAudioSendDescriptor)
|
||||
)
|
||||
};
|
||||
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
|
||||
|
@ -197,7 +197,12 @@ namespace MoonWorks.Audio
|
|||
sendDesc[1].pOutputVoice = ReverbVoice;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
public void SetMasteringVolume(float volume)
|
||||
{
|
||||
FAudio.FAudioVoice_SetVolume(MasteringVoice, volume, 0);
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
for (var i = streamingSounds.Count - 1; i >= 0; i--)
|
||||
{
|
||||
|
@ -240,16 +245,16 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (var i = streamingSounds.Count - 1; i >= 0; i--)
|
||||
for (var i = resources.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var weakReference = streamingSounds[i];
|
||||
var weakReference = resources[i];
|
||||
|
||||
if (weakReference.TryGetTarget(out var streamingSound))
|
||||
if (weakReference.TryGetTarget(out var resource))
|
||||
{
|
||||
streamingSound.Dispose();
|
||||
resource.Dispose();
|
||||
}
|
||||
}
|
||||
streamingSounds.Clear();
|
||||
resources.Clear();
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_DestroyVoice(ReverbVoice);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
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; }
|
||||
internal IntPtr Handle;
|
||||
internal FAudio.FAudioWaveFormatEx Format;
|
||||
|
||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||
|
||||
protected bool is3D;
|
||||
public bool Is3D { get; protected set; }
|
||||
|
||||
public abstract SoundState State { get; protected set; }
|
||||
public virtual SoundState State { get; protected set; }
|
||||
|
||||
private float _pan = 0;
|
||||
public float Pan
|
||||
|
@ -33,7 +31,7 @@ namespace MoonWorks.Audio
|
|||
_pan = 1f;
|
||||
}
|
||||
|
||||
if (is3D) { return; }
|
||||
if (Is3D) { return; }
|
||||
|
||||
SetPanMatrixCoefficients();
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
|
@ -53,7 +51,7 @@ namespace MoonWorks.Audio
|
|||
get => _pitch;
|
||||
set
|
||||
{
|
||||
_pitch = MathHelper.Clamp(value, -1f, 1f);
|
||||
_pitch = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
|
@ -163,17 +161,17 @@ namespace MoonWorks.Audio
|
|||
|
||||
public SoundInstance(
|
||||
AudioDevice device,
|
||||
ushort formatTag,
|
||||
ushort bitsPerSample,
|
||||
ushort blockAlign,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
bool is3D,
|
||||
bool loop
|
||||
uint samplesPerSecond
|
||||
) : base(device)
|
||||
{
|
||||
var blockAlign = (ushort) (4 * channels);
|
||||
var format = new FAudio.FAudioWaveFormatEx
|
||||
{
|
||||
wFormatTag = 3,
|
||||
wBitsPerSample = 32,
|
||||
wFormatTag = formatTag,
|
||||
wBitsPerSample = bitsPerSample,
|
||||
nChannels = channels,
|
||||
nBlockAlign = blockAlign,
|
||||
nSamplesPerSec = samplesPerSecond,
|
||||
|
@ -184,8 +182,8 @@ namespace MoonWorks.Audio
|
|||
|
||||
FAudio.FAudio_CreateSourceVoice(
|
||||
Device.Handle,
|
||||
out var handle,
|
||||
ref format,
|
||||
out Handle,
|
||||
ref Format,
|
||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||
IntPtr.Zero,
|
||||
|
@ -193,28 +191,28 @@ namespace MoonWorks.Audio
|
|||
IntPtr.Zero
|
||||
);
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
Logger.LogError("SoundInstance failed to initialize!");
|
||||
return;
|
||||
}
|
||||
|
||||
Handle = handle;
|
||||
this.is3D = is3D;
|
||||
InitDSPSettings(Format.nChannels);
|
||||
|
||||
// FIXME: not everything should be running through reverb...
|
||||
/*
|
||||
FAudio.FAudioVoice_SetOutputVoices(
|
||||
handle,
|
||||
Handle,
|
||||
ref Device.ReverbSends
|
||||
);
|
||||
*/
|
||||
|
||||
Loop = loop;
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
public void Apply3D(AudioListener listener, AudioEmitter emitter)
|
||||
{
|
||||
is3D = true;
|
||||
Is3D = true;
|
||||
|
||||
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
|
||||
emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount;
|
||||
|
@ -240,7 +238,8 @@ namespace MoonWorks.Audio
|
|||
|
||||
public abstract void Play();
|
||||
public abstract void Pause();
|
||||
public abstract void Stop(bool immediate);
|
||||
public abstract void Stop();
|
||||
public abstract void StopImmediate();
|
||||
|
||||
private void InitDSPSettings(uint srcChannels)
|
||||
{
|
||||
|
@ -271,7 +270,7 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
float doppler;
|
||||
float dopplerScale = Device.DopplerScale;
|
||||
if (!is3D || dopplerScale == 0.0f)
|
||||
if (!Is3D || dopplerScale == 0.0f)
|
||||
{
|
||||
doppler = 1.0f;
|
||||
}
|
||||
|
@ -343,8 +342,7 @@ namespace MoonWorks.Audio
|
|||
|
||||
protected override void Destroy()
|
||||
{
|
||||
Stop(true);
|
||||
|
||||
StopImmediate();
|
||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
|
@ -6,12 +8,17 @@ namespace MoonWorks.Audio
|
|||
public class StaticSound : AudioResource
|
||||
{
|
||||
internal FAudio.FAudioBuffer Handle;
|
||||
public ushort FormatTag { get; }
|
||||
public ushort BitsPerSample { get; }
|
||||
public ushort Channels { get; }
|
||||
public uint SamplesPerSecond { get; }
|
||||
public ushort BlockAlign { get; }
|
||||
|
||||
public uint LoopStart { get; set; } = 0;
|
||||
public uint LoopLength { get; set; } = 0;
|
||||
|
||||
private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>();
|
||||
|
||||
public static StaticSound LoadOgg(AudioDevice device, string filePath)
|
||||
{
|
||||
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
|
@ -43,6 +50,194 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
}
|
||||
|
||||
// mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385
|
||||
public static StaticSound LoadWav(AudioDevice device, string filePath)
|
||||
{
|
||||
// Sample data
|
||||
byte[] data;
|
||||
|
||||
// WaveFormatEx data
|
||||
ushort wFormatTag;
|
||||
ushort nChannels;
|
||||
uint nSamplesPerSec;
|
||||
uint nAvgBytesPerSec;
|
||||
ushort nBlockAlign;
|
||||
ushort wBitsPerSample;
|
||||
int samplerLoopStart = 0;
|
||||
int samplerLoopEnd = 0;
|
||||
|
||||
using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath)))
|
||||
{
|
||||
// RIFF Signature
|
||||
string signature = new string(reader.ReadChars(4));
|
||||
if (signature != "RIFF")
|
||||
{
|
||||
throw new NotSupportedException("Specified stream is not a wave file.");
|
||||
}
|
||||
|
||||
reader.ReadUInt32(); // Riff Chunk Size
|
||||
|
||||
string wformat = new string(reader.ReadChars(4));
|
||||
if (wformat != "WAVE")
|
||||
{
|
||||
throw new NotSupportedException("Specified stream is not a wave file.");
|
||||
}
|
||||
|
||||
// WAVE Header
|
||||
string format_signature = new string(reader.ReadChars(4));
|
||||
while (format_signature != "fmt ")
|
||||
{
|
||||
reader.ReadBytes(reader.ReadInt32());
|
||||
format_signature = new string(reader.ReadChars(4));
|
||||
}
|
||||
|
||||
int format_chunk_size = reader.ReadInt32();
|
||||
|
||||
wFormatTag = reader.ReadUInt16();
|
||||
nChannels = reader.ReadUInt16();
|
||||
nSamplesPerSec = reader.ReadUInt32();
|
||||
nAvgBytesPerSec = reader.ReadUInt32();
|
||||
nBlockAlign = reader.ReadUInt16();
|
||||
wBitsPerSample = reader.ReadUInt16();
|
||||
|
||||
// Reads residual bytes
|
||||
if (format_chunk_size > 16)
|
||||
{
|
||||
reader.ReadBytes(format_chunk_size - 16);
|
||||
}
|
||||
|
||||
// data Signature
|
||||
string data_signature = new string(reader.ReadChars(4));
|
||||
while (data_signature.ToLowerInvariant() != "data")
|
||||
{
|
||||
reader.ReadBytes(reader.ReadInt32());
|
||||
data_signature = new string(reader.ReadChars(4));
|
||||
}
|
||||
if (data_signature != "data")
|
||||
{
|
||||
throw new NotSupportedException("Specified wave file is not supported.");
|
||||
}
|
||||
|
||||
int waveDataLength = reader.ReadInt32();
|
||||
data = reader.ReadBytes(waveDataLength);
|
||||
|
||||
// Scan for other chunks
|
||||
while (reader.PeekChar() != -1)
|
||||
{
|
||||
char[] chunkIDChars = reader.ReadChars(4);
|
||||
if (chunkIDChars.Length < 4)
|
||||
{
|
||||
break; // EOL!
|
||||
}
|
||||
byte[] chunkSizeBytes = reader.ReadBytes(4);
|
||||
if (chunkSizeBytes.Length < 4)
|
||||
{
|
||||
break; // EOL!
|
||||
}
|
||||
string chunk_signature = new string(chunkIDChars);
|
||||
int chunkDataSize = BitConverter.ToInt32(chunkSizeBytes, 0);
|
||||
if (chunk_signature == "smpl") // "smpl", Sampler Chunk Found
|
||||
{
|
||||
reader.ReadUInt32(); // Manufacturer
|
||||
reader.ReadUInt32(); // Product
|
||||
reader.ReadUInt32(); // Sample Period
|
||||
reader.ReadUInt32(); // MIDI Unity Note
|
||||
reader.ReadUInt32(); // MIDI Pitch Fraction
|
||||
reader.ReadUInt32(); // SMPTE Format
|
||||
reader.ReadUInt32(); // SMPTE Offset
|
||||
uint numSampleLoops = reader.ReadUInt32();
|
||||
int samplerData = reader.ReadInt32();
|
||||
|
||||
for (int i = 0; i < numSampleLoops; i += 1)
|
||||
{
|
||||
reader.ReadUInt32(); // Cue Point ID
|
||||
reader.ReadUInt32(); // Type
|
||||
int start = reader.ReadInt32();
|
||||
int end = reader.ReadInt32();
|
||||
reader.ReadUInt32(); // Fraction
|
||||
reader.ReadUInt32(); // Play Count
|
||||
|
||||
if (i == 0) // Grab loopStart and loopEnd from first sample loop
|
||||
{
|
||||
samplerLoopStart = start;
|
||||
samplerLoopEnd = end;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplerData != 0) // Read Sampler Data if it exists
|
||||
{
|
||||
reader.ReadBytes(samplerData);
|
||||
}
|
||||
}
|
||||
else // Read unwanted chunk data and try again
|
||||
{
|
||||
reader.ReadBytes(chunkDataSize);
|
||||
}
|
||||
}
|
||||
// End scan
|
||||
}
|
||||
|
||||
return new StaticSound(
|
||||
device,
|
||||
wFormatTag,
|
||||
wBitsPerSample,
|
||||
nBlockAlign,
|
||||
nChannels,
|
||||
nSamplesPerSec,
|
||||
data,
|
||||
0,
|
||||
(uint) data.Length
|
||||
);
|
||||
}
|
||||
|
||||
public StaticSound(
|
||||
AudioDevice device,
|
||||
ushort formatTag,
|
||||
ushort bitsPerSample,
|
||||
ushort blockAlign,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
byte[] buffer,
|
||||
uint bufferOffset, /* number of bytes */
|
||||
uint bufferLength /* number of bytes */
|
||||
) : base(device)
|
||||
{
|
||||
FormatTag = formatTag;
|
||||
BitsPerSample = bitsPerSample;
|
||||
BlockAlign = blockAlign;
|
||||
Channels = channels;
|
||||
SamplesPerSecond = samplesPerSecond;
|
||||
|
||||
Handle = new FAudio.FAudioBuffer();
|
||||
Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
|
||||
Handle.pContext = IntPtr.Zero;
|
||||
Handle.AudioBytes = bufferLength;
|
||||
Handle.pAudioData = Marshal.AllocHGlobal((int) bufferLength);
|
||||
Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
|
||||
Handle.PlayBegin = 0;
|
||||
Handle.PlayLength = 0;
|
||||
|
||||
if (formatTag == 1)
|
||||
{
|
||||
Handle.PlayLength = (uint) (
|
||||
bufferLength /
|
||||
channels /
|
||||
(bitsPerSample / 8)
|
||||
);
|
||||
}
|
||||
else if (formatTag == 2)
|
||||
{
|
||||
Handle.PlayLength = (uint) (
|
||||
bufferLength /
|
||||
blockAlign *
|
||||
(((blockAlign / channels) - 6) * 2)
|
||||
);
|
||||
}
|
||||
|
||||
LoopStart = 0;
|
||||
LoopLength = 0;
|
||||
}
|
||||
|
||||
public StaticSound(
|
||||
AudioDevice device,
|
||||
ushort channels,
|
||||
|
@ -52,6 +247,9 @@ namespace MoonWorks.Audio
|
|||
uint bufferLength /* in floats */
|
||||
) : base(device)
|
||||
{
|
||||
FormatTag = 3;
|
||||
BitsPerSample = 32;
|
||||
BlockAlign = (ushort) (4 * channels);
|
||||
Channels = channels;
|
||||
SamplesPerSecond = samplesPerSecond;
|
||||
|
||||
|
@ -69,9 +267,23 @@ namespace MoonWorks.Audio
|
|||
LoopLength = 0;
|
||||
}
|
||||
|
||||
public StaticSoundInstance CreateInstance(bool loop = false)
|
||||
/// <summary>
|
||||
/// Gets a sound instance from the pool.
|
||||
/// NOTE: If you lose track of instances, you will create garbage collection pressure!
|
||||
/// </summary>
|
||||
public StaticSoundInstance GetInstance()
|
||||
{
|
||||
return new StaticSoundInstance(Device, this, false, loop);
|
||||
if (Instances.Count == 0)
|
||||
{
|
||||
Instances.Push(new StaticSoundInstance(Device, this));
|
||||
}
|
||||
|
||||
return Instances.Pop();
|
||||
}
|
||||
|
||||
internal void FreeInstance(StaticSoundInstance instance)
|
||||
{
|
||||
Instances.Push(instance);
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
|
|
|
@ -6,6 +6,8 @@ namespace MoonWorks.Audio
|
|||
{
|
||||
public StaticSound Parent { get; }
|
||||
|
||||
public bool Loop { get; set; }
|
||||
|
||||
private SoundState _state = SoundState.Stopped;
|
||||
public override SoundState State
|
||||
{
|
||||
|
@ -18,7 +20,7 @@ namespace MoonWorks.Audio
|
|||
);
|
||||
if (state.BuffersQueued == 0)
|
||||
{
|
||||
Stop(true);
|
||||
StopImmediate();
|
||||
}
|
||||
|
||||
return _state;
|
||||
|
@ -32,10 +34,8 @@ namespace MoonWorks.Audio
|
|||
|
||||
internal StaticSoundInstance(
|
||||
AudioDevice device,
|
||||
StaticSound parent,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop)
|
||||
StaticSound parent
|
||||
) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
@ -79,18 +79,33 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
public override void Stop(bool immediate = true)
|
||||
public override void Stop()
|
||||
{
|
||||
if (immediate)
|
||||
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
public override void StopImmediate()
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
public void Seek(uint sampleFrame)
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
else
|
||||
{
|
||||
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
|
||||
}
|
||||
|
||||
Parent.Handle.PlayBegin = sampleFrame;
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
Parent.FreeInstance(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,38 @@
|
|||
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.
|
||||
/// Must be extended with a decoder routine called by FillBuffer.
|
||||
/// See StreamingSoundOgg for an example.
|
||||
/// </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;
|
||||
private const int BUFFER_COUNT = 3;
|
||||
private readonly IntPtr[] buffers;
|
||||
private int nextBufferIndex = 0;
|
||||
private uint queuedBufferCount = 0;
|
||||
protected abstract int BUFFER_SIZE { get; }
|
||||
|
||||
public int PendingBufferCount => queuedBuffers.Count;
|
||||
|
||||
public StreamingSound(
|
||||
public unsafe StreamingSound(
|
||||
AudioDevice device,
|
||||
ushort formatTag,
|
||||
ushort bitsPerSample,
|
||||
ushort blockAlign,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) : base(device, channels, samplesPerSecond, is3D, loop) { }
|
||||
uint samplesPerSecond
|
||||
) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
||||
{
|
||||
device.AddDynamicSoundInstance(this);
|
||||
|
||||
buffers = new IntPtr[BUFFER_COUNT];
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
{
|
||||
buffers[i] = (IntPtr) NativeMemory.Alloc((nuint) BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
|
@ -32,6 +42,7 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
|
||||
State = SoundState.Playing;
|
||||
|
||||
Update();
|
||||
FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
|
||||
}
|
||||
|
@ -45,19 +56,21 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
public override void Stop(bool immediate = true)
|
||||
public override void Stop()
|
||||
{
|
||||
if (immediate)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
ClearBuffers();
|
||||
}
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
public override void StopImmediate()
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
ClearBuffers();
|
||||
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
internal unsafe void Update()
|
||||
{
|
||||
if (State != SoundState.Playing)
|
||||
{
|
||||
|
@ -70,109 +83,83 @@ namespace MoonWorks.Audio
|
|||
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
||||
);
|
||||
|
||||
while (PendingBufferCount > state.BuffersQueued)
|
||||
lock (queuedBuffers)
|
||||
{
|
||||
Marshal.FreeHGlobal(queuedBuffers[0]);
|
||||
queuedBuffers.RemoveAt(0);
|
||||
}
|
||||
queuedBufferCount = state.BuffersQueued;
|
||||
|
||||
QueueBuffers();
|
||||
}
|
||||
|
||||
protected void QueueBuffers()
|
||||
{
|
||||
for (
|
||||
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
|
||||
i > 0;
|
||||
i -= 1
|
||||
)
|
||||
for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
|
||||
{
|
||||
AddBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
protected void ClearBuffers()
|
||||
protected unsafe void ClearBuffers()
|
||||
{
|
||||
lock (queuedBuffers)
|
||||
{
|
||||
foreach (IntPtr buf in queuedBuffers)
|
||||
{
|
||||
Marshal.FreeHGlobal(buf);
|
||||
}
|
||||
queuedBuffers.Clear();
|
||||
queuedSizes.Clear();
|
||||
}
|
||||
nextBufferIndex = 0;
|
||||
queuedBufferCount = 0;
|
||||
}
|
||||
|
||||
protected void AddBuffer()
|
||||
protected unsafe void AddBuffer()
|
||||
{
|
||||
AddBuffer(
|
||||
out var buffer,
|
||||
out var bufferOffset,
|
||||
out var bufferLength,
|
||||
out var reachedEnd
|
||||
var buffer = buffers[nextBufferIndex];
|
||||
nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
|
||||
|
||||
FillBuffer(
|
||||
(void*) buffer,
|
||||
BUFFER_SIZE,
|
||||
out int filledLengthInBytes,
|
||||
out bool reachedEnd
|
||||
);
|
||||
|
||||
var lengthInBytes = bufferLength * sizeof(float);
|
||||
|
||||
IntPtr next = Marshal.AllocHGlobal((int) lengthInBytes);
|
||||
Marshal.Copy(buffer, (int) bufferOffset, next, (int) bufferLength);
|
||||
|
||||
lock (queuedBuffers)
|
||||
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
|
||||
{
|
||||
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)
|
||||
)
|
||||
};
|
||||
AudioBytes = (uint) filledLengthInBytes,
|
||||
pAudioData = (IntPtr) buffer,
|
||||
PlayLength = (
|
||||
(uint) (filledLengthInBytes /
|
||||
Format.nChannels /
|
||||
(uint) (Format.wBitsPerSample / 8))
|
||||
)
|
||||
};
|
||||
|
||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||
Handle,
|
||||
ref buf,
|
||||
IntPtr.Zero
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
queuedSizes.Add(lengthInBytes);
|
||||
}
|
||||
}
|
||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||
Handle,
|
||||
ref buf,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
queuedBufferCount += 1;
|
||||
|
||||
/* We have reached the end of the file, what do we do? */
|
||||
if (reachedEnd)
|
||||
{
|
||||
if (Loop)
|
||||
{
|
||||
SeekStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop(false);
|
||||
}
|
||||
OnReachedEnd();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void AddBuffer(
|
||||
out float[] buffer,
|
||||
out uint bufferOffset, /* in floats */
|
||||
out uint bufferLength, /* in floats */
|
||||
protected virtual void OnReachedEnd()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
protected unsafe abstract void FillBuffer(
|
||||
void* buffer,
|
||||
int bufferLengthInBytes, /* in bytes */
|
||||
out int filledLengthInBytes, /* in bytes */
|
||||
out bool reachedEnd
|
||||
);
|
||||
|
||||
protected abstract void SeekStart();
|
||||
|
||||
protected override void Destroy()
|
||||
protected unsafe override void Destroy()
|
||||
{
|
||||
Stop(true);
|
||||
StopImmediate();
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
{
|
||||
NativeMemory.Free((void*) buffers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,91 +1,90 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class StreamingSoundOgg : StreamingSound
|
||||
public class StreamingSoundOgg : StreamingSoundSeekable
|
||||
{
|
||||
// FIXME: what should this value be?
|
||||
public const int BUFFER_SIZE = 1024 * 128;
|
||||
private IntPtr VorbisHandle;
|
||||
private IntPtr FileDataPtr;
|
||||
private FAudio.stb_vorbis_info Info;
|
||||
|
||||
internal IntPtr FileHandle { get; }
|
||||
internal FAudio.stb_vorbis_info Info { get; }
|
||||
protected override int BUFFER_SIZE => 32768;
|
||||
|
||||
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
|
||||
)
|
||||
public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
|
||||
{
|
||||
var fileHandle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
var fileData = File.ReadAllBytes(filePath);
|
||||
var fileDataPtr = NativeMemory.Alloc((nuint) fileData.Length);
|
||||
Marshal.Copy(fileData, 0, (IntPtr) fileDataPtr, fileData.Length);
|
||||
var vorbisHandle = FAudio.stb_vorbis_open_memory((IntPtr) fileDataPtr, fileData.Length, out int error, IntPtr.Zero);
|
||||
if (error != 0)
|
||||
{
|
||||
NativeMemory.Free(fileDataPtr);
|
||||
Logger.LogError("Error opening OGG file!");
|
||||
Logger.LogError("Error: " + error);
|
||||
throw new AudioLoadException("Error opening OGG file!");
|
||||
}
|
||||
|
||||
var info = FAudio.stb_vorbis_get_info(fileHandle);
|
||||
var info = FAudio.stb_vorbis_get_info(vorbisHandle);
|
||||
|
||||
return new StreamingSoundOgg(
|
||||
device,
|
||||
fileHandle,
|
||||
info,
|
||||
is3D,
|
||||
loop
|
||||
(IntPtr) fileDataPtr,
|
||||
vorbisHandle,
|
||||
info
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
|
||||
IntPtr vorbisHandle,
|
||||
FAudio.stb_vorbis_info info
|
||||
) : base(
|
||||
device,
|
||||
3, /* float type */
|
||||
32, /* size of float */
|
||||
(ushort) (4 * info.channels),
|
||||
(ushort) info.channels,
|
||||
info.sample_rate
|
||||
)
|
||||
{
|
||||
FileHandle = fileHandle;
|
||||
FileDataPtr = fileDataPtr;
|
||||
VorbisHandle = vorbisHandle;
|
||||
Info = info;
|
||||
buffer = new float[BUFFER_SIZE];
|
||||
|
||||
device.AddDynamicSoundInstance(this);
|
||||
}
|
||||
|
||||
protected override void AddBuffer(
|
||||
out float[] buffer,
|
||||
out uint bufferOffset,
|
||||
out uint bufferLength,
|
||||
public override void Seek(uint sampleFrame)
|
||||
{
|
||||
FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
|
||||
}
|
||||
|
||||
protected unsafe override void FillBuffer(
|
||||
void* buffer,
|
||||
int bufferLengthInBytes,
|
||||
out int filledLengthInBytes,
|
||||
out bool reachedEnd
|
||||
)
|
||||
{
|
||||
buffer = this.buffer;
|
||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||
|
||||
/* NOTE: this function returns samples per channel, not total samples */
|
||||
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
|
||||
FileHandle,
|
||||
VorbisHandle,
|
||||
Info.channels,
|
||||
buffer,
|
||||
buffer.Length
|
||||
(IntPtr) buffer,
|
||||
lengthInFloats
|
||||
);
|
||||
|
||||
var sampleCount = samples * Info.channels;
|
||||
bufferOffset = 0;
|
||||
bufferLength = (uint) sampleCount;
|
||||
reachedEnd = sampleCount < buffer.Length;
|
||||
reachedEnd = sampleCount < lengthInFloats;
|
||||
filledLengthInBytes = sampleCount * sizeof(float);
|
||||
}
|
||||
|
||||
protected override void SeekStart()
|
||||
protected unsafe override void Destroy()
|
||||
{
|
||||
FAudio.stb_vorbis_seek_start(FileHandle);
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
FAudio.stb_vorbis_close(FileHandle);
|
||||
FAudio.stb_vorbis_close(VorbisHandle);
|
||||
NativeMemory.Free((void*) FileDataPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class StreamingSoundSeekable : StreamingSound
|
||||
{
|
||||
public bool Loop { get; set; }
|
||||
|
||||
protected StreamingSoundSeekable(AudioDevice device, ushort formatTag, ushort bitsPerSample, ushort blockAlign, ushort channels, uint samplesPerSecond) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void Seek(uint sampleFrame);
|
||||
|
||||
protected override void OnReachedEnd()
|
||||
{
|
||||
if (Loop)
|
||||
{
|
||||
Seek(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// Axis-aligned bounding box.
|
||||
/// </summary>
|
||||
public struct AABB2D : System.IEquatable<AABB2D>
|
||||
{
|
||||
/// <summary>
|
||||
/// The top-left position of the AABB.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Vector2 Min { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bottom-right position of the AABB.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Vector2 Max { get; private set; }
|
||||
|
||||
public Fix64 Width { get { return Max.X - Min.X; } }
|
||||
public Fix64 Height { get { return Max.Y - Min.Y; } }
|
||||
|
||||
public Fix64 Right { get { return Max.X; } }
|
||||
public Fix64 Left { get { return Min.X; } }
|
||||
|
||||
/// <summary>
|
||||
/// The top of the AABB. Assumes a downward-aligned Y axis, so this value will be smaller than Bottom.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Fix64 Top { get { return Min.Y; } }
|
||||
|
||||
/// <summary>
|
||||
/// The bottom of the AABB. Assumes a downward-aligned Y axis, so this value will be larger than Top.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Fix64 Bottom { get { return Max.Y; } }
|
||||
|
||||
public AABB2D(Fix64 minX, Fix64 minY, Fix64 maxX, Fix64 maxY)
|
||||
{
|
||||
Min = new Vector2(minX, minY);
|
||||
Max = new Vector2(maxX, maxY);
|
||||
}
|
||||
|
||||
public AABB2D(int minX, int minY, int maxX, int maxY)
|
||||
{
|
||||
Min = new Vector2(minX, minY);
|
||||
Max = new Vector2(maxX, maxY);
|
||||
}
|
||||
|
||||
public AABB2D(Vector2 min, Vector2 max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
private static Matrix3x2 AbsoluteMatrix(Matrix3x2 matrix)
|
||||
{
|
||||
return new Matrix3x2
|
||||
(
|
||||
Fix64.Abs(matrix.M11), Fix64.Abs(matrix.M12),
|
||||
Fix64.Abs(matrix.M21), Fix64.Abs(matrix.M22),
|
||||
Fix64.Abs(matrix.M31), Fix64.Abs(matrix.M32)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Efficiently transforms the AABB by a Transform2D.
|
||||
/// </summary>
|
||||
/// <param name="aabb"></param>
|
||||
/// <param name="transform"></param>
|
||||
/// <returns></returns>
|
||||
public static AABB2D Transformed(AABB2D aabb, Transform2D transform)
|
||||
{
|
||||
var two = new Fix64(2);
|
||||
var center = (aabb.Min + aabb.Max) / two;
|
||||
var extent = (aabb.Max - aabb.Min) / two;
|
||||
|
||||
var newCenter = Vector2.Transform(center, transform.TransformMatrix);
|
||||
var newExtent = Vector2.TransformNormal(extent, AbsoluteMatrix(transform.TransformMatrix));
|
||||
|
||||
return new AABB2D(newCenter - newExtent, newCenter + newExtent);
|
||||
}
|
||||
|
||||
public AABB2D Compose(AABB2D aabb)
|
||||
{
|
||||
Fix64 left = Left;
|
||||
Fix64 top = Top;
|
||||
Fix64 right = Right;
|
||||
Fix64 bottom = Bottom;
|
||||
|
||||
if (aabb.Left < left)
|
||||
{
|
||||
left = aabb.Left;
|
||||
}
|
||||
if (aabb.Right > right)
|
||||
{
|
||||
right = aabb.Right;
|
||||
}
|
||||
if (aabb.Top < top)
|
||||
{
|
||||
top = aabb.Top;
|
||||
}
|
||||
if (aabb.Bottom > bottom)
|
||||
{
|
||||
bottom = aabb.Bottom;
|
||||
}
|
||||
|
||||
return new AABB2D(left, top, right, bottom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AABB for an arbitrary collection of positions.
|
||||
/// This is less efficient than defining a custom AABB method for most shapes, so avoid using this if possible.
|
||||
/// </summary>
|
||||
/// <param name="vertices"></param>
|
||||
/// <returns></returns>
|
||||
public static AABB2D FromVertices(IEnumerable<Vector2> vertices)
|
||||
{
|
||||
var minX = Fix64.MaxValue;
|
||||
var minY = Fix64.MaxValue;
|
||||
var maxX = Fix64.MinValue;
|
||||
var maxY = Fix64.MinValue;
|
||||
|
||||
foreach (var vertex in vertices)
|
||||
{
|
||||
if (vertex.X < minX)
|
||||
{
|
||||
minX = vertex.X;
|
||||
}
|
||||
if (vertex.Y < minY)
|
||||
{
|
||||
minY = vertex.Y;
|
||||
}
|
||||
if (vertex.X > maxX)
|
||||
{
|
||||
maxX = vertex.X;
|
||||
}
|
||||
if (vertex.Y > maxY)
|
||||
{
|
||||
maxY = vertex.Y;
|
||||
}
|
||||
}
|
||||
|
||||
return new AABB2D(minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
public static bool TestOverlap(AABB2D a, AABB2D b)
|
||||
{
|
||||
return a.Left < b.Right && a.Right > b.Left && a.Top < b.Bottom && a.Bottom > b.Top;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is AABB2D aabb && Equals(aabb);
|
||||
}
|
||||
|
||||
public bool Equals(AABB2D other)
|
||||
{
|
||||
return Min == other.Min &&
|
||||
Max == other.Max;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Min, Max);
|
||||
}
|
||||
|
||||
public static bool operator ==(AABB2D left, AABB2D right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(AABB2D left, AABB2D right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
public interface ICollidable
|
||||
{
|
||||
IEnumerable<IShape2D> Shapes { get; }
|
||||
AABB2D AABB { get; }
|
||||
AABB2D TransformedAABB(Transform2D transform);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
public interface IShape2D : ICollidable, System.IEquatable<IShape2D>
|
||||
{
|
||||
/// <summary>
|
||||
/// A Minkowski support function. Gives the farthest point on the edge of a shape along the given direction.
|
||||
/// </summary>
|
||||
/// <param name="direction">A normalized Vector2.</param>
|
||||
/// <param name="transform">A Transform for transforming the shape vertices.</param>
|
||||
/// <returns>The farthest point on the edge of the shape along the given direction.</returns>
|
||||
Vector2 Support(Vector2 direction, Transform2D transform);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A Minkowski difference between two shapes.
|
||||
/// </summary>
|
||||
public struct MinkowskiDifference : System.IEquatable<MinkowskiDifference>
|
||||
{
|
||||
private IShape2D ShapeA { get; }
|
||||
private Transform2D TransformA { get; }
|
||||
private IShape2D ShapeB { get; }
|
||||
private Transform2D TransformB { get; }
|
||||
|
||||
public MinkowskiDifference(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||
{
|
||||
ShapeA = shapeA;
|
||||
TransformA = transformA;
|
||||
ShapeB = shapeB;
|
||||
TransformB = transformB;
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction)
|
||||
{
|
||||
return ShapeA.Support(direction, TransformA) - ShapeB.Support(-direction, TransformB);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return other is MinkowskiDifference minkowskiDifference && Equals(minkowskiDifference);
|
||||
}
|
||||
|
||||
public bool Equals(MinkowskiDifference other)
|
||||
{
|
||||
return
|
||||
ShapeA == other.ShapeA &&
|
||||
TransformA == other.TransformA &&
|
||||
ShapeB == other.ShapeB &&
|
||||
TransformB == other.TransformB;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(ShapeA, TransformA, ShapeB, TransformB);
|
||||
}
|
||||
|
||||
public static bool operator ==(MinkowskiDifference a, MinkowskiDifference b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(MinkowskiDifference a, MinkowskiDifference b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
public static class NarrowPhase
|
||||
{
|
||||
private struct Edge
|
||||
{
|
||||
public Fix64 Distance;
|
||||
public Vector2 Normal;
|
||||
public int Index;
|
||||
}
|
||||
|
||||
public static bool TestCollision(ICollidable collidableA, Transform2D transformA, ICollidable collidableB, Transform2D transformB)
|
||||
{
|
||||
foreach (var shapeA in collidableA.Shapes)
|
||||
{
|
||||
foreach (var shapeB in collidableB.Shapes)
|
||||
{
|
||||
if (TestCollision(shapeA, transformA, shapeB, transformB))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TestCollision(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||
{
|
||||
// If we can use a fast path check, let's do that!
|
||||
if (shapeA is Rectangle rectangleA && shapeB is Rectangle rectangleB && transformA.IsAxisAligned && transformB.IsAxisAligned)
|
||||
{
|
||||
return TestRectangleOverlap(rectangleA, transformA, rectangleB, transformB);
|
||||
}
|
||||
else if (shapeA is Point && shapeB is Rectangle && transformB.IsAxisAligned)
|
||||
{
|
||||
return TestPointRectangleOverlap((Point) shapeA, transformA, (Rectangle) shapeB, transformB);
|
||||
}
|
||||
else if (shapeA is Rectangle && shapeB is Point && transformA.IsAxisAligned)
|
||||
{
|
||||
return TestPointRectangleOverlap((Point) shapeB, transformB, (Rectangle) shapeA, transformA);
|
||||
}
|
||||
else if (shapeA is Rectangle && shapeB is Circle && transformA.IsAxisAligned && transformB.IsUniformScale)
|
||||
{
|
||||
return TestCircleRectangleOverlap((Circle) shapeB, transformB, (Rectangle) shapeA, transformA);
|
||||
}
|
||||
else if (shapeA is Circle && shapeB is Rectangle && transformA.IsUniformScale && transformB.IsAxisAligned)
|
||||
{
|
||||
return TestCircleRectangleOverlap((Circle) shapeA, transformA, (Rectangle) shapeB, transformB);
|
||||
}
|
||||
else if (shapeA is Circle && shapeB is Point && transformA.IsUniformScale)
|
||||
{
|
||||
return TestCirclePointOverlap((Circle) shapeA, transformA, (Point) shapeB, transformB);
|
||||
}
|
||||
else if (shapeA is Point && shapeB is Circle && transformB.IsUniformScale)
|
||||
{
|
||||
return TestCirclePointOverlap((Circle) shapeB, transformB, (Point) shapeA, transformA);
|
||||
}
|
||||
else if (shapeA is Circle circleA && shapeB is Circle circleB && transformA.IsUniformScale && transformB.IsUniformScale)
|
||||
{
|
||||
return TestCircleOverlap(circleA, transformA, circleB, transformB);
|
||||
}
|
||||
|
||||
// Sad, we can't do a fast path optimization. Time for a simplex reduction.
|
||||
return FindCollisionSimplex(shapeA, transformA, shapeB, transformB).Item1;
|
||||
}
|
||||
|
||||
public static bool TestRectangleOverlap(Rectangle rectangleA, Transform2D transformA, Rectangle rectangleB, Transform2D transformB)
|
||||
{
|
||||
var firstAABB = rectangleA.TransformedAABB(transformA);
|
||||
var secondAABB = rectangleB.TransformedAABB(transformB);
|
||||
|
||||
return firstAABB.Left < secondAABB.Right && firstAABB.Right > secondAABB.Left && firstAABB.Top < secondAABB.Bottom && firstAABB.Bottom > secondAABB.Top;
|
||||
}
|
||||
|
||||
public static bool TestPointRectangleOverlap(Point point, Transform2D pointTransform, Rectangle rectangle, Transform2D rectangleTransform)
|
||||
{
|
||||
var transformedPoint = pointTransform.Position;
|
||||
var AABB = rectangle.TransformedAABB(rectangleTransform);
|
||||
|
||||
return transformedPoint.X > AABB.Left && transformedPoint.X < AABB.Right && transformedPoint.Y < AABB.Bottom && transformedPoint.Y > AABB.Top;
|
||||
}
|
||||
|
||||
public static bool TestCirclePointOverlap(Circle circle, Transform2D circleTransform, Point point, Transform2D pointTransform)
|
||||
{
|
||||
var circleCenter = circleTransform.Position;
|
||||
var circleRadius = circle.Radius * circleTransform.Scale.X;
|
||||
|
||||
var distanceX = circleCenter.X - pointTransform.Position.X;
|
||||
var distanceY = circleCenter.Y - pointTransform.Position.Y;
|
||||
|
||||
return (distanceX * distanceX) + (distanceY * distanceY) < (circleRadius * circleRadius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NOTE: The rectangle must be axis aligned, and the scaling of the circle must be uniform.
|
||||
/// </summary>
|
||||
public static bool TestCircleRectangleOverlap(Circle circle, Transform2D circleTransform, Rectangle rectangle, Transform2D rectangleTransform)
|
||||
{
|
||||
var circleCenter = circleTransform.Position;
|
||||
var circleRadius = circle.Radius * circleTransform.Scale.X;
|
||||
var AABB = rectangle.TransformedAABB(rectangleTransform);
|
||||
|
||||
var closestX = Fix64.Clamp(circleCenter.X, AABB.Left, AABB.Right);
|
||||
var closestY = Fix64.Clamp(circleCenter.Y, AABB.Top, AABB.Bottom);
|
||||
|
||||
var distanceX = circleCenter.X - closestX;
|
||||
var distanceY = circleCenter.Y - closestY;
|
||||
|
||||
var distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
|
||||
return distanceSquared < (circleRadius * circleRadius);
|
||||
}
|
||||
|
||||
public static bool TestCircleOverlap(Circle circleA, Transform2D transformA, Circle circleB, Transform2D transformB)
|
||||
{
|
||||
var radiusA = circleA.Radius * transformA.Scale.X;
|
||||
var radiusB = circleB.Radius * transformB.Scale.Y;
|
||||
|
||||
var centerA = transformA.Position;
|
||||
var centerB = transformB.Position;
|
||||
|
||||
var distanceSquared = (centerA - centerB).LengthSquared();
|
||||
var radiusSumSquared = (radiusA + radiusB) * (radiusA + radiusB);
|
||||
|
||||
return distanceSquared < radiusSumSquared;
|
||||
}
|
||||
|
||||
public static (bool, Simplex2D) FindCollisionSimplex(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||
{
|
||||
var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB);
|
||||
var c = minkowskiDifference.Support(Vector2.UnitX);
|
||||
var b = minkowskiDifference.Support(-Vector2.UnitX);
|
||||
return Check(minkowskiDifference, c, b);
|
||||
}
|
||||
|
||||
public unsafe static Vector2 Intersect(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Simplex2D simplex)
|
||||
{
|
||||
if (shapeA == null) { throw new System.ArgumentNullException(nameof(shapeA)); }
|
||||
if (shapeB == null) { throw new System.ArgumentNullException(nameof(shapeB)); }
|
||||
if (!simplex.TwoSimplex) { throw new System.ArgumentException("Simplex must be a 2-Simplex.", nameof(simplex)); }
|
||||
|
||||
var epsilon = Fix64.FromFraction(1, 10000);
|
||||
|
||||
var a = simplex.A;
|
||||
var b = simplex.B.Value;
|
||||
var c = simplex.C.Value;
|
||||
|
||||
Vector2 intersection = default;
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var edge = FindClosestEdge(simplex);
|
||||
var support = CalculateSupport(shapeA, Transform2DA, shapeB, Transform2DB, edge.Normal);
|
||||
var distance = Vector2.Dot(support, edge.Normal);
|
||||
|
||||
intersection = edge.Normal;
|
||||
intersection *= distance;
|
||||
|
||||
if (Fix64.Abs(distance - edge.Distance) <= epsilon)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
else
|
||||
{
|
||||
simplex.Insert(support, edge.Index);
|
||||
}
|
||||
}
|
||||
|
||||
return intersection; // close enough
|
||||
}
|
||||
|
||||
private static unsafe Edge FindClosestEdge(Simplex2D simplex)
|
||||
{
|
||||
var closestDistance = Fix64.MaxValue;
|
||||
var closestNormal = Vector2.Zero;
|
||||
var closestIndex = 0;
|
||||
|
||||
for (var i = 0; i < 4; i += 1)
|
||||
{
|
||||
var j = (i + 1 == 3) ? 0 : i + 1;
|
||||
|
||||
var a = simplex[i];
|
||||
var b = simplex[j];
|
||||
|
||||
var e = b - a;
|
||||
|
||||
var oa = a;
|
||||
|
||||
var n = Vector2.Normalize(TripleProduct(e, oa, e));
|
||||
|
||||
var d = Vector2.Dot(n, a);
|
||||
|
||||
if (d < closestDistance)
|
||||
{
|
||||
closestDistance = d;
|
||||
closestNormal = n;
|
||||
closestIndex = j;
|
||||
}
|
||||
}
|
||||
|
||||
return new Edge
|
||||
{
|
||||
Distance = closestDistance,
|
||||
Normal = closestNormal,
|
||||
Index = closestIndex
|
||||
};
|
||||
}
|
||||
|
||||
private static Vector2 CalculateSupport(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Vector2 direction)
|
||||
{
|
||||
return shapeA.Support(direction, Transform2DA) - shapeB.Support(-direction, Transform2DB);
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D) Check(MinkowskiDifference minkowskiDifference, Vector2 c, Vector2 b)
|
||||
{
|
||||
var cb = c - b;
|
||||
var c0 = -c;
|
||||
var d = Direction(cb, c0);
|
||||
return DoSimplex(minkowskiDifference, new Simplex2D(b, c), d);
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D) DoSimplex(MinkowskiDifference minkowskiDifference, Simplex2D simplex, Vector2 direction)
|
||||
{
|
||||
var a = minkowskiDifference.Support(direction);
|
||||
var notPastOrigin = Vector2.Dot(a, direction) < Fix64.Zero;
|
||||
var (intersects, newSimplex, newDirection) = EnclosesOrigin(a, simplex);
|
||||
|
||||
if (notPastOrigin)
|
||||
{
|
||||
return (false, default(Simplex2D));
|
||||
}
|
||||
else if (intersects)
|
||||
{
|
||||
return (true, new Simplex2D(simplex.A, simplex.B.Value, a));
|
||||
}
|
||||
else
|
||||
{
|
||||
return DoSimplex(minkowskiDifference, newSimplex, newDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D, Vector2) EnclosesOrigin(Vector2 a, Simplex2D simplex)
|
||||
{
|
||||
if (simplex.ZeroSimplex)
|
||||
{
|
||||
return HandleZeroSimplex(a, simplex.A);
|
||||
}
|
||||
else if (simplex.OneSimplex)
|
||||
{
|
||||
return HandleOneSimplex(a, simplex.A, simplex.B.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false, simplex, Vector2.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D, Vector2) HandleZeroSimplex(Vector2 a, Vector2 b)
|
||||
{
|
||||
var ab = b - a;
|
||||
var a0 = -a;
|
||||
var (newSimplex, newDirection) = SameDirection(ab, a0) ? (new Simplex2D(a, b), Perpendicular(ab, a0)) : (new Simplex2D(a), a0);
|
||||
return (false, newSimplex, newDirection);
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D, Vector2) HandleOneSimplex(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
var a0 = -a;
|
||||
var ab = b - a;
|
||||
var ac = c - a;
|
||||
var abp = Perpendicular(ab, -ac);
|
||||
var acp = Perpendicular(ac, -ab);
|
||||
|
||||
if (SameDirection(abp, a0))
|
||||
{
|
||||
if (SameDirection(ab, a0))
|
||||
{
|
||||
return (false, new Simplex2D(a, b), abp);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false, new Simplex2D(a), a0);
|
||||
}
|
||||
}
|
||||
else if (SameDirection(acp, a0))
|
||||
{
|
||||
if (SameDirection(ac, a0))
|
||||
{
|
||||
return (false, new Simplex2D(a, c), acp);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false, new Simplex2D(a), a0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (true, new Simplex2D(b, c), a0);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 TripleProduct(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
var A = new Vector3(a.X, a.Y, Fix64.Zero);
|
||||
var B = new Vector3(b.X, b.Y, Fix64.Zero);
|
||||
var C = new Vector3(c.X, c.Y, Fix64.Zero);
|
||||
|
||||
var first = Vector3.Cross(A, B);
|
||||
var second = Vector3.Cross(first, C);
|
||||
|
||||
return new Vector2(second.X, second.Y);
|
||||
}
|
||||
|
||||
private static Vector2 Direction(Vector2 a, Vector2 b)
|
||||
{
|
||||
var d = TripleProduct(a, b, a);
|
||||
var collinear = d == Vector2.Zero;
|
||||
return collinear ? new Vector2(a.Y, -a.X) : d;
|
||||
}
|
||||
|
||||
private static bool SameDirection(Vector2 a, Vector2 b)
|
||||
{
|
||||
return Vector2.Dot(a, b) > Fix64.Zero;
|
||||
}
|
||||
|
||||
private static Vector2 Perpendicular(Vector2 a, Vector2 b)
|
||||
{
|
||||
return TripleProduct(a, b, a);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A Circle is a shape defined by a radius.
|
||||
/// </summary>
|
||||
public struct Circle : IShape2D, System.IEquatable<Circle>
|
||||
{
|
||||
public Fix64 Radius { get; }
|
||||
public AABB2D AABB { get; }
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Circle(Fix64 radius)
|
||||
{
|
||||
Radius = radius;
|
||||
AABB = new AABB2D(-Radius, -Radius, Radius, Radius);
|
||||
}
|
||||
|
||||
public Circle(int radius)
|
||||
{
|
||||
Radius = (Fix64) radius;
|
||||
AABB = new AABB2D(-Radius, -Radius, Radius, Radius);
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
return Vector2.Transform(Vector2.Normalize(direction) * Radius, transform.TransformMatrix);
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform2D)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform2D);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return other is Circle circle && Equals(circle);
|
||||
}
|
||||
|
||||
public bool Equals(Circle other)
|
||||
{
|
||||
return Radius == other.Radius;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Radius);
|
||||
}
|
||||
|
||||
public static bool operator ==(Circle a, Circle b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Circle a, Circle b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A line is a shape defined by exactly two points in space.
|
||||
/// </summary>
|
||||
public struct Line : IShape2D, System.IEquatable<Line>
|
||||
{
|
||||
public Vector2 Start { get; }
|
||||
public Vector2 End { get; }
|
||||
|
||||
public AABB2D AABB { get; }
|
||||
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Line(Vector2 start, Vector2 end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
AABB = new AABB2D(
|
||||
Fix64.Min(Start.X, End.X),
|
||||
Fix64.Min(Start.Y, End.Y),
|
||||
Fix64.Max(Start.X, End.X),
|
||||
Fix64.Max(Start.Y, End.Y)
|
||||
);
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
var transformedStart = Vector2.Transform(Start, transform.TransformMatrix);
|
||||
var transformedEnd = Vector2.Transform(End, transform.TransformMatrix);
|
||||
return Vector2.Dot(transformedStart, direction) > Vector2.Dot(transformedEnd, direction) ?
|
||||
transformedStart :
|
||||
transformedEnd;
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return other is Line otherLine && Equals(otherLine);
|
||||
}
|
||||
|
||||
public bool Equals(Line other)
|
||||
{
|
||||
return
|
||||
(Start == other.Start && End == other.End) ||
|
||||
(End == other.Start && Start == other.End);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Start, End);
|
||||
}
|
||||
|
||||
public static bool operator ==(Line a, Line b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Line a, Line b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A Point is "that which has no part".
|
||||
/// All points by themselves are identical.
|
||||
/// </summary>
|
||||
public struct Point : IShape2D, System.IEquatable<Point>
|
||||
{
|
||||
public AABB2D AABB { get; }
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform);
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
return Vector2.Transform(Vector2.Zero, transform.TransformMatrix);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return other is Point otherPoint && Equals(otherPoint);
|
||||
}
|
||||
|
||||
public bool Equals(Point other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool operator ==(Point a, Point b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator !=(Point a, Point b)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A rectangle is a shape defined by a width and height. The origin is the center of the rectangle.
|
||||
/// </summary>
|
||||
public struct Rectangle : IShape2D, System.IEquatable<Rectangle>
|
||||
{
|
||||
public AABB2D AABB { get; }
|
||||
public Fix64 Width { get; }
|
||||
public Fix64 Height { get; }
|
||||
|
||||
public Fix64 Right { get; }
|
||||
public Fix64 Left { get; }
|
||||
public Fix64 Top { get; }
|
||||
public Fix64 Bottom { get; }
|
||||
public Vector2 TopLeft { get; }
|
||||
public Vector2 BottomRight { get; }
|
||||
|
||||
public Vector2 Min { get; }
|
||||
public Vector2 Max { get; }
|
||||
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Rectangle(Fix64 left, Fix64 top, Fix64 width, Fix64 height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Left = left;
|
||||
Right = left + width;
|
||||
Top = top;
|
||||
Bottom = top + height;
|
||||
AABB = new AABB2D(left, top, Right, Bottom);
|
||||
TopLeft = new Vector2(Left, Top);
|
||||
BottomRight = new Vector2(Right, Bottom);
|
||||
Min = AABB.Min;
|
||||
Max = AABB.Max;
|
||||
}
|
||||
|
||||
public Rectangle(int left, int top, int width, int height)
|
||||
{
|
||||
Width = (Fix64) width;
|
||||
Height = (Fix64) height;
|
||||
Left = (Fix64) left;
|
||||
Right = (Fix64) (left + width);
|
||||
Top = (Fix64) top;
|
||||
Bottom = (Fix64) (top + height);
|
||||
AABB = new AABB2D(Left, Top, Right, Bottom);
|
||||
TopLeft = new Vector2(Left, Top);
|
||||
BottomRight = new Vector2(Right, Bottom);
|
||||
Min = AABB.Min;
|
||||
Max = AABB.Max;
|
||||
}
|
||||
|
||||
private Vector2 Support(Vector2 direction)
|
||||
{
|
||||
if (direction.X >= Fix64.Zero && direction.Y >= Fix64.Zero)
|
||||
{
|
||||
return Max;
|
||||
}
|
||||
else if (direction.X >= Fix64.Zero && direction.Y < Fix64.Zero)
|
||||
{
|
||||
return new Vector2(Max.X, Min.Y);
|
||||
}
|
||||
else if (direction.X < Fix64.Zero && direction.Y >= Fix64.Zero)
|
||||
{
|
||||
return new Vector2(Min.X, Max.Y);
|
||||
}
|
||||
else if (direction.X < Fix64.Zero && direction.Y < Fix64.Zero)
|
||||
{
|
||||
return new Vector2(Min.X, Min.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.ArgumentException("Support vector direction cannot be zero.");
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
Matrix3x2 inverseTransform;
|
||||
Matrix3x2.Invert(transform.TransformMatrix, out inverseTransform);
|
||||
var inverseDirection = Vector2.TransformNormal(direction, inverseTransform);
|
||||
return Vector2.Transform(Support(inverseDirection), transform.TransformMatrix);
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return (other is Rectangle rectangle && Equals(rectangle));
|
||||
}
|
||||
|
||||
public bool Equals(Rectangle other)
|
||||
{
|
||||
return Min == other.Min && Max == other.Max;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Min, Max);
|
||||
}
|
||||
|
||||
public static bool operator ==(Rectangle a, Rectangle b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Rectangle a, Rectangle b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A simplex is a shape with up to n - 2 vertices in the nth dimension.
|
||||
/// </summary>
|
||||
public struct Simplex2D : System.IEquatable<Simplex2D>
|
||||
{
|
||||
private Vector2 a;
|
||||
private Vector2? b;
|
||||
private Vector2? c;
|
||||
|
||||
public Vector2 A => a;
|
||||
public Vector2? B => b;
|
||||
public Vector2? C => c;
|
||||
|
||||
public bool ZeroSimplex { get { return !b.HasValue && !c.HasValue; } }
|
||||
public bool OneSimplex { get { return b.HasValue && !c.HasValue; } }
|
||||
public bool TwoSimplex { get { return b.HasValue && c.HasValue; } }
|
||||
|
||||
public int Count => TwoSimplex ? 3 : (OneSimplex ? 2 : 1);
|
||||
|
||||
public Simplex2D(Vector2 a)
|
||||
{
|
||||
this.a = a;
|
||||
b = null;
|
||||
c = null;
|
||||
}
|
||||
|
||||
public Simplex2D(Vector2 a, Vector2 b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
c = null;
|
||||
}
|
||||
|
||||
public Simplex2D(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public Vector2 this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index == 0) { return a; }
|
||||
if (index == 1) { return b.Value; }
|
||||
if (index == 2) { return c.Value; }
|
||||
throw new System.IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Vector2> Vertices
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return (Vector2) a;
|
||||
if (b.HasValue) { yield return (Vector2) b; }
|
||||
if (c.HasValue) { yield return (Vector2) c; }
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
var maxDotProduct = Fix64.MinValue;
|
||||
var maxVertex = a;
|
||||
foreach (var vertex in Vertices)
|
||||
{
|
||||
var transformed = Vector2.Transform(vertex, transform.TransformMatrix);
|
||||
var dot = Vector2.Dot(transformed, direction);
|
||||
if (dot > maxDotProduct)
|
||||
{
|
||||
maxVertex = transformed;
|
||||
maxDotProduct = dot;
|
||||
}
|
||||
}
|
||||
return maxVertex;
|
||||
}
|
||||
|
||||
public void Insert(Vector2 point, int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
c = b;
|
||||
b = a;
|
||||
a = point;
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
c = b;
|
||||
b = point;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = point;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Simplex2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(Simplex2D other)
|
||||
{
|
||||
if (Count != other.Count) { return false; }
|
||||
|
||||
return
|
||||
(A == other.A && B == other.B && C == other.C) ||
|
||||
(A == other.A && B == other.C && C == other.B) ||
|
||||
(A == other.B && B == other.A && C == other.C) ||
|
||||
(A == other.B && B == other.C && C == other.A) ||
|
||||
(A == other.C && B == other.A && C == other.B) ||
|
||||
(A == other.C && B == other.B && C == other.A);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Vertices);
|
||||
}
|
||||
|
||||
public static bool operator ==(Simplex2D a, Simplex2D b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Simplex2D a, Simplex2D b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Fixed;
|
||||
|
||||
namespace MoonWorks.Collision.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to quickly check if two shapes are potentially overlapping.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that will be used to uniquely identify shape-transform pairs.</typeparam>
|
||||
public class SpatialHash2D<T> where T : System.IEquatable<T>
|
||||
{
|
||||
private readonly Fix64 cellSize;
|
||||
|
||||
private readonly Dictionary<long, HashSet<T>> hashDictionary = new Dictionary<long, HashSet<T>>();
|
||||
private readonly Dictionary<T, (ICollidable, Transform2D, uint)> IDLookup = new Dictionary<T, (ICollidable, Transform2D, uint)>();
|
||||
|
||||
public int MinX { get; private set; } = 0;
|
||||
public int MaxX { get; private set; } = 0;
|
||||
public int MinY { get; private set; } = 0;
|
||||
public int MaxY { get; private set; } = 0;
|
||||
|
||||
private Queue<HashSet<T>> hashSetPool = new Queue<HashSet<T>>();
|
||||
|
||||
public SpatialHash2D(int cellSize)
|
||||
{
|
||||
this.cellSize = new Fix64(cellSize);
|
||||
}
|
||||
|
||||
private (int, int) Hash(Vector2 position)
|
||||
{
|
||||
return ((int) (position.X / cellSize), (int) (position.Y / cellSize));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an element into the SpatialHash.
|
||||
/// </summary>
|
||||
/// <param name="id">A unique ID for the shape-transform pair.</param>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="transform2D"></param>
|
||||
/// <param name="collisionGroups">A bitmask value specifying the groups this object belongs to.</param>
|
||||
public void Insert(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
|
||||
{
|
||||
var box = shape.TransformedAABB(transform2D);
|
||||
var minHash = Hash(box.Min);
|
||||
var maxHash = Hash(box.Max);
|
||||
|
||||
foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
|
||||
{
|
||||
if (!hashDictionary.ContainsKey(key))
|
||||
{
|
||||
hashDictionary.Add(key, new HashSet<T>());
|
||||
}
|
||||
|
||||
hashDictionary[key].Add(id);
|
||||
IDLookup[id] = (shape, transform2D, collisionGroups);
|
||||
}
|
||||
|
||||
MinX = System.Math.Min(MinX, minHash.Item1);
|
||||
MinY = System.Math.Min(MinY, minHash.Item2);
|
||||
MaxX = System.Math.Max(MaxX, maxHash.Item1);
|
||||
MaxY = System.Math.Max(MaxY, maxHash.Item2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID.
|
||||
/// </summary>
|
||||
public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(T id, ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
|
||||
{
|
||||
var returned = AcquireHashSet();
|
||||
|
||||
var box = shape.TransformedAABB(transform2D);
|
||||
var (minX, minY) = Hash(box.Min);
|
||||
var (maxX, maxY) = Hash(box.Max);
|
||||
|
||||
if (minX < MinX) { minX = MinX; }
|
||||
if (maxX > MaxX) { maxX = MaxX; }
|
||||
if (minY < MinY) { minY = MinY; }
|
||||
if (maxY > MaxY) { maxY = MaxY; }
|
||||
|
||||
foreach (var key in Keys(minX, minY, maxX, maxY))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
foreach (var t in hashDictionary[key])
|
||||
{
|
||||
if (!returned.Contains(t))
|
||||
{
|
||||
var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
|
||||
if (!id.Equals(t) && ((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
|
||||
{
|
||||
returned.Add(t);
|
||||
yield return (t, otherShape, otherTransform, collisionGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeHashSet(returned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all the potential collisions of a shape-transform pair.
|
||||
/// </summary>
|
||||
public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
|
||||
{
|
||||
var returned = AcquireHashSet();
|
||||
|
||||
var box = shape.TransformedAABB(transform2D);
|
||||
var (minX, minY) = Hash(box.Min);
|
||||
var (maxX, maxY) = Hash(box.Max);
|
||||
|
||||
if (minX < MinX) { minX = MinX; }
|
||||
if (maxX > MaxX) { maxX = MaxX; }
|
||||
if (minY < MinY) { minY = MinY; }
|
||||
if (maxY > MaxY) { maxY = MaxY; }
|
||||
|
||||
foreach (var key in Keys(minX, minY, maxX, maxY))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
foreach (var t in hashDictionary[key])
|
||||
{
|
||||
if (!returned.Contains(t))
|
||||
{
|
||||
var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
|
||||
if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
|
||||
{
|
||||
returned.Add(t);
|
||||
yield return (t, otherShape, otherTransform, collisionGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeHashSet(returned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves objects based on a pre-transformed AABB.
|
||||
/// </summary>
|
||||
/// <param name="aabb">A transformed AABB.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(AABB2D aabb, uint collisionMask = uint.MaxValue)
|
||||
{
|
||||
var returned = AcquireHashSet();
|
||||
|
||||
var (minX, minY) = Hash(aabb.Min);
|
||||
var (maxX, maxY) = Hash(aabb.Max);
|
||||
|
||||
if (minX < MinX) { minX = MinX; }
|
||||
if (maxX > MaxX) { maxX = MaxX; }
|
||||
if (minY < MinY) { minY = MinY; }
|
||||
if (maxY > MaxY) { maxY = MaxY; }
|
||||
|
||||
foreach (var key in Keys(minX, minY, maxX, maxY))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
foreach (var t in hashDictionary[key])
|
||||
{
|
||||
if (!returned.Contains(t))
|
||||
{
|
||||
var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
|
||||
if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(aabb, otherShape.TransformedAABB(otherTransform)))
|
||||
{
|
||||
yield return (t, otherShape, otherTransform, collisionGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeHashSet(returned);
|
||||
}
|
||||
|
||||
public void Update(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
|
||||
{
|
||||
Remove(id);
|
||||
Insert(id, shape, transform2D, collisionGroups);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific ID from the SpatialHash.
|
||||
/// </summary>
|
||||
public void Remove(T id)
|
||||
{
|
||||
var (shape, transform, collisionGroups) = IDLookup[id];
|
||||
|
||||
var box = shape.TransformedAABB(transform);
|
||||
var minHash = Hash(box.Min);
|
||||
var maxHash = Hash(box.Max);
|
||||
|
||||
foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
hashDictionary[key].Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
IDLookup.Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes everything that has been inserted into the SpatialHash.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var hash in hashDictionary.Values)
|
||||
{
|
||||
hash.Clear();
|
||||
}
|
||||
|
||||
IDLookup.Clear();
|
||||
}
|
||||
|
||||
private static long MakeLong(int left, int right)
|
||||
{
|
||||
return ((long) left << 32) | ((uint) right);
|
||||
}
|
||||
|
||||
private IEnumerable<long> Keys(int minX, int minY, int maxX, int maxY)
|
||||
{
|
||||
for (var i = minX; i <= maxX; i++)
|
||||
{
|
||||
for (var j = minY; j <= maxY; j++)
|
||||
{
|
||||
yield return MakeLong(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<T> AcquireHashSet()
|
||||
{
|
||||
if (hashSetPool.Count == 0)
|
||||
{
|
||||
hashSetPool.Enqueue(new HashSet<T>());
|
||||
}
|
||||
|
||||
var hashSet = hashSetPool.Dequeue();
|
||||
hashSet.Clear();
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
private void FreeHashSet(HashSet<T> hashSet)
|
||||
{
|
||||
hashSetPool.Enqueue(hashSet);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Axis-aligned bounding box.
|
||||
/// </summary>
|
||||
public struct AABB2D : System.IEquatable<AABB2D>
|
||||
{
|
||||
/// <summary>
|
||||
/// The top-left position of the AABB.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Vector2 Min { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bottom-right position of the AABB.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public Vector2 Max { get; private set; }
|
||||
|
||||
public float Width { get { return Max.X - Min.X; } }
|
||||
public float Height { get { return Max.Y - Min.Y; } }
|
||||
|
||||
public float Right { get { return Max.X; } }
|
||||
public float Left { get { return Min.X; } }
|
||||
|
||||
/// <summary>
|
||||
/// The top of the AABB. Assumes a downward-aligned Y axis, so this value will be smaller than Bottom.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public float Top { get { return Min.Y; } }
|
||||
|
||||
/// <summary>
|
||||
/// The bottom of the AABB. Assumes a downward-aligned Y axis, so this value will be larger than Top.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public float Bottom { get { return Max.Y; } }
|
||||
|
||||
public AABB2D(float minX, float minY, float maxX, float maxY)
|
||||
{
|
||||
Min = new Vector2(minX, minY);
|
||||
Max = new Vector2(maxX, maxY);
|
||||
}
|
||||
|
||||
public AABB2D(Vector2 min, Vector2 max)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
private static Matrix3x2 AbsoluteMatrix(Matrix3x2 matrix)
|
||||
{
|
||||
return new Matrix3x2
|
||||
(
|
||||
System.Math.Abs(matrix.M11), System.Math.Abs(matrix.M12),
|
||||
System.Math.Abs(matrix.M21), System.Math.Abs(matrix.M22),
|
||||
System.Math.Abs(matrix.M31), System.Math.Abs(matrix.M32)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Efficiently transforms the AABB by a Transform2D.
|
||||
/// </summary>
|
||||
/// <param name="aabb"></param>
|
||||
/// <param name="transform"></param>
|
||||
/// <returns></returns>
|
||||
public static AABB2D Transformed(AABB2D aabb, Transform2D transform)
|
||||
{
|
||||
var center = (aabb.Min + aabb.Max) / 2f;
|
||||
var extent = (aabb.Max - aabb.Min) / 2f;
|
||||
|
||||
var newCenter = Vector2.Transform(center, transform.TransformMatrix);
|
||||
var newExtent = Vector2.TransformNormal(extent, AbsoluteMatrix(transform.TransformMatrix));
|
||||
|
||||
return new AABB2D(newCenter - newExtent, newCenter + newExtent);
|
||||
}
|
||||
|
||||
public AABB2D Compose(AABB2D aabb)
|
||||
{
|
||||
float left = Left;
|
||||
float top = Top;
|
||||
float right = Right;
|
||||
float bottom = Bottom;
|
||||
|
||||
if (aabb.Left < left)
|
||||
{
|
||||
left = aabb.Left;
|
||||
}
|
||||
if (aabb.Right > right)
|
||||
{
|
||||
right = aabb.Right;
|
||||
}
|
||||
if (aabb.Top < top)
|
||||
{
|
||||
top = aabb.Top;
|
||||
}
|
||||
if (aabb.Bottom > bottom)
|
||||
{
|
||||
bottom = aabb.Bottom;
|
||||
}
|
||||
|
||||
return new AABB2D(left, top, right, bottom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AABB for an arbitrary collection of positions.
|
||||
/// This is less efficient than defining a custom AABB method for most shapes, so avoid using this if possible.
|
||||
/// </summary>
|
||||
/// <param name="vertices"></param>
|
||||
/// <returns></returns>
|
||||
public static AABB2D FromVertices(IEnumerable<Vector2> vertices)
|
||||
{
|
||||
var minX = float.MaxValue;
|
||||
var minY = float.MaxValue;
|
||||
var maxX = float.MinValue;
|
||||
var maxY = float.MinValue;
|
||||
|
||||
foreach (var vertex in vertices)
|
||||
{
|
||||
if (vertex.X < minX)
|
||||
{
|
||||
minX = vertex.X;
|
||||
}
|
||||
if (vertex.Y < minY)
|
||||
{
|
||||
minY = vertex.Y;
|
||||
}
|
||||
if (vertex.X > maxX)
|
||||
{
|
||||
maxX = vertex.X;
|
||||
}
|
||||
if (vertex.Y > maxY)
|
||||
{
|
||||
maxY = vertex.Y;
|
||||
}
|
||||
}
|
||||
|
||||
return new AABB2D(minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
public static bool TestOverlap(AABB2D a, AABB2D b)
|
||||
{
|
||||
return a.Left < b.Right && a.Right > b.Left && a.Top < b.Bottom && a.Bottom > b.Top;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is AABB2D aabb && Equals(aabb);
|
||||
}
|
||||
|
||||
public bool Equals(AABB2D other)
|
||||
{
|
||||
return Min == other.Min &&
|
||||
Max == other.Max;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Min, Max);
|
||||
}
|
||||
|
||||
public static bool operator ==(AABB2D left, AABB2D right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(AABB2D left, AABB2D right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
public interface ICollidable
|
||||
{
|
||||
IEnumerable<IShape2D> Shapes { get; }
|
||||
AABB2D AABB { get; }
|
||||
AABB2D TransformedAABB(Transform2D transform);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
public interface IShape2D : ICollidable, System.IEquatable<IShape2D>
|
||||
{
|
||||
/// <summary>
|
||||
/// A Minkowski support function. Gives the farthest point on the edge of a shape along the given direction.
|
||||
/// </summary>
|
||||
/// <param name="direction">A normalized Vector2.</param>
|
||||
/// <param name="transform">A Transform for transforming the shape vertices.</param>
|
||||
/// <returns>The farthest point on the edge of the shape along the given direction.</returns>
|
||||
Vector2 Support(Vector2 direction, Transform2D transform);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A Minkowski difference between two shapes.
|
||||
/// </summary>
|
||||
public struct MinkowskiDifference : System.IEquatable<MinkowskiDifference>
|
||||
{
|
||||
private IShape2D ShapeA { get; }
|
||||
private Transform2D TransformA { get; }
|
||||
private IShape2D ShapeB { get; }
|
||||
private Transform2D TransformB { get; }
|
||||
|
||||
public MinkowskiDifference(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||
{
|
||||
ShapeA = shapeA;
|
||||
TransformA = transformA;
|
||||
ShapeB = shapeB;
|
||||
TransformB = transformB;
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction)
|
||||
{
|
||||
return ShapeA.Support(direction, TransformA) - ShapeB.Support(-direction, TransformB);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
return other is MinkowskiDifference minkowskiDifference && Equals(minkowskiDifference);
|
||||
}
|
||||
|
||||
public bool Equals(MinkowskiDifference other)
|
||||
{
|
||||
return
|
||||
ShapeA == other.ShapeA &&
|
||||
TransformA == other.TransformA &&
|
||||
ShapeB == other.ShapeB &&
|
||||
TransformB == other.TransformB;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(ShapeA, TransformA, ShapeB, TransformB);
|
||||
}
|
||||
|
||||
public static bool operator ==(MinkowskiDifference a, MinkowskiDifference b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(MinkowskiDifference a, MinkowskiDifference b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
public static class NarrowPhase
|
||||
{
|
||||
private struct Edge
|
||||
{
|
||||
public float Distance;
|
||||
public Vector2 Normal;
|
||||
public int Index;
|
||||
}
|
||||
|
||||
public static bool TestCollision(ICollidable collidableA, Transform2D transformA, ICollidable collidableB, Transform2D transformB)
|
||||
{
|
||||
foreach (var shapeA in collidableA.Shapes)
|
||||
{
|
||||
foreach (var shapeB in collidableB.Shapes)
|
||||
{
|
||||
if (TestCollision(shapeA, transformA, shapeB, transformB))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TestCollision(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||
{
|
||||
// If we can use a fast path check, let's do that!
|
||||
if (shapeA is Rectangle rectangleA && shapeB is Rectangle rectangleB && transformA.IsAxisAligned && transformB.IsAxisAligned)
|
||||
{
|
||||
return TestRectangleOverlap(rectangleA, transformA, rectangleB, transformB);
|
||||
}
|
||||
else if (shapeA is Point && shapeB is Rectangle && transformB.IsAxisAligned)
|
||||
{
|
||||
return TestPointRectangleOverlap((Point) shapeA, transformA, (Rectangle) shapeB, transformB);
|
||||
}
|
||||
else if (shapeA is Rectangle && shapeB is Point && transformA.IsAxisAligned)
|
||||
{
|
||||
return TestPointRectangleOverlap((Point) shapeB, transformB, (Rectangle) shapeA, transformA);
|
||||
}
|
||||
else if (shapeA is Rectangle && shapeB is Circle && transformA.IsAxisAligned && transformB.IsUniformScale)
|
||||
{
|
||||
return TestCircleRectangleOverlap((Circle) shapeB, transformB, (Rectangle) shapeA, transformA);
|
||||
}
|
||||
else if (shapeA is Circle && shapeB is Rectangle && transformA.IsUniformScale && transformB.IsAxisAligned)
|
||||
{
|
||||
return TestCircleRectangleOverlap((Circle) shapeA, transformA, (Rectangle) shapeB, transformB);
|
||||
}
|
||||
else if (shapeA is Circle && shapeB is Point && transformA.IsUniformScale)
|
||||
{
|
||||
return TestCirclePointOverlap((Circle) shapeA, transformA, (Point) shapeB, transformB);
|
||||
}
|
||||
else if (shapeA is Point && shapeB is Circle && transformB.IsUniformScale)
|
||||
{
|
||||
return TestCirclePointOverlap((Circle) shapeB, transformB, (Point) shapeA, transformA);
|
||||
}
|
||||
else if (shapeA is Circle circleA && shapeB is Circle circleB && transformA.IsUniformScale && transformB.IsUniformScale)
|
||||
{
|
||||
return TestCircleOverlap(circleA, transformA, circleB, transformB);
|
||||
}
|
||||
|
||||
// Sad, we can't do a fast path optimization. Time for a simplex reduction.
|
||||
return FindCollisionSimplex(shapeA, transformA, shapeB, transformB).Item1;
|
||||
}
|
||||
|
||||
public static bool TestRectangleOverlap(Rectangle rectangleA, Transform2D transformA, Rectangle rectangleB, Transform2D transformB)
|
||||
{
|
||||
var firstAABB = rectangleA.TransformedAABB(transformA);
|
||||
var secondAABB = rectangleB.TransformedAABB(transformB);
|
||||
|
||||
return firstAABB.Left < secondAABB.Right && firstAABB.Right > secondAABB.Left && firstAABB.Top < secondAABB.Bottom && firstAABB.Bottom > secondAABB.Top;
|
||||
}
|
||||
|
||||
public static bool TestPointRectangleOverlap(Point point, Transform2D pointTransform, Rectangle rectangle, Transform2D rectangleTransform)
|
||||
{
|
||||
var transformedPoint = pointTransform.Position;
|
||||
var AABB = rectangle.TransformedAABB(rectangleTransform);
|
||||
|
||||
return transformedPoint.X > AABB.Left && transformedPoint.X < AABB.Right && transformedPoint.Y < AABB.Bottom && transformedPoint.Y > AABB.Top;
|
||||
}
|
||||
|
||||
public static bool TestCirclePointOverlap(Circle circle, Transform2D circleTransform, Point point, Transform2D pointTransform)
|
||||
{
|
||||
var circleCenter = circleTransform.Position;
|
||||
var circleRadius = circle.Radius * circleTransform.Scale.X;
|
||||
|
||||
var distanceX = circleCenter.X - pointTransform.Position.X;
|
||||
var distanceY = circleCenter.Y - pointTransform.Position.Y;
|
||||
|
||||
return (distanceX * distanceX) + (distanceY * distanceY) < (circleRadius * circleRadius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NOTE: The rectangle must be axis aligned, and the scaling of the circle must be uniform.
|
||||
/// </summary>
|
||||
public static bool TestCircleRectangleOverlap(Circle circle, Transform2D circleTransform, Rectangle rectangle, Transform2D rectangleTransform)
|
||||
{
|
||||
var circleCenter = circleTransform.Position;
|
||||
var circleRadius = circle.Radius * circleTransform.Scale.X;
|
||||
var AABB = rectangle.TransformedAABB(rectangleTransform);
|
||||
|
||||
var closestX = Math.MathHelper.Clamp(circleCenter.X, AABB.Left, AABB.Right);
|
||||
var closestY = Math.MathHelper.Clamp(circleCenter.Y, AABB.Top, AABB.Bottom);
|
||||
|
||||
var distanceX = circleCenter.X - closestX;
|
||||
var distanceY = circleCenter.Y - closestY;
|
||||
|
||||
var distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
|
||||
return distanceSquared < (circleRadius * circleRadius);
|
||||
}
|
||||
|
||||
public static bool TestCircleOverlap(Circle circleA, Transform2D transformA, Circle circleB, Transform2D transformB)
|
||||
{
|
||||
var radiusA = circleA.Radius * transformA.Scale.X;
|
||||
var radiusB = circleB.Radius * transformB.Scale.Y;
|
||||
|
||||
var centerA = transformA.Position;
|
||||
var centerB = transformB.Position;
|
||||
|
||||
var distanceSquared = (centerA - centerB).LengthSquared();
|
||||
var radiusSumSquared = (radiusA + radiusB) * (radiusA + radiusB);
|
||||
|
||||
return distanceSquared < radiusSumSquared;
|
||||
}
|
||||
|
||||
public static (bool, Simplex2D) FindCollisionSimplex(IShape2D shapeA, Transform2D transformA, IShape2D shapeB, Transform2D transformB)
|
||||
{
|
||||
var minkowskiDifference = new MinkowskiDifference(shapeA, transformA, shapeB, transformB);
|
||||
var c = minkowskiDifference.Support(Vector2.UnitX);
|
||||
var b = minkowskiDifference.Support(-Vector2.UnitX);
|
||||
return Check(minkowskiDifference, c, b);
|
||||
}
|
||||
|
||||
public unsafe static Vector2 Intersect(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Simplex2D simplex)
|
||||
{
|
||||
if (shapeA == null) { throw new System.ArgumentNullException(nameof(shapeA)); }
|
||||
if (shapeB == null) { throw new System.ArgumentNullException(nameof(shapeB)); }
|
||||
if (!simplex.TwoSimplex) { throw new System.ArgumentException("Simplex must be a 2-Simplex.", nameof(simplex)); }
|
||||
|
||||
var a = simplex.A;
|
||||
var b = simplex.B.Value;
|
||||
var c = simplex.C.Value;
|
||||
|
||||
Vector2 intersection = default;
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
var edge = FindClosestEdge(simplex);
|
||||
var support = CalculateSupport(shapeA, Transform2DA, shapeB, Transform2DB, edge.Normal);
|
||||
var distance = Vector2.Dot(support, edge.Normal);
|
||||
|
||||
intersection = edge.Normal;
|
||||
intersection *= distance;
|
||||
|
||||
if (System.Math.Abs(distance - edge.Distance) <= 0.00001f)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
else
|
||||
{
|
||||
simplex.Insert(support, edge.Index);
|
||||
}
|
||||
}
|
||||
|
||||
return intersection; // close enough
|
||||
}
|
||||
|
||||
private static unsafe Edge FindClosestEdge(Simplex2D simplex)
|
||||
{
|
||||
var closestDistance = float.PositiveInfinity;
|
||||
var closestNormal = Vector2.Zero;
|
||||
var closestIndex = 0;
|
||||
|
||||
for (var i = 0; i < 4; i += 1)
|
||||
{
|
||||
var j = (i + 1 == 3) ? 0 : i + 1;
|
||||
|
||||
var a = simplex[i];
|
||||
var b = simplex[j];
|
||||
|
||||
var e = b - a;
|
||||
|
||||
var oa = a;
|
||||
|
||||
var n = Vector2.Normalize(TripleProduct(e, oa, e));
|
||||
|
||||
var d = Vector2.Dot(n, a);
|
||||
|
||||
if (d < closestDistance)
|
||||
{
|
||||
closestDistance = d;
|
||||
closestNormal = n;
|
||||
closestIndex = j;
|
||||
}
|
||||
}
|
||||
|
||||
return new Edge
|
||||
{
|
||||
Distance = closestDistance,
|
||||
Normal = closestNormal,
|
||||
Index = closestIndex
|
||||
};
|
||||
}
|
||||
|
||||
private static Vector2 CalculateSupport(IShape2D shapeA, Transform2D Transform2DA, IShape2D shapeB, Transform2D Transform2DB, Vector2 direction)
|
||||
{
|
||||
return shapeA.Support(direction, Transform2DA) - shapeB.Support(-direction, Transform2DB);
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D) Check(MinkowskiDifference minkowskiDifference, Vector2 c, Vector2 b)
|
||||
{
|
||||
var cb = c - b;
|
||||
var c0 = -c;
|
||||
var d = Direction(cb, c0);
|
||||
return DoSimplex(minkowskiDifference, new Simplex2D(b, c), d);
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D) DoSimplex(MinkowskiDifference minkowskiDifference, Simplex2D simplex, Vector2 direction)
|
||||
{
|
||||
var a = minkowskiDifference.Support(direction);
|
||||
var notPastOrigin = Vector2.Dot(a, direction) < 0;
|
||||
var (intersects, newSimplex, newDirection) = EnclosesOrigin(a, simplex);
|
||||
|
||||
if (notPastOrigin)
|
||||
{
|
||||
return (false, default(Simplex2D));
|
||||
}
|
||||
else if (intersects)
|
||||
{
|
||||
return (true, new Simplex2D(simplex.A, simplex.B.Value, a));
|
||||
}
|
||||
else
|
||||
{
|
||||
return DoSimplex(minkowskiDifference, newSimplex, newDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D, Vector2) EnclosesOrigin(Vector2 a, Simplex2D simplex)
|
||||
{
|
||||
if (simplex.ZeroSimplex)
|
||||
{
|
||||
return HandleZeroSimplex(a, simplex.A);
|
||||
}
|
||||
else if (simplex.OneSimplex)
|
||||
{
|
||||
return HandleOneSimplex(a, simplex.A, simplex.B.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false, simplex, Vector2.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D, Vector2) HandleZeroSimplex(Vector2 a, Vector2 b)
|
||||
{
|
||||
var ab = b - a;
|
||||
var a0 = -a;
|
||||
var (newSimplex, newDirection) = SameDirection(ab, a0) ? (new Simplex2D(a, b), Perpendicular(ab, a0)) : (new Simplex2D(a), a0);
|
||||
return (false, newSimplex, newDirection);
|
||||
}
|
||||
|
||||
private static (bool, Simplex2D, Vector2) HandleOneSimplex(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
var a0 = -a;
|
||||
var ab = b - a;
|
||||
var ac = c - a;
|
||||
var abp = Perpendicular(ab, -ac);
|
||||
var acp = Perpendicular(ac, -ab);
|
||||
|
||||
if (SameDirection(abp, a0))
|
||||
{
|
||||
if (SameDirection(ab, a0))
|
||||
{
|
||||
return (false, new Simplex2D(a, b), abp);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false, new Simplex2D(a), a0);
|
||||
}
|
||||
}
|
||||
else if (SameDirection(acp, a0))
|
||||
{
|
||||
if (SameDirection(ac, a0))
|
||||
{
|
||||
return (false, new Simplex2D(a, c), acp);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (false, new Simplex2D(a), a0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (true, new Simplex2D(b, c), a0);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 TripleProduct(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
var A = new Vector3(a.X, a.Y, 0);
|
||||
var B = new Vector3(b.X, b.Y, 0);
|
||||
var C = new Vector3(c.X, c.Y, 0);
|
||||
|
||||
var first = Vector3.Cross(A, B);
|
||||
var second = Vector3.Cross(first, C);
|
||||
|
||||
return new Vector2(second.X, second.Y);
|
||||
}
|
||||
|
||||
private static Vector2 Direction(Vector2 a, Vector2 b)
|
||||
{
|
||||
var d = TripleProduct(a, b, a);
|
||||
var collinear = d == Vector2.Zero;
|
||||
return collinear ? new Vector2(a.Y, -a.X) : d;
|
||||
}
|
||||
|
||||
private static bool SameDirection(Vector2 a, Vector2 b)
|
||||
{
|
||||
return Vector2.Dot(a, b) > 0;
|
||||
}
|
||||
|
||||
private static Vector2 Perpendicular(Vector2 a, Vector2 b)
|
||||
{
|
||||
return TripleProduct(a, b, a);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A Circle is a shape defined by a radius.
|
||||
/// </summary>
|
||||
public struct Circle : IShape2D, System.IEquatable<Circle>
|
||||
{
|
||||
public float Radius { get; }
|
||||
public AABB2D AABB { get; }
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Circle(float radius)
|
||||
{
|
||||
Radius = radius;
|
||||
AABB = new AABB2D(-Radius, -Radius, Radius, Radius);
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
return Vector2.Transform(Vector2.Normalize(direction) * Radius, transform.TransformMatrix);
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform2D)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform2D);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return other is Circle circle && Equals(circle);
|
||||
}
|
||||
|
||||
public bool Equals(Circle other)
|
||||
{
|
||||
return Radius == other.Radius;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Radius);
|
||||
}
|
||||
|
||||
public static bool operator ==(Circle a, Circle b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Circle a, Circle b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A line is a shape defined by exactly two points in space.
|
||||
/// </summary>
|
||||
public struct Line : IShape2D, System.IEquatable<Line>
|
||||
{
|
||||
public Vector2 Start { get; }
|
||||
public Vector2 End { get; }
|
||||
|
||||
public AABB2D AABB { get; }
|
||||
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Line(Vector2 start, Vector2 end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
|
||||
AABB = new AABB2D(
|
||||
System.Math.Min(Start.X, End.X),
|
||||
System.Math.Min(Start.Y, End.Y),
|
||||
System.Math.Max(Start.X, End.X),
|
||||
System.Math.Max(Start.Y, End.Y)
|
||||
);
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
var transformedStart = Vector2.Transform(Start, transform.TransformMatrix);
|
||||
var transformedEnd = Vector2.Transform(End, transform.TransformMatrix);
|
||||
return Vector2.Dot(transformedStart, direction) > Vector2.Dot(transformedEnd, direction) ?
|
||||
transformedStart :
|
||||
transformedEnd;
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return other is Line otherLine && Equals(otherLine);
|
||||
}
|
||||
|
||||
public bool Equals(Line other)
|
||||
{
|
||||
return
|
||||
(Start == other.Start && End == other.End) ||
|
||||
(End == other.Start && Start == other.End);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Start, End);
|
||||
}
|
||||
|
||||
public static bool operator ==(Line a, Line b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Line a, Line b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A Point is "that which has no part".
|
||||
/// All points by themselves are identical.
|
||||
/// </summary>
|
||||
public struct Point : IShape2D, System.IEquatable<Point>
|
||||
{
|
||||
public AABB2D AABB { get; }
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform);
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
return Vector2.Transform(Vector2.Zero, transform.TransformMatrix);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return other is Point otherPoint && Equals(otherPoint);
|
||||
}
|
||||
|
||||
public bool Equals(Point other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool operator ==(Point a, Point b)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool operator !=(Point a, Point b)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A rectangle is a shape defined by a width and height. The origin is the center of the rectangle.
|
||||
/// </summary>
|
||||
public struct Rectangle : IShape2D, System.IEquatable<Rectangle>
|
||||
{
|
||||
public AABB2D AABB { get; }
|
||||
public float Width { get; }
|
||||
public float Height { get; }
|
||||
|
||||
public float Right { get; }
|
||||
public float Left { get; }
|
||||
public float Top { get; }
|
||||
public float Bottom { get; }
|
||||
public Vector2 TopLeft { get; }
|
||||
public Vector2 BottomRight { get; }
|
||||
|
||||
public Vector2 Min { get; }
|
||||
public Vector2 Max { get; }
|
||||
|
||||
public IEnumerable<IShape2D> Shapes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Rectangle(float left, float top, float width, float height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Left = left;
|
||||
Right = left + width;
|
||||
Top = top;
|
||||
Bottom = top + height;
|
||||
AABB = new AABB2D(left, top, Right, Bottom);
|
||||
TopLeft = new Vector2(Left, Top);
|
||||
BottomRight = new Vector2(Right, Bottom);
|
||||
Min = AABB.Min;
|
||||
Max = AABB.Max;
|
||||
}
|
||||
|
||||
private Vector2 Support(Vector2 direction)
|
||||
{
|
||||
if (direction.X >= 0 && direction.Y >= 0)
|
||||
{
|
||||
return Max;
|
||||
}
|
||||
else if (direction.X >= 0 && direction.Y < 0)
|
||||
{
|
||||
return new Vector2(Max.X, Min.Y);
|
||||
}
|
||||
else if (direction.X < 0 && direction.Y >= 0)
|
||||
{
|
||||
return new Vector2(Min.X, Max.Y);
|
||||
}
|
||||
else if (direction.X < 0 && direction.Y < 0)
|
||||
{
|
||||
return new Vector2(Min.X, Min.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.ArgumentException("Support vector direction cannot be zero.");
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
Matrix3x2 inverseTransform;
|
||||
Matrix3x2.Invert(transform.TransformMatrix, out inverseTransform);
|
||||
var inverseDirection = Vector2.TransformNormal(direction, inverseTransform);
|
||||
return Vector2.Transform(Support(inverseDirection), transform.TransformMatrix);
|
||||
}
|
||||
|
||||
public AABB2D TransformedAABB(Transform2D transform)
|
||||
{
|
||||
return AABB2D.Transformed(AABB, transform);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is IShape2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(IShape2D other)
|
||||
{
|
||||
return (other is Rectangle rectangle && Equals(rectangle));
|
||||
}
|
||||
|
||||
public bool Equals(Rectangle other)
|
||||
{
|
||||
return Min == other.Min && Max == other.Max;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Min, Max);
|
||||
}
|
||||
|
||||
public static bool operator ==(Rectangle a, Rectangle b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Rectangle a, Rectangle b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A simplex is a shape with up to n - 2 vertices in the nth dimension.
|
||||
/// </summary>
|
||||
public struct Simplex2D : System.IEquatable<Simplex2D>
|
||||
{
|
||||
private Vector2 a;
|
||||
private Vector2? b;
|
||||
private Vector2? c;
|
||||
|
||||
public Vector2 A => a;
|
||||
public Vector2? B => b;
|
||||
public Vector2? C => c;
|
||||
|
||||
public bool ZeroSimplex { get { return !b.HasValue && !c.HasValue; } }
|
||||
public bool OneSimplex { get { return b.HasValue && !c.HasValue; } }
|
||||
public bool TwoSimplex { get { return b.HasValue && c.HasValue; } }
|
||||
|
||||
public int Count => TwoSimplex ? 3 : (OneSimplex ? 2 : 1);
|
||||
|
||||
public Simplex2D(Vector2 a)
|
||||
{
|
||||
this.a = a;
|
||||
b = null;
|
||||
c = null;
|
||||
}
|
||||
|
||||
public Simplex2D(Vector2 a, Vector2 b)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
c = null;
|
||||
}
|
||||
|
||||
public Simplex2D(Vector2 a, Vector2 b, Vector2 c)
|
||||
{
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public Vector2 this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index == 0) { return a; }
|
||||
if (index == 1) { return b.Value; }
|
||||
if (index == 2) { return c.Value; }
|
||||
throw new System.IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Vector2> Vertices
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return (Vector2) a;
|
||||
if (b.HasValue) { yield return (Vector2) b; }
|
||||
if (c.HasValue) { yield return (Vector2) c; }
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Support(Vector2 direction, Transform2D transform)
|
||||
{
|
||||
var maxDotProduct = float.NegativeInfinity;
|
||||
var maxVertex = a;
|
||||
foreach (var vertex in Vertices)
|
||||
{
|
||||
var transformed = Vector2.Transform(vertex, transform.TransformMatrix);
|
||||
var dot = Vector2.Dot(transformed, direction);
|
||||
if (dot > maxDotProduct)
|
||||
{
|
||||
maxVertex = transformed;
|
||||
maxDotProduct = dot;
|
||||
}
|
||||
}
|
||||
return maxVertex;
|
||||
}
|
||||
|
||||
public void Insert(Vector2 point, int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
c = b;
|
||||
b = a;
|
||||
a = point;
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
c = b;
|
||||
b = point;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = point;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Simplex2D other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(Simplex2D other)
|
||||
{
|
||||
if (Count != other.Count) { return false; }
|
||||
|
||||
return
|
||||
(A == other.A && B == other.B && C == other.C) ||
|
||||
(A == other.A && B == other.C && C == other.B) ||
|
||||
(A == other.B && B == other.A && C == other.C) ||
|
||||
(A == other.B && B == other.C && C == other.A) ||
|
||||
(A == other.C && B == other.A && C == other.B) ||
|
||||
(A == other.C && B == other.B && C == other.A);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Vertices);
|
||||
}
|
||||
|
||||
public static bool operator ==(Simplex2D a, Simplex2D b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Simplex2D a, Simplex2D b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
using System.Collections.Generic;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Collision.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to quickly check if two shapes are potentially overlapping.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that will be used to uniquely identify shape-transform pairs.</typeparam>
|
||||
public class SpatialHash2D<T> where T : System.IEquatable<T>
|
||||
{
|
||||
private readonly int cellSize;
|
||||
|
||||
private readonly Dictionary<long, HashSet<T>> hashDictionary = new Dictionary<long, HashSet<T>>();
|
||||
private readonly Dictionary<T, (ICollidable, Transform2D, uint)> IDLookup = new Dictionary<T, (ICollidable, Transform2D, uint)>();
|
||||
|
||||
public int MinX { get; private set; } = 0;
|
||||
public int MaxX { get; private set; } = 0;
|
||||
public int MinY { get; private set; } = 0;
|
||||
public int MaxY { get; private set; } = 0;
|
||||
|
||||
private Queue<HashSet<T>> hashSetPool = new Queue<HashSet<T>>();
|
||||
|
||||
public SpatialHash2D(int cellSize)
|
||||
{
|
||||
this.cellSize = cellSize;
|
||||
}
|
||||
|
||||
private (int, int) Hash(Vector2 position)
|
||||
{
|
||||
return ((int) System.Math.Floor(position.X / cellSize), (int) System.Math.Floor(position.Y / cellSize));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an element into the SpatialHash.
|
||||
/// </summary>
|
||||
/// <param name="id">A unique ID for the shape-transform pair.</param>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="transform2D"></param>
|
||||
/// <param name="collisionGroups">A bitmask value specifying the groups this object belongs to.</param>
|
||||
public void Insert(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
|
||||
{
|
||||
var box = shape.TransformedAABB(transform2D);
|
||||
var minHash = Hash(box.Min);
|
||||
var maxHash = Hash(box.Max);
|
||||
|
||||
foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
|
||||
{
|
||||
if (!hashDictionary.ContainsKey(key))
|
||||
{
|
||||
hashDictionary.Add(key, new HashSet<T>());
|
||||
}
|
||||
|
||||
hashDictionary[key].Add(id);
|
||||
IDLookup[id] = (shape, transform2D, collisionGroups);
|
||||
}
|
||||
|
||||
MinX = System.Math.Min(MinX, minHash.Item1);
|
||||
MinY = System.Math.Min(MinY, minHash.Item2);
|
||||
MaxX = System.Math.Max(MaxX, maxHash.Item1);
|
||||
MaxY = System.Math.Max(MaxY, maxHash.Item2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all the potential collisions of a shape-transform pair. Excludes any shape-transforms with the given ID.
|
||||
/// </summary>
|
||||
public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(T id, ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
|
||||
{
|
||||
var returned = AcquireHashSet();
|
||||
|
||||
var box = shape.TransformedAABB(transform2D);
|
||||
var (minX, minY) = Hash(box.Min);
|
||||
var (maxX, maxY) = Hash(box.Max);
|
||||
|
||||
if (minX < MinX) { minX = MinX; }
|
||||
if (maxX > MaxX) { maxX = MaxX; }
|
||||
if (minY < MinY) { minY = MinY; }
|
||||
if (maxY > MaxY) { maxY = MaxY; }
|
||||
|
||||
foreach (var key in Keys(minX, minY, maxX, maxY))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
foreach (var t in hashDictionary[key])
|
||||
{
|
||||
if (!returned.Contains(t))
|
||||
{
|
||||
var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
|
||||
if (!id.Equals(t) && ((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
|
||||
{
|
||||
returned.Add(t);
|
||||
yield return (t, otherShape, otherTransform, collisionGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeHashSet(returned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all the potential collisions of a shape-transform pair.
|
||||
/// </summary>
|
||||
public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(ICollidable shape, Transform2D transform2D, uint collisionMask = uint.MaxValue)
|
||||
{
|
||||
var returned = AcquireHashSet();
|
||||
|
||||
var box = shape.TransformedAABB(transform2D);
|
||||
var (minX, minY) = Hash(box.Min);
|
||||
var (maxX, maxY) = Hash(box.Max);
|
||||
|
||||
if (minX < MinX) { minX = MinX; }
|
||||
if (maxX > MaxX) { maxX = MaxX; }
|
||||
if (minY < MinY) { minY = MinY; }
|
||||
if (maxY > MaxY) { maxY = MaxY; }
|
||||
|
||||
foreach (var key in Keys(minX, minY, maxX, maxY))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
foreach (var t in hashDictionary[key])
|
||||
{
|
||||
if (!returned.Contains(t))
|
||||
{
|
||||
var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
|
||||
if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(box, otherShape.TransformedAABB(otherTransform)))
|
||||
{
|
||||
returned.Add(t);
|
||||
yield return (t, otherShape, otherTransform, collisionGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeHashSet(returned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves objects based on a pre-transformed AABB.
|
||||
/// </summary>
|
||||
/// <param name="aabb">A transformed AABB.</param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<(T, ICollidable, Transform2D, uint)> Retrieve(AABB2D aabb, uint collisionMask = uint.MaxValue)
|
||||
{
|
||||
var returned = AcquireHashSet();
|
||||
|
||||
var (minX, minY) = Hash(aabb.Min);
|
||||
var (maxX, maxY) = Hash(aabb.Max);
|
||||
|
||||
if (minX < MinX) { minX = MinX; }
|
||||
if (maxX > MaxX) { maxX = MaxX; }
|
||||
if (minY < MinY) { minY = MinY; }
|
||||
if (maxY > MaxY) { maxY = MaxY; }
|
||||
|
||||
foreach (var key in Keys(minX, minY, maxX, maxY))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
foreach (var t in hashDictionary[key])
|
||||
{
|
||||
if (!returned.Contains(t))
|
||||
{
|
||||
var (otherShape, otherTransform, collisionGroups) = IDLookup[t];
|
||||
if (((collisionGroups & collisionMask) > 0) && AABB2D.TestOverlap(aabb, otherShape.TransformedAABB(otherTransform)))
|
||||
{
|
||||
yield return (t, otherShape, otherTransform, collisionGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeHashSet(returned);
|
||||
}
|
||||
|
||||
public void Update(T id, ICollidable shape, Transform2D transform2D, uint collisionGroups = uint.MaxValue)
|
||||
{
|
||||
Remove(id);
|
||||
Insert(id, shape, transform2D, collisionGroups);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific ID from the SpatialHash.
|
||||
/// </summary>
|
||||
public void Remove(T id)
|
||||
{
|
||||
var (shape, transform, collisionGroups) = IDLookup[id];
|
||||
|
||||
var box = shape.TransformedAABB(transform);
|
||||
var minHash = Hash(box.Min);
|
||||
var maxHash = Hash(box.Max);
|
||||
|
||||
foreach (var key in Keys(minHash.Item1, minHash.Item2, maxHash.Item1, maxHash.Item2))
|
||||
{
|
||||
if (hashDictionary.ContainsKey(key))
|
||||
{
|
||||
hashDictionary[key].Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
IDLookup.Remove(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes everything that has been inserted into the SpatialHash.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var hash in hashDictionary.Values)
|
||||
{
|
||||
hash.Clear();
|
||||
}
|
||||
|
||||
IDLookup.Clear();
|
||||
}
|
||||
|
||||
private static long MakeLong(int left, int right)
|
||||
{
|
||||
return ((long) left << 32) | ((uint) right);
|
||||
}
|
||||
|
||||
private IEnumerable<long> Keys(int minX, int minY, int maxX, int maxY)
|
||||
{
|
||||
for (var i = minX; i <= maxX; i++)
|
||||
{
|
||||
for (var j = minY; j <= maxY; j++)
|
||||
{
|
||||
yield return MakeLong(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<T> AcquireHashSet()
|
||||
{
|
||||
if (hashSetPool.Count == 0)
|
||||
{
|
||||
hashSetPool.Enqueue(new HashSet<T>());
|
||||
}
|
||||
|
||||
var hashSet = hashSetPool.Dequeue();
|
||||
hashSet.Clear();
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
private void FreeHashSet(HashSet<T> hashSet)
|
||||
{
|
||||
hashSetPool.Enqueue(hashSet);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace MoonWorks
|
||||
{
|
||||
public enum FramerateMode
|
||||
{
|
||||
Uncapped,
|
||||
Capped
|
||||
}
|
||||
|
||||
public struct FramerateSettings
|
||||
{
|
||||
public FramerateMode Mode;
|
||||
public int Cap;
|
||||
}
|
||||
}
|
208
src/Game.cs
208
src/Game.cs
|
@ -3,7 +3,6 @@ using SDL2;
|
|||
using MoonWorks.Audio;
|
||||
using MoonWorks.Graphics;
|
||||
using MoonWorks.Input;
|
||||
using MoonWorks.Window;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@ -13,14 +12,13 @@ namespace MoonWorks
|
|||
public abstract class Game
|
||||
{
|
||||
public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
|
||||
public TimeSpan Timestep { get; private set; }
|
||||
|
||||
private bool quit = false;
|
||||
bool debugMode;
|
||||
|
||||
private Stopwatch gameTimer;
|
||||
private TimeSpan timestep;
|
||||
private long previousTicks = 0;
|
||||
TimeSpan accumulatedElapsedTime = TimeSpan.Zero;
|
||||
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;
|
||||
|
@ -28,7 +26,10 @@ namespace MoonWorks
|
|||
private int sleepTimeIndex = 0;
|
||||
private TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1);
|
||||
|
||||
public OSWindow Window { get; }
|
||||
private bool FramerateCapped = false;
|
||||
private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero;
|
||||
|
||||
public Window Window { get; }
|
||||
public GraphicsDevice GraphicsDevice { get; }
|
||||
public AudioDevice AudioDevice { get; }
|
||||
public Inputs Inputs { get; }
|
||||
|
@ -44,13 +45,21 @@ namespace MoonWorks
|
|||
public Game(
|
||||
WindowCreateInfo windowCreateInfo,
|
||||
PresentMode presentMode,
|
||||
FramerateSettings framerateSettings,
|
||||
int targetTimestep = 60,
|
||||
bool debugMode = false
|
||||
)
|
||||
{
|
||||
timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||
gameTimer = Stopwatch.StartNew();
|
||||
|
||||
FramerateCapped = framerateSettings.Mode == FramerateMode.Capped;
|
||||
|
||||
if (FramerateCapped)
|
||||
{
|
||||
FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / framerateSettings.Cap);
|
||||
}
|
||||
|
||||
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
||||
{
|
||||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
||||
|
@ -66,7 +75,7 @@ namespace MoonWorks
|
|||
|
||||
Inputs = new Inputs();
|
||||
|
||||
Window = new OSWindow(windowCreateInfo);
|
||||
Window = new Window(windowCreateInfo);
|
||||
|
||||
GraphicsDevice = new GraphicsDevice(
|
||||
Window.Handle,
|
||||
|
@ -75,22 +84,51 @@ namespace MoonWorks
|
|||
);
|
||||
|
||||
AudioDevice = new AudioDevice();
|
||||
|
||||
this.debugMode = debugMode;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
while (!quit)
|
||||
{
|
||||
AdvanceElapsedTime();
|
||||
Tick();
|
||||
}
|
||||
|
||||
/* We want to wait until the next frame,
|
||||
* 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 (accumulatedElapsedTime + worstCaseSleepPrecision < timestep)
|
||||
Destroy();
|
||||
|
||||
AudioDevice.Dispose();
|
||||
GraphicsDevice.Dispose();
|
||||
Window.Dispose();
|
||||
|
||||
SDL.SDL_Quit();
|
||||
}
|
||||
|
||||
protected abstract void Update(TimeSpan delta);
|
||||
protected abstract void Draw(double alpha);
|
||||
protected virtual void Destroy() {}
|
||||
|
||||
// Called when a file is dropped on the game window.
|
||||
protected virtual void DropFile(string filePath) {}
|
||||
|
||||
/* Required to distinguish between multiple files dropped at once
|
||||
* vs multiple files dropped one at a time.
|
||||
*
|
||||
* Called once for every multi-file drop.
|
||||
*/
|
||||
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();
|
||||
|
@ -98,55 +136,43 @@ namespace MoonWorks
|
|||
}
|
||||
|
||||
/* 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 (accumulatedElapsedTime < timestep)
|
||||
* 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 (accumulatedElapsedTime > MAX_DELTA_TIME)
|
||||
{
|
||||
accumulatedElapsedTime = MAX_DELTA_TIME;
|
||||
}
|
||||
|
||||
if (!quit)
|
||||
{
|
||||
while (accumulatedElapsedTime >= timestep)
|
||||
{
|
||||
Inputs.Mouse.Wheel = 0;
|
||||
|
||||
Inputs.Update();
|
||||
AudioDevice.Update();
|
||||
|
||||
Update(timestep);
|
||||
|
||||
accumulatedElapsedTime -= timestep;
|
||||
}
|
||||
|
||||
var alpha = accumulatedElapsedTime / timestep;
|
||||
|
||||
Draw(timestep, alpha);
|
||||
|
||||
}
|
||||
|
||||
GraphicsDevice.SubmitDestroyCommandBuffer();
|
||||
}
|
||||
|
||||
OnDestroy();
|
||||
// Now that we are going to perform an update, let's handle SDL events.
|
||||
HandleSDLEvents();
|
||||
|
||||
AudioDevice.Dispose();
|
||||
GraphicsDevice.Dispose();
|
||||
Window.Dispose();
|
||||
// Do not let any step take longer than our maximum.
|
||||
if (accumulatedUpdateTime > MAX_DELTA_TIME)
|
||||
{
|
||||
accumulatedUpdateTime = MAX_DELTA_TIME;
|
||||
}
|
||||
|
||||
SDL.SDL_Quit();
|
||||
if (!quit)
|
||||
{
|
||||
while (accumulatedUpdateTime >= Timestep)
|
||||
{
|
||||
Inputs.Update();
|
||||
AudioDevice.Update();
|
||||
|
||||
Update(Timestep);
|
||||
|
||||
accumulatedUpdateTime -= Timestep;
|
||||
}
|
||||
|
||||
var alpha = accumulatedUpdateTime / Timestep;
|
||||
|
||||
Draw(alpha);
|
||||
accumulatedDrawTime -= FramerateCapTimeSpan;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSDLEvents()
|
||||
|
@ -166,19 +192,43 @@ namespace MoonWorks
|
|||
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
|
||||
Inputs.Mouse.Wheel += _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void Update(TimeSpan dt);
|
||||
private void HandleWindowEvent(SDL.SDL_Event evt)
|
||||
{
|
||||
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
|
||||
{
|
||||
Window.SizeChanged((uint) evt.window.data1, (uint) evt.window.data2);
|
||||
}
|
||||
}
|
||||
|
||||
// alpha refers to a percentage value between the current and next state
|
||||
protected abstract void Draw(TimeSpan dt, double alpha);
|
||||
|
||||
// Clean up any objects you created in this function
|
||||
protected abstract void OnDestroy();
|
||||
|
||||
private void HandleTextInput(SDL2.SDL.SDL_Event evt)
|
||||
private void HandleTextInput(SDL.SDL_Event evt)
|
||||
{
|
||||
// Based on the SDL2# LPUtf8StrMarshaler
|
||||
unsafe
|
||||
|
@ -206,11 +256,35 @@ namespace MoonWorks
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
System.Console.WriteLine($"New controller detected!");
|
||||
Inputs.AddGamepad(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleControllerRemoved(SDL.SDL_Event evt)
|
||||
{
|
||||
System.Console.WriteLine($"Controller removal detected!");
|
||||
Inputs.RemoveGamepad(evt.cdevice.which);
|
||||
}
|
||||
|
||||
private TimeSpan AdvanceElapsedTime()
|
||||
{
|
||||
long currentTicks = gameTimer.Elapsed.Ticks;
|
||||
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
|
||||
accumulatedElapsedTime += timeAdvanced;
|
||||
accumulatedUpdateTime += timeAdvanced;
|
||||
accumulatedDrawTime += timeAdvanced;
|
||||
previousTicks = currentTicks;
|
||||
return timeAdvanced;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
namespace MoonWorks.Graphics
|
||||
using System;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct TextureSamplerBinding
|
||||
{
|
||||
public Texture Texture;
|
||||
public Sampler Sampler;
|
||||
public IntPtr TextureHandle;
|
||||
public IntPtr SamplerHandle;
|
||||
|
||||
public TextureSamplerBinding(Texture texture, Sampler sampler)
|
||||
{
|
||||
Texture = texture;
|
||||
Sampler = sampler;
|
||||
TextureHandle = texture.Handle;
|
||||
SamplerHandle = sampler.Handle;
|
||||
}
|
||||
|
||||
public TextureSamplerBinding(IntPtr textureHandle, IntPtr samplerHandle)
|
||||
{
|
||||
TextureHandle = textureHandle;
|
||||
SamplerHandle = samplerHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ using System;
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Window;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
@ -22,6 +19,8 @@ namespace MoonWorks.Graphics
|
|||
Handle = handle;
|
||||
}
|
||||
|
||||
// FIXME: we can probably use the NativeMemory functions to not have to generate arrays here
|
||||
|
||||
/// <summary>
|
||||
/// Begins a render pass.
|
||||
/// All render state, resource binding, and draw commands must be made within a render pass.
|
||||
|
@ -58,7 +57,7 @@ namespace MoonWorks.Graphics
|
|||
Refresh.Refresh_BeginRenderPass(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
colorAttachmentInfos[0].renderTarget.TextureSlice.Rectangle.ToRefresh(),
|
||||
IntPtr.Zero,
|
||||
(IntPtr) pColorAttachmentInfos,
|
||||
(uint) colorAttachmentInfos.Length,
|
||||
IntPtr.Zero
|
||||
|
@ -106,7 +105,7 @@ namespace MoonWorks.Graphics
|
|||
Refresh.Refresh_BeginRenderPass(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
colorAttachmentInfos[0].renderTarget.TextureSlice.Rectangle.ToRefresh(),
|
||||
IntPtr.Zero,
|
||||
pColorAttachmentInfos,
|
||||
(uint) colorAttachmentInfos.Length,
|
||||
&refreshDepthStencilAttachmentInfo
|
||||
|
@ -308,6 +307,30 @@ namespace MoonWorks.Graphics
|
|||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the viewport. Only valid during a render pass.
|
||||
/// </summary>
|
||||
public void SetViewport(Viewport viewport)
|
||||
{
|
||||
Refresh.Refresh_SetViewport(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
viewport.ToRefresh()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the scissor area. Only valid during a render pass.
|
||||
/// </summary>
|
||||
public void SetScissor(Rect scissor)
|
||||
{
|
||||
Refresh.Refresh_SetScissor(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
scissor.ToRefresh()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds vertex buffers to be used by subsequent draw calls.
|
||||
/// </summary>
|
||||
|
@ -400,8 +423,8 @@ namespace MoonWorks.Graphics
|
|||
|
||||
for (var i = 0; i < length; i += 1)
|
||||
{
|
||||
texturePtrs[i] = textureSamplerBindings[i].Texture.Handle;
|
||||
samplerPtrs[i] = textureSamplerBindings[i].Sampler.Handle;
|
||||
texturePtrs[i] = textureSamplerBindings[i].TextureHandle;
|
||||
samplerPtrs[i] = textureSamplerBindings[i].SamplerHandle;
|
||||
}
|
||||
|
||||
Refresh.Refresh_BindVertexSamplers(
|
||||
|
@ -438,8 +461,19 @@ namespace MoonWorks.Graphics
|
|||
|
||||
for (var i = 0; i < length; i += 1)
|
||||
{
|
||||
texturePtrs[i] = textureSamplerBindings[i].Texture.Handle;
|
||||
samplerPtrs[i] = textureSamplerBindings[i].Sampler.Handle;
|
||||
#if DEBUG
|
||||
if (textureSamplerBindings[i].TextureHandle == IntPtr.Zero)
|
||||
{
|
||||
throw new NullReferenceException("Texture binding must not be null!");
|
||||
}
|
||||
if (textureSamplerBindings[i].TextureHandle == IntPtr.Zero)
|
||||
{
|
||||
throw new NullReferenceException("Sampler binding must not be null!");
|
||||
}
|
||||
#endif
|
||||
|
||||
texturePtrs[i] = textureSamplerBindings[i].TextureHandle;
|
||||
samplerPtrs[i] = textureSamplerBindings[i].SamplerHandle;
|
||||
}
|
||||
|
||||
Refresh.Refresh_BindFragmentSamplers(
|
||||
|
@ -475,7 +509,7 @@ namespace MoonWorks.Graphics
|
|||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
(uint) (uniforms.Length * Marshal.SizeOf<T>())
|
||||
(uint) (uniforms.Length * sizeof(T))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +528,7 @@ namespace MoonWorks.Graphics
|
|||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
(uint) (uniforms.Length * Marshal.SizeOf<T>())
|
||||
(uint) (uniforms.Length * sizeof(T))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -513,49 +547,11 @@ namespace MoonWorks.Graphics
|
|||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
(uint) (uniforms.Length * Marshal.SizeOf<T>())
|
||||
(uint) (uniforms.Length * sizeof(T))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the render targets on the current framebuffer to a single color or depth/stencil value.
|
||||
/// NOTE: It is recommended that you clear when beginning render passes unless you have a good reason to clear mid-pass.
|
||||
/// </summary>
|
||||
/// <param name="clearRect">The area of the framebuffer to clear.</param>
|
||||
/// <param name="clearOptions">Whether to clear colors, depth, or stencil value, or multiple.</param>
|
||||
/// <param name="depthStencilClearValue">The depth/stencil clear values. Will be ignored if color is not provided in ClearOptions.</param>
|
||||
/// <param name="clearColors">The color clear values. Must provide one per render target. Can be omitted if depth/stencil is not cleared.</param>
|
||||
public unsafe void Clear(
|
||||
in Rect clearRect,
|
||||
ClearOptionsFlags clearOptions,
|
||||
in DepthStencilValue depthStencilClearValue,
|
||||
params Vector4[] clearColors
|
||||
)
|
||||
{
|
||||
Refresh.Vec4* colors = stackalloc Refresh.Vec4[clearColors.Length];
|
||||
for (var i = 0; i < clearColors.Length; i++)
|
||||
{
|
||||
colors[i] = new Refresh.Vec4
|
||||
{
|
||||
x = clearColors[i].X,
|
||||
y = clearColors[i].Y,
|
||||
z = clearColors[i].Z,
|
||||
w = clearColors[i].W
|
||||
};
|
||||
}
|
||||
|
||||
Refresh.Refresh_Clear(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
clearRect.ToRefresh(),
|
||||
(Refresh.ClearOptionsFlags) clearOptions,
|
||||
(IntPtr) colors,
|
||||
(uint) clearColors.Length,
|
||||
depthStencilClearValue.ToRefresh()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws using instanced rendering.
|
||||
/// It is an error to call this method unless two vertex buffers have been bound.
|
||||
|
@ -651,126 +647,35 @@ namespace MoonWorks.Graphics
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a texture to be presented to a window.
|
||||
/// This particular variant of this method will present to the entire window area.
|
||||
/// Acquires a swapchain texture.
|
||||
/// This texture will be presented to the given window when the command buffer is submitted.
|
||||
/// Can return null if the swapchain is unavailable. The user should ALWAYS handle the case where this occurs.
|
||||
/// If null is returned, presentation will not occur.
|
||||
/// It is an error to acquire two swapchain textures from the same window in one command buffer.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to present.</param>
|
||||
/// <param name="filter">The filter to use when the texture size differs from the window size.</param>
|
||||
public void QueuePresent(
|
||||
Texture texture,
|
||||
Filter filter,
|
||||
OSWindow window
|
||||
public Texture? AcquireSwapchainTexture(
|
||||
Window window
|
||||
)
|
||||
{
|
||||
var refreshTextureSlice = new Refresh.TextureSlice
|
||||
var texturePtr = Refresh.Refresh_AcquireSwapchainTexture(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
window.Handle,
|
||||
out var width,
|
||||
out var height
|
||||
);
|
||||
|
||||
if (texturePtr == IntPtr.Zero)
|
||||
{
|
||||
texture = texture.Handle,
|
||||
rectangle = new Refresh.Rect
|
||||
{
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = (int) texture.Width,
|
||||
h = (int) texture.Height
|
||||
},
|
||||
layer = 0,
|
||||
level = 0,
|
||||
depth = 0
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
Refresh.Refresh_QueuePresent(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
refreshTextureSlice,
|
||||
IntPtr.Zero,
|
||||
(Refresh.Filter) filter,
|
||||
window.Handle
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a texture slice to be presented to a window.
|
||||
/// This particular variant of this method will present to the entire window area.
|
||||
/// </summary>
|
||||
/// <param name="textureSlice">The texture slice to present.</param>
|
||||
/// <param name="filter">The filter to use when the texture size differs from the window size.</param>
|
||||
public void QueuePresent(
|
||||
in TextureSlice textureSlice,
|
||||
Filter filter,
|
||||
OSWindow window
|
||||
)
|
||||
{
|
||||
Refresh.Refresh_QueuePresent(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
textureSlice.ToRefreshTextureSlice(),
|
||||
IntPtr.Zero,
|
||||
(Refresh.Filter) filter,
|
||||
window.Handle
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a texture to be presented to a window.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to present.</param>
|
||||
/// <param name="destinationRectangle">The area of the window to present to.</param>
|
||||
/// <param name="filter">The filter to use when the texture size differs from the destination rectangle.</param>
|
||||
public void QueuePresent(
|
||||
in Texture texture,
|
||||
in Rect destinationRectangle,
|
||||
Filter filter,
|
||||
OSWindow window
|
||||
)
|
||||
{
|
||||
var refreshRect = destinationRectangle.ToRefresh();
|
||||
var refreshTextureSlice = new Refresh.TextureSlice
|
||||
{
|
||||
texture = texture.Handle,
|
||||
rectangle = new Refresh.Rect
|
||||
{
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = (int) texture.Width,
|
||||
h = (int) texture.Height
|
||||
},
|
||||
layer = 0,
|
||||
level = 0,
|
||||
depth = 0
|
||||
};
|
||||
|
||||
Refresh.Refresh_QueuePresent(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
refreshTextureSlice,
|
||||
refreshRect,
|
||||
(Refresh.Filter) filter,
|
||||
window.Handle
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a texture slice to be presented to a window.
|
||||
/// </summary>
|
||||
/// <param name="textureSlice">The texture slice to present.</param>
|
||||
/// <param name="destinationRectangle">The area of the window to present to.</param>
|
||||
/// <param name="filter">The filter to use when the texture size differs from the destination rectangle.</param>
|
||||
public void QueuePresent(
|
||||
in TextureSlice textureSlice,
|
||||
in Rect destinationRectangle,
|
||||
Filter filter,
|
||||
OSWindow window
|
||||
)
|
||||
{
|
||||
var refreshTextureSlice = textureSlice.ToRefreshTextureSlice();
|
||||
var refreshRect = destinationRectangle.ToRefresh();
|
||||
|
||||
Refresh.Refresh_QueuePresent(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
refreshTextureSlice,
|
||||
refreshRect,
|
||||
(Refresh.Filter) filter,
|
||||
window.Handle
|
||||
return new Texture(
|
||||
Device,
|
||||
texturePtr,
|
||||
Device.GetSwapchainFormat(window),
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -838,7 +743,7 @@ namespace MoonWorks.Graphics
|
|||
uint numElements
|
||||
) where T : unmanaged
|
||||
{
|
||||
var elementSize = Marshal.SizeOf<T>();
|
||||
var elementSize = sizeof(T);
|
||||
|
||||
fixed (T* ptr = &data[0])
|
||||
{
|
||||
|
@ -855,6 +760,22 @@ namespace MoonWorks.Graphics
|
|||
}
|
||||
}
|
||||
|
||||
public unsafe void SetBufferData<T>(
|
||||
Buffer buffer,
|
||||
IntPtr dataPtr,
|
||||
uint bufferOffsetInElements,
|
||||
uint numElements
|
||||
) where T : unmanaged {
|
||||
Refresh.Refresh_SetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
buffer.Handle,
|
||||
(uint) sizeof(T) * bufferOffsetInElements,
|
||||
dataPtr,
|
||||
(uint) sizeof(T) * numElements
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into a texture.
|
||||
/// </summary>
|
||||
|
@ -871,7 +792,7 @@ namespace MoonWorks.Graphics
|
|||
/// <param name="data">An array of data to copy into the texture.</param>
|
||||
public unsafe void SetTextureData<T>(in TextureSlice textureSlice, T[] data) where T : unmanaged
|
||||
{
|
||||
var size = Marshal.SizeOf<T>();
|
||||
var size = sizeof(T);
|
||||
|
||||
fixed (T* ptr = &data[0])
|
||||
{
|
||||
|
@ -912,6 +833,26 @@ namespace MoonWorks.Graphics
|
|||
SetTextureData(new TextureSlice(texture), dataPtr, dataLengthInBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies YUV data into three textures. Use with compressed video.
|
||||
/// </summary>
|
||||
public void SetTextureDataYUV(Texture yTexture, Texture uTexture, Texture vTexture, IntPtr dataPtr, uint dataLengthInBytes)
|
||||
{
|
||||
Refresh.Refresh_SetTextureDataYUV(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
yTexture.Handle,
|
||||
uTexture.Handle,
|
||||
vTexture.Handle,
|
||||
yTexture.Width,
|
||||
yTexture.Height,
|
||||
uTexture.Width,
|
||||
uTexture.Height,
|
||||
dataPtr,
|
||||
dataLengthInBytes
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an asynchronous texture-to-texture copy on the GPU.
|
||||
/// </summary>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public enum HorizontalAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum VerticalAlignment
|
||||
{
|
||||
Baseline,
|
||||
Top,
|
||||
Middle,
|
||||
Bottom
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public class Font : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public unsafe Font(string path)
|
||||
{
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
fixed (byte* pByte = &bytes[0])
|
||||
{
|
||||
Handle = Wellspring.Wellspring_CreateFont((IntPtr) pByte, (uint) bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Wellspring.Wellspring_DestroyFont(Handle);
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Font()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public class Packer : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
public Texture Texture { get; }
|
||||
|
||||
public Font Font { get; }
|
||||
|
||||
private byte[] StringBytes;
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public unsafe Packer(GraphicsDevice graphicsDevice, Font font, float fontSize, uint textureWidth, uint textureHeight, uint padding = 1)
|
||||
{
|
||||
Font = font;
|
||||
Handle = Wellspring.Wellspring_CreatePacker(Font.Handle, fontSize, textureWidth, textureHeight, 0, padding);
|
||||
Texture = Texture.CreateTexture2D(graphicsDevice, textureWidth, textureHeight, TextureFormat.R8, TextureUsageFlags.Sampler);
|
||||
StringBytes = new byte[128];
|
||||
}
|
||||
|
||||
public unsafe bool PackFontRanges(params FontRange[] fontRanges)
|
||||
{
|
||||
fixed (FontRange *pFontRanges = &fontRanges[0])
|
||||
{
|
||||
var nativeSize = fontRanges.Length * sizeof(Wellspring.FontRange);
|
||||
void* fontRangeMemory = NativeMemory.Alloc((nuint) fontRanges.Length, (nuint) sizeof(Wellspring.FontRange));
|
||||
System.Buffer.MemoryCopy(pFontRanges, fontRangeMemory, nativeSize, nativeSize);
|
||||
|
||||
var result = Wellspring.Wellspring_PackFontRanges(Handle, (IntPtr) fontRangeMemory, (uint) fontRanges.Length);
|
||||
|
||||
NativeMemory.Free(fontRangeMemory);
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void SetTextureData(CommandBuffer commandBuffer)
|
||||
{
|
||||
var pixelDataPointer = Wellspring.Wellspring_GetPixelDataPointer(Handle);
|
||||
commandBuffer.SetTextureData(Texture, pixelDataPointer, Texture.Width * Texture.Height);
|
||||
}
|
||||
|
||||
public unsafe void TextBounds(
|
||||
string text,
|
||||
float x,
|
||||
float y,
|
||||
HorizontalAlignment horizontalAlignment,
|
||||
VerticalAlignment verticalAlignment,
|
||||
out Wellspring.Rectangle rectangle
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytes.Length < byteCount)
|
||||
{
|
||||
System.Array.Resize(ref StringBytes, byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
fixed (byte* bytes = StringBytes)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
|
||||
Wellspring.Wellspring_TextBounds(
|
||||
Handle,
|
||||
x,
|
||||
y,
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) bytes,
|
||||
(uint) byteCount,
|
||||
out rectangle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
|
||||
Wellspring.Wellspring_DestroyPacker(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Packer()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FontRange
|
||||
{
|
||||
public uint FirstCodepoint;
|
||||
public uint NumChars;
|
||||
public byte OversampleH;
|
||||
public byte OversampleV;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector2 TexCoord;
|
||||
public Color Color;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public class TextBatch
|
||||
{
|
||||
private GraphicsDevice GraphicsDevice { get; }
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
public Buffer VertexBuffer { get; protected set; } = null;
|
||||
public Buffer IndexBuffer { get; protected set; } = null;
|
||||
public Texture Texture { get; protected set; }
|
||||
public uint PrimitiveCount { get; protected set; }
|
||||
|
||||
private byte[] StringBytes;
|
||||
|
||||
public TextBatch(GraphicsDevice graphicsDevice)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
Handle = Wellspring.Wellspring_CreateTextBatch();
|
||||
StringBytes = new byte[128];
|
||||
}
|
||||
|
||||
public void Start(Packer packer)
|
||||
{
|
||||
Wellspring.Wellspring_StartTextBatch(Handle, packer.Handle);
|
||||
Texture = packer.Texture;
|
||||
PrimitiveCount = 0;
|
||||
}
|
||||
|
||||
public unsafe void Draw(
|
||||
string text,
|
||||
float x,
|
||||
float y,
|
||||
float depth,
|
||||
Color color,
|
||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytes.Length < byteCount)
|
||||
{
|
||||
System.Array.Resize(ref StringBytes, byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
fixed (byte* bytes = StringBytes)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount);
|
||||
|
||||
var result = Wellspring.Wellspring_Draw(
|
||||
Handle,
|
||||
x,
|
||||
y,
|
||||
depth,
|
||||
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) bytes,
|
||||
(uint) byteCount
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
throw new System.ArgumentException("Could not decode string!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this after you have made all the Draw calls you want.
|
||||
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 == null)
|
||||
{
|
||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
}
|
||||
else if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
}
|
||||
|
||||
if (IndexBuffer == null)
|
||||
{
|
||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
||||
}
|
||||
else if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||
{
|
||||
IndexBuffer.Dispose();
|
||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes);
|
||||
}
|
||||
|
||||
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
|
||||
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
||||
|
||||
PrimitiveCount = vertexCount / 2; // FIXME: is this jank?
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
@ -8,10 +9,14 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
// Built-in video pipeline
|
||||
private ShaderModule VideoVertexShader { get; }
|
||||
private ShaderModule VideoFragmentShader { get; }
|
||||
internal GraphicsPipeline VideoPipeline { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
|
||||
private Dictionary<IntPtr, Action<IntPtr, IntPtr, IntPtr>> resourcesToDestroy = new Dictionary<IntPtr, Action<IntPtr, IntPtr, IntPtr>>();
|
||||
|
||||
public GraphicsDevice(
|
||||
IntPtr deviceWindowHandle,
|
||||
|
@ -29,6 +34,26 @@ namespace MoonWorks.Graphics
|
|||
presentationParameters,
|
||||
Conversions.BoolToByte(debugMode)
|
||||
);
|
||||
|
||||
VideoVertexShader = new ShaderModule(this, GetEmbeddedResource("MoonWorks.Shaders.FullscreenVert.spv"));
|
||||
VideoFragmentShader = new ShaderModule(this, GetEmbeddedResource("MoonWorks.Shaders.YUV2RGBAFrag.spv"));
|
||||
|
||||
VideoPipeline = new GraphicsPipeline(
|
||||
this,
|
||||
new GraphicsPipelineCreateInfo
|
||||
{
|
||||
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
||||
new ColorAttachmentDescription(TextureFormat.R8G8B8A8, ColorAttachmentBlendState.None)
|
||||
),
|
||||
DepthStencilState = DepthStencilState.Disable,
|
||||
VertexShaderInfo = GraphicsShaderInfo.Create(VideoVertexShader, "main", 0),
|
||||
FragmentShaderInfo = GraphicsShaderInfo.Create(VideoFragmentShader, "main", 3),
|
||||
VertexInputState = VertexInputState.Empty,
|
||||
RasterizerState = RasterizerState.CCW_CullNone,
|
||||
PrimitiveType = PrimitiveType.TriangleList,
|
||||
MultisampleState = MultisampleState.None
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public CommandBuffer AcquireCommandBuffer()
|
||||
|
@ -57,24 +82,9 @@ namespace MoonWorks.Graphics
|
|||
Refresh.Refresh_Wait(Handle);
|
||||
}
|
||||
|
||||
internal void SubmitDestroyCommandBuffer()
|
||||
public TextureFormat GetSwapchainFormat(Window window)
|
||||
{
|
||||
if (resourcesToDestroy.Count > 0)
|
||||
{
|
||||
var commandBuffer = AcquireCommandBuffer();
|
||||
|
||||
foreach (var kv in resourcesToDestroy)
|
||||
{
|
||||
kv.Value.Invoke(Handle, commandBuffer.Handle, kv.Key);
|
||||
}
|
||||
|
||||
Submit(commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
internal void PrepareDestroyResource(GraphicsResource resource, Action<IntPtr, IntPtr, IntPtr> destroyFunction)
|
||||
{
|
||||
resourcesToDestroy.Add(resource.Handle, destroyFunction);
|
||||
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
|
||||
}
|
||||
|
||||
internal void AddResourceReference(WeakReference<GraphicsResource> resourceReference)
|
||||
|
@ -93,6 +103,11 @@ namespace MoonWorks.Graphics
|
|||
}
|
||||
}
|
||||
|
||||
private static Stream GetEmbeddedResource(string name)
|
||||
{
|
||||
return typeof(GraphicsDevice).Assembly.GetManifestResourceStream(name);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
|
@ -112,7 +127,6 @@ namespace MoonWorks.Graphics
|
|||
resources.Clear();
|
||||
}
|
||||
|
||||
SubmitDestroyCommandBuffer();
|
||||
Refresh.Refresh_DestroyDevice(Handle);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,26 +8,28 @@ namespace MoonWorks.Graphics
|
|||
public IntPtr Handle { get; protected set; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
protected abstract Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
|
||||
private WeakReference<GraphicsResource> selfReference;
|
||||
|
||||
public GraphicsResource(GraphicsDevice device)
|
||||
public GraphicsResource(GraphicsDevice device, bool trackResource = true)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
selfReference = new WeakReference<GraphicsResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
if (trackResource)
|
||||
{
|
||||
selfReference = new WeakReference<GraphicsResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Device.PrepareDestroyResource(this, QueueDestroyFunction);
|
||||
|
||||
if (selfReference != null)
|
||||
{
|
||||
QueueDestroyFunction(Device.Handle, Handle);
|
||||
Device.RemoveResourceReference(selfReference);
|
||||
selfReference = null;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#endregion
|
||||
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
|
|
@ -60,12 +60,14 @@ namespace MoonWorks.Graphics
|
|||
public enum TextureFormat
|
||||
{
|
||||
R8G8B8A8,
|
||||
B8G8R8A8,
|
||||
R5G6B5,
|
||||
A1R5G5B5,
|
||||
B4G4R4A4,
|
||||
BC1,
|
||||
BC2,
|
||||
BC3,
|
||||
BC7,
|
||||
R8G8_SNORM,
|
||||
R8G8B8A8_SNORM,
|
||||
A2R10G10B10,
|
||||
|
@ -123,7 +125,8 @@ namespace MoonWorks.Graphics
|
|||
|
||||
public enum VertexElementFormat
|
||||
{
|
||||
Single,
|
||||
UInt,
|
||||
Float,
|
||||
Vector2,
|
||||
Vector3,
|
||||
Vector4,
|
||||
|
@ -146,16 +149,14 @@ namespace MoonWorks.Graphics
|
|||
public enum FillMode
|
||||
{
|
||||
Fill,
|
||||
Line,
|
||||
Point
|
||||
Line
|
||||
}
|
||||
|
||||
public enum CullMode
|
||||
{
|
||||
None,
|
||||
Front,
|
||||
Back,
|
||||
FrontAndBack
|
||||
Back
|
||||
}
|
||||
|
||||
public enum FrontFace
|
||||
|
@ -197,26 +198,6 @@ namespace MoonWorks.Graphics
|
|||
Max
|
||||
}
|
||||
|
||||
public enum LogicOp
|
||||
{
|
||||
Clear,
|
||||
And,
|
||||
AndReverse,
|
||||
Copy,
|
||||
AndInverted,
|
||||
NoOp,
|
||||
Xor,
|
||||
Or,
|
||||
Nor,
|
||||
Equivalent,
|
||||
Invert,
|
||||
OrReverse,
|
||||
CopyInverted,
|
||||
OrInverted,
|
||||
Nand,
|
||||
Set
|
||||
}
|
||||
|
||||
public enum BlendFactor
|
||||
{
|
||||
Zero,
|
||||
|
@ -231,8 +212,6 @@ namespace MoonWorks.Graphics
|
|||
OneMinusDestinationAlpha,
|
||||
ConstantColor,
|
||||
OneMinusConstantColor,
|
||||
ConstantAlpha,
|
||||
OneMinusConstantAlpha,
|
||||
SourceAlphaSaturate,
|
||||
SourceOneColor,
|
||||
OneMinusSourceOneColor,
|
||||
|
@ -263,17 +242,10 @@ namespace MoonWorks.Graphics
|
|||
None = 0
|
||||
}
|
||||
|
||||
public enum ShaderStageType
|
||||
{
|
||||
Vertex,
|
||||
Fragment
|
||||
}
|
||||
|
||||
public enum Filter
|
||||
{
|
||||
Nearest,
|
||||
Linear,
|
||||
Cubic
|
||||
Linear
|
||||
}
|
||||
|
||||
public enum SamplerMipmapMode
|
||||
|
|
|
@ -12,6 +12,12 @@ namespace MoonWorks.Graphics
|
|||
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()
|
||||
{
|
||||
|
@ -31,6 +37,22 @@ namespace MoonWorks.Graphics
|
|||
public int W;
|
||||
public int H;
|
||||
|
||||
public Rect(int x, int y, int w, int h)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
W = w;
|
||||
H = h;
|
||||
}
|
||||
|
||||
public Rect(int w, int h)
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
W = w;
|
||||
H = h;
|
||||
}
|
||||
|
||||
// FIXME: can we do an unsafe cast somehow?
|
||||
public Refresh.Rect ToRefresh()
|
||||
{
|
||||
|
@ -53,6 +75,49 @@ namespace MoonWorks.Graphics
|
|||
public float H;
|
||||
public float MinDepth;
|
||||
public float MaxDepth;
|
||||
|
||||
public Viewport(float w, float h)
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
W = w;
|
||||
H = h;
|
||||
MinDepth = 0;
|
||||
MaxDepth = 1;
|
||||
}
|
||||
|
||||
public Viewport(float x, float y, float w, float h)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
W = w;
|
||||
H = h;
|
||||
MinDepth = 0;
|
||||
MaxDepth = 1;
|
||||
}
|
||||
|
||||
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)]
|
||||
|
@ -61,6 +126,17 @@ namespace MoonWorks.Graphics
|
|||
public uint Binding;
|
||||
public uint Stride;
|
||||
public VertexInputRate InputRate;
|
||||
|
||||
// Shortcut for the common case of having a single vertex binding.
|
||||
public unsafe static VertexBinding Create<T>() where T : unmanaged
|
||||
{
|
||||
return new VertexBinding
|
||||
{
|
||||
Binding = 0,
|
||||
InputRate = VertexInputRate.Vertex,
|
||||
Stride = (uint) sizeof(T)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -70,6 +146,28 @@ namespace MoonWorks.Graphics
|
|||
public uint Binding;
|
||||
public VertexElementFormat Format;
|
||||
public uint Offset;
|
||||
|
||||
public static VertexAttribute Create<T>(
|
||||
string fieldName,
|
||||
uint location,
|
||||
uint binding = 0
|
||||
)
|
||||
{
|
||||
var fieldInfo = typeof(T).GetField(fieldName);
|
||||
|
||||
if (fieldInfo == null)
|
||||
{
|
||||
throw new System.ArgumentException("Field not recognized!");
|
||||
}
|
||||
|
||||
return new VertexAttribute
|
||||
{
|
||||
Binding = binding,
|
||||
Location = location,
|
||||
Format = Conversions.TypeToVertexElementFormat(fieldInfo.FieldType),
|
||||
Offset = (uint) Marshal.OffsetOf<T>(fieldName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
@ -102,25 +200,57 @@ namespace MoonWorks.Graphics
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ColorAttachmentInfo
|
||||
{
|
||||
public RenderTarget renderTarget;
|
||||
public Color clearColor;
|
||||
public LoadOp loadOp;
|
||||
public StoreOp storeOp;
|
||||
public Texture Texture;
|
||||
public uint Depth;
|
||||
public uint Layer;
|
||||
public uint Level;
|
||||
public SampleCount SampleCount;
|
||||
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;
|
||||
SampleCount = SampleCount.One;
|
||||
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;
|
||||
SampleCount = SampleCount.One;
|
||||
ClearColor = Color.White;
|
||||
LoadOp = loadOp;
|
||||
StoreOp = storeOp;
|
||||
}
|
||||
|
||||
public Refresh.ColorAttachmentInfo ToRefresh()
|
||||
{
|
||||
return new Refresh.ColorAttachmentInfo
|
||||
{
|
||||
renderTarget = renderTarget.Handle,
|
||||
texture = Texture.Handle,
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level,
|
||||
sampleCount = (Refresh.SampleCount) SampleCount,
|
||||
clearColor = new Refresh.Vec4
|
||||
{
|
||||
x = clearColor.R / 255f,
|
||||
y = clearColor.G / 255f,
|
||||
z = clearColor.B / 255f,
|
||||
w = clearColor.A / 255f
|
||||
x = ClearColor.R / 255f,
|
||||
y = ClearColor.G / 255f,
|
||||
z = ClearColor.B / 255f,
|
||||
w = ClearColor.A / 255f
|
||||
},
|
||||
loadOp = (Refresh.LoadOp) loadOp,
|
||||
storeOp = (Refresh.StoreOp) storeOp
|
||||
loadOp = (Refresh.LoadOp) LoadOp,
|
||||
storeOp = (Refresh.StoreOp) StoreOp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -128,23 +258,65 @@ namespace MoonWorks.Graphics
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DepthStencilAttachmentInfo
|
||||
{
|
||||
public RenderTarget depthStencilTarget;
|
||||
public DepthStencilValue depthStencilValue;
|
||||
public LoadOp loadOp;
|
||||
public StoreOp storeOp;
|
||||
public LoadOp stencilLoadOp;
|
||||
public StoreOp stencilStoreOp;
|
||||
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 Refresh.DepthStencilAttachmentInfo ToRefresh()
|
||||
{
|
||||
return new Refresh.DepthStencilAttachmentInfo
|
||||
{
|
||||
depthStencilTarget = depthStencilTarget.Handle,
|
||||
depthStencilValue = depthStencilValue.ToRefresh(),
|
||||
loadOp = (Refresh.LoadOp) loadOp,
|
||||
storeOp = (Refresh.StoreOp) storeOp,
|
||||
stencilLoadOp = (Refresh.LoadOp) stencilLoadOp,
|
||||
stencilStoreOp = (Refresh.StoreOp) stencilStoreOp
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +324,18 @@ namespace MoonWorks.Graphics
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ColorAttachmentDescription
|
||||
{
|
||||
public TextureFormat format;
|
||||
public SampleCount sampleCount;
|
||||
public TextureFormat Format;
|
||||
public SampleCount SampleCount;
|
||||
public ColorAttachmentBlendState BlendState;
|
||||
|
||||
public ColorAttachmentDescription(
|
||||
TextureFormat format,
|
||||
ColorAttachmentBlendState blendState,
|
||||
SampleCount sampleCount = SampleCount.One
|
||||
) {
|
||||
Format = format;
|
||||
SampleCount = sampleCount;
|
||||
BlendState = blendState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,33 @@ namespace MoonWorks.Graphics
|
|||
/// </summary>
|
||||
public class Buffer : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Size in bytes.
|
||||
/// </summary>
|
||||
public uint Size { get; }
|
||||
|
||||
/// <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) sizeof(T) * elementCount
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer.
|
||||
|
@ -28,6 +54,7 @@ namespace MoonWorks.Graphics
|
|||
(Refresh.BufferUsageFlags) usageFlags,
|
||||
sizeInBytes
|
||||
);
|
||||
Size = sizeInBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -6,40 +6,30 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
public class ComputePipeline : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
||||
|
||||
public ShaderStageState ComputeShaderState { get; }
|
||||
public ComputeShaderInfo ComputeShaderInfo { get; }
|
||||
|
||||
public unsafe ComputePipeline(
|
||||
GraphicsDevice device,
|
||||
ShaderStageState computeShaderState,
|
||||
uint bufferBindingCount,
|
||||
uint imageBindingCount
|
||||
ComputeShaderInfo computeShaderInfo
|
||||
) : base(device)
|
||||
{
|
||||
var computePipelineLayoutCreateInfo = new Refresh.ComputePipelineLayoutCreateInfo
|
||||
var refreshComputeShaderInfo = new Refresh.ComputeShaderInfo
|
||||
{
|
||||
bufferBindingCount = bufferBindingCount,
|
||||
imageBindingCount = imageBindingCount
|
||||
};
|
||||
|
||||
var computePipelineCreateInfo = new Refresh.ComputePipelineCreateInfo
|
||||
{
|
||||
pipelineLayoutCreateInfo = computePipelineLayoutCreateInfo,
|
||||
computeShaderState = new Refresh.ShaderStageState
|
||||
{
|
||||
entryPointName = computeShaderState.EntryPointName,
|
||||
shaderModule = computeShaderState.ShaderModule.Handle,
|
||||
uniformBufferSize = computeShaderState.UniformBufferSize
|
||||
}
|
||||
entryPointName = computeShaderInfo.EntryPointName,
|
||||
shaderModule = computeShaderInfo.ShaderModule.Handle,
|
||||
uniformBufferSize = computeShaderInfo.UniformBufferSize,
|
||||
bufferBindingCount = computeShaderInfo.BufferBindingCount,
|
||||
imageBindingCount = computeShaderInfo.ImageBindingCount
|
||||
};
|
||||
|
||||
Handle = Refresh.Refresh_CreateComputePipeline(
|
||||
device.Handle,
|
||||
computePipelineCreateInfo
|
||||
refreshComputeShaderInfo
|
||||
);
|
||||
|
||||
ComputeShaderState = computeShaderState;
|
||||
ComputeShaderInfo = computeShaderInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,27 +10,25 @@ namespace MoonWorks.Graphics
|
|||
/// </summary>
|
||||
public class GraphicsPipeline : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||
|
||||
public ShaderStageState VertexShaderState { get; }
|
||||
public ShaderStageState FragmentShaderState { get; }
|
||||
public GraphicsShaderInfo VertexShaderInfo { get; }
|
||||
public GraphicsShaderInfo FragmentShaderInfo { get; }
|
||||
|
||||
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;
|
||||
GraphicsShaderInfo vertexShaderInfo = graphicsPipelineCreateInfo.VertexShaderInfo;
|
||||
GraphicsShaderInfo fragmentShaderInfo = graphicsPipelineCreateInfo.FragmentShaderInfo;
|
||||
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
|
||||
GraphicsPipelineLayoutInfo pipelineLayoutInfo = graphicsPipelineCreateInfo.PipelineLayoutInfo;
|
||||
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
|
||||
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
|
||||
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
|
||||
ViewportState viewportState = graphicsPipelineCreateInfo.ViewportState;
|
||||
GraphicsPipelineAttachmentInfo attachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
|
||||
BlendConstants blendConstants = graphicsPipelineCreateInfo.BlendConstants;
|
||||
|
||||
var vertexAttributesHandle = GCHandle.Alloc(
|
||||
vertexInputState.VertexAttributes,
|
||||
|
@ -40,44 +38,24 @@ namespace MoonWorks.Graphics
|
|||
vertexInputState.VertexBindings,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
var viewportHandle = GCHandle.Alloc(
|
||||
viewportState.Viewports,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
var scissorHandle = GCHandle.Alloc(
|
||||
viewportState.Scissors,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
|
||||
var colorTargetBlendStates = stackalloc Refresh.ColorTargetBlendState[
|
||||
colorBlendState.ColorTargetBlendStates.Length
|
||||
];
|
||||
|
||||
for (var i = 0; i < colorBlendState.ColorTargetBlendStates.Length; i += 1)
|
||||
{
|
||||
colorTargetBlendStates[i] = colorBlendState.ColorTargetBlendStates[i].ToRefreshColorTargetBlendState();
|
||||
}
|
||||
|
||||
var colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
|
||||
(int) attachmentInfo.colorAttachmentCount
|
||||
(int) attachmentInfo.ColorAttachmentDescriptions.Length
|
||||
];
|
||||
|
||||
for (var i = 0; i < attachmentInfo.colorAttachmentCount; i += 1)
|
||||
for (var i = 0; i < attachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
|
||||
{
|
||||
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.colorAttachmentDescriptions[i].format;
|
||||
colorAttachmentDescriptions[i].sampleCount = (Refresh.SampleCount) attachmentInfo.colorAttachmentDescriptions[i].sampleCount;
|
||||
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
|
||||
colorAttachmentDescriptions[i].sampleCount = (Refresh.SampleCount) attachmentInfo.ColorAttachmentDescriptions[i].SampleCount;
|
||||
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -89,20 +67,19 @@ namespace MoonWorks.Graphics
|
|||
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.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;
|
||||
|
@ -111,34 +88,26 @@ namespace MoonWorks.Graphics
|
|||
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.viewportState.viewports = viewportHandle.AddrOfPinnedObject();
|
||||
refreshGraphicsPipelineCreateInfo.viewportState.viewportCount = (uint) viewportState.Viewports.Length;
|
||||
refreshGraphicsPipelineCreateInfo.viewportState.scissors = scissorHandle.AddrOfPinnedObject();
|
||||
refreshGraphicsPipelineCreateInfo.viewportState.scissorCount = (uint) viewportState.Scissors.Length;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.primitiveType = (Refresh.PrimitiveType) primitiveType;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentCount = attachmentInfo.colorAttachmentCount;
|
||||
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.attachmentInfo.depthStencilFormat = (Refresh.TextureFormat) attachmentInfo.DepthStencilFormat;
|
||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment);
|
||||
|
||||
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
|
||||
|
||||
vertexAttributesHandle.Free();
|
||||
vertexBindingsHandle.Free();
|
||||
viewportHandle.Free();
|
||||
scissorHandle.Free();
|
||||
|
||||
VertexShaderState = vertexShaderState;
|
||||
FragmentShaderState = fragmentShaderState;
|
||||
VertexShaderInfo = vertexShaderInfo;
|
||||
FragmentShaderInfo = fragmentShaderInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
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;
|
||||
|
||||
public uint Width => (uint) TextureSlice.Rectangle.W;
|
||||
public uint Height => (uint) TextureSlice.Rectangle.H;
|
||||
|
||||
protected override Action<IntPtr, 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ namespace MoonWorks.Graphics
|
|||
/// </summary>
|
||||
public class Sampler : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||
|
||||
public Sampler(
|
||||
GraphicsDevice device,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using RefreshCS;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
|
@ -8,19 +9,33 @@ namespace MoonWorks.Graphics
|
|||
/// </summary>
|
||||
public class ShaderModule : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||
|
||||
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
|
||||
{
|
||||
var bytecode = Bytecode.ReadBytecodeAsUInt32(filePath);
|
||||
using (FileStream stream = new FileStream(filePath, FileMode.Open))
|
||||
{
|
||||
Handle = CreateFromStream(device, stream);
|
||||
}
|
||||
}
|
||||
|
||||
fixed (uint* ptr = bytecode)
|
||||
public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
|
||||
{
|
||||
Handle = CreateFromStream(device, stream);
|
||||
}
|
||||
|
||||
private unsafe static IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
|
||||
{
|
||||
var bytecode = new byte[stream.Length];
|
||||
stream.Read(bytecode, 0, (int) stream.Length);
|
||||
|
||||
fixed (byte* ptr = bytecode)
|
||||
{
|
||||
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
|
||||
shaderModuleCreateInfo.codeSize = (UIntPtr) (bytecode.Length * sizeof(uint));
|
||||
shaderModuleCreateInfo.codeSize = (UIntPtr) bytecode.Length;
|
||||
shaderModuleCreateInfo.byteCode = (IntPtr) ptr;
|
||||
|
||||
Handle = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
|
||||
return Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
@ -17,7 +18,7 @@ namespace MoonWorks.Graphics
|
|||
public SampleCount SampleCount { get; }
|
||||
public TextureUsageFlags UsageFlags { get; }
|
||||
|
||||
protected override Action<IntPtr, IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a PNG from a file path.
|
||||
|
@ -56,11 +57,62 @@ namespace MoonWorks.Graphics
|
|||
return texture;
|
||||
}
|
||||
|
||||
public unsafe static void SavePNG(string path, int width, int height, byte[] pixels)
|
||||
/// <summary>
|
||||
/// Saves RGBA or BGRA pixel data to a file in PNG format.
|
||||
/// </summary>
|
||||
public unsafe static void SavePNG(string path, int width, int height, TextureFormat format, byte[] pixels)
|
||||
{
|
||||
if (format != TextureFormat.R8G8B8A8 && format != TextureFormat.B8G8R8A8)
|
||||
{
|
||||
throw new ArgumentException("Texture format must be RGBA8 or BGRA8!", "format");
|
||||
}
|
||||
|
||||
fixed (byte* ptr = &pixels[0])
|
||||
{
|
||||
Refresh.Refresh_Image_SavePNG(path, width, height, (IntPtr) ptr);
|
||||
Refresh.Refresh_Image_SavePNG(path, width, height, Conversions.BoolToByte(format == TextureFormat.B8G8R8A8), (IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream)
|
||||
{
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
Texture texture;
|
||||
int faces;
|
||||
ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
|
||||
|
||||
if (isCube)
|
||||
{
|
||||
texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, SampleCount.One, (uint) levels);
|
||||
faces = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, SampleCount.One, (uint) levels);
|
||||
faces = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < faces; i += 1)
|
||||
{
|
||||
for (int j = 0; j < levels; j += 1)
|
||||
{
|
||||
var levelWidth = width >> j;
|
||||
var levelHeight = height >> j;
|
||||
|
||||
var pixels = reader.ReadBytes(
|
||||
Texture.CalculateDDSLevelSize(
|
||||
levelWidth,
|
||||
levelHeight,
|
||||
format
|
||||
)
|
||||
);
|
||||
|
||||
var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j);
|
||||
commandBuffer.SetTextureData(textureSlice, pixels);
|
||||
}
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,8 +243,301 @@ namespace MoonWorks.Graphics
|
|||
IsCube = textureCreateInfo.IsCube;
|
||||
SampleCount = textureCreateInfo.SampleCount;
|
||||
LevelCount = textureCreateInfo.LevelCount;
|
||||
SampleCount = textureCreateInfo.SampleCount;
|
||||
UsageFlags = textureCreateInfo.UsageFlags;
|
||||
}
|
||||
|
||||
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,
|
||||
IntPtr handle,
|
||||
TextureFormat format,
|
||||
uint width,
|
||||
uint height
|
||||
) : base(device, false)
|
||||
{
|
||||
Handle = handle;
|
||||
|
||||
Format = format;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = 1;
|
||||
IsCube = false;
|
||||
SampleCount = SampleCount.One;
|
||||
LevelCount = 1;
|
||||
UsageFlags = TextureUsageFlags.ColorTarget;
|
||||
}
|
||||
|
||||
// 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_CAPS = 0x1;
|
||||
const uint DDSD_HEIGHT = 0x2;
|
||||
const uint DDSD_WIDTH = 0x4;
|
||||
const uint DDSD_PITCH = 0x8;
|
||||
const uint DDSD_FMT = 0x1000;
|
||||
const uint DDSD_LINEARSIZE = 0x80000;
|
||||
const uint DDSD_REQ = (
|
||||
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_FMT
|
||||
);
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct ColorTargetBlendState
|
||||
public struct ColorAttachmentBlendState
|
||||
{
|
||||
/// <summary>
|
||||
/// If disabled, no blending will occur.
|
||||
|
@ -43,7 +43,7 @@ namespace MoonWorks.Graphics
|
|||
/// </summary>
|
||||
public BlendFactor SourceColorBlendFactor;
|
||||
|
||||
public static readonly ColorTargetBlendState Additive = new ColorTargetBlendState
|
||||
public static readonly ColorAttachmentBlendState Additive = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
|
@ -55,7 +55,7 @@ namespace MoonWorks.Graphics
|
|||
DestinationAlphaBlendFactor = BlendFactor.One
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState AlphaBlend = new ColorTargetBlendState
|
||||
public static readonly ColorAttachmentBlendState AlphaBlend = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
|
@ -67,7 +67,7 @@ namespace MoonWorks.Graphics
|
|||
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState NonPremultiplied = new ColorTargetBlendState
|
||||
public static readonly ColorAttachmentBlendState NonPremultiplied = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
|
@ -79,7 +79,7 @@ namespace MoonWorks.Graphics
|
|||
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState Opaque = new ColorTargetBlendState
|
||||
public static readonly ColorAttachmentBlendState Opaque = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
|
@ -91,21 +91,21 @@ namespace MoonWorks.Graphics
|
|||
DestinationAlphaBlendFactor = BlendFactor.Zero
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState None = new ColorTargetBlendState
|
||||
public static readonly ColorAttachmentBlendState None = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = false,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState Disable = new ColorTargetBlendState
|
||||
public static readonly ColorAttachmentBlendState Disable = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = false,
|
||||
ColorWriteMask = ColorComponentFlags.None
|
||||
};
|
||||
|
||||
public Refresh.ColorTargetBlendState ToRefreshColorTargetBlendState()
|
||||
public Refresh.ColorAttachmentBlendState ToRefresh()
|
||||
{
|
||||
return new Refresh.ColorTargetBlendState
|
||||
return new Refresh.ColorAttachmentBlendState
|
||||
{
|
||||
blendEnable = Conversions.BoolToByte(BlendEnable),
|
||||
alphaBlendOp = (Refresh.BlendOp) AlphaBlendOp,
|
|
@ -1,14 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Information that the pipeline needs about a 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) 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,25 @@ namespace MoonWorks.Graphics
|
|||
/// </summary>
|
||||
public struct GraphicsPipelineAttachmentInfo
|
||||
{
|
||||
public ColorAttachmentDescription[] colorAttachmentDescriptions;
|
||||
public uint colorAttachmentCount;
|
||||
public bool hasDepthStencilAttachment;
|
||||
public TextureFormat depthStencilFormat;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
{
|
||||
public struct GraphicsPipelineCreateInfo
|
||||
{
|
||||
public ColorBlendState ColorBlendState;
|
||||
public DepthStencilState DepthStencilState;
|
||||
public ShaderStageState VertexShaderState;
|
||||
public ShaderStageState FragmentShaderState;
|
||||
public GraphicsShaderInfo VertexShaderInfo;
|
||||
public GraphicsShaderInfo FragmentShaderInfo;
|
||||
public MultisampleState MultisampleState;
|
||||
public GraphicsPipelineLayoutInfo PipelineLayoutInfo;
|
||||
public RasterizerState RasterizerState;
|
||||
public PrimitiveType PrimitiveType;
|
||||
public VertexInputState VertexInputState;
|
||||
public ViewportState ViewportState;
|
||||
public GraphicsPipelineAttachmentInfo AttachmentInfo;
|
||||
public BlendConstants BlendConstants;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Information that the pipeline needs about a 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) 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,18 +41,12 @@
|
|||
/// </summary>
|
||||
public FrontFace FrontFace;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the width of the line rendering in terms of pixels.
|
||||
/// </summary>
|
||||
public float LineWidth;
|
||||
|
||||
public static readonly RasterizerState CW_CullFront = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Front,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_CullBack = new RasterizerState
|
||||
|
@ -60,8 +54,7 @@
|
|||
CullMode = CullMode.Back,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_CullNone = new RasterizerState
|
||||
|
@ -69,8 +62,7 @@
|
|||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_Wireframe = new RasterizerState
|
||||
|
@ -78,8 +70,7 @@
|
|||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_CullFront = new RasterizerState
|
||||
|
@ -87,8 +78,7 @@
|
|||
CullMode = CullMode.Front,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_CullBack = new RasterizerState
|
||||
|
@ -96,8 +86,7 @@
|
|||
CullMode = CullMode.Back,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_CullNone = new RasterizerState
|
||||
|
@ -105,8 +94,7 @@
|
|||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_Wireframe = new RasterizerState
|
||||
|
@ -114,8 +102,7 @@
|
|||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -7,5 +7,19 @@
|
|||
{
|
||||
public VertexBinding[] VertexBindings;
|
||||
public VertexAttribute[] VertexAttributes;
|
||||
|
||||
public static readonly VertexInputState Empty = new VertexInputState
|
||||
{
|
||||
VertexBindings = new VertexBinding[0],
|
||||
VertexAttributes = new VertexAttribute[0]
|
||||
};
|
||||
|
||||
public VertexInputState(
|
||||
VertexBinding vertexBinding,
|
||||
params VertexAttribute[] vertexAttributes
|
||||
) {
|
||||
VertexBindings = new VertexBinding[] { vertexBinding };
|
||||
VertexAttributes = vertexAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the dimensions of viewports and scissor areas.
|
||||
/// </summary>
|
||||
public struct ViewportState
|
||||
{
|
||||
public Viewport[] Viewports;
|
||||
public Rect[] Scissors;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,5 +11,39 @@
|
|||
{
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
public static Graphics.VertexElementFormat TypeToVertexElementFormat(System.Type type)
|
||||
{
|
||||
if (type == typeof(uint))
|
||||
{
|
||||
return Graphics.VertexElementFormat.UInt;
|
||||
}
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return Graphics.VertexElementFormat.Float;
|
||||
}
|
||||
else if (type == typeof(Math.Float.Vector2))
|
||||
{
|
||||
return Graphics.VertexElementFormat.Vector2;
|
||||
}
|
||||
else if (type == typeof(Math.Float.Vector3))
|
||||
{
|
||||
return Graphics.VertexElementFormat.Vector3;
|
||||
}
|
||||
else if (type == typeof(Math.Float.Vector4))
|
||||
{
|
||||
return Graphics.VertexElementFormat.Vector4;
|
||||
}
|
||||
else if (type == typeof(Graphics.Color))
|
||||
{
|
||||
return Graphics.VertexElementFormat.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.ArgumentException(
|
||||
"Cannot automatically convert this type to a VertexElementFormat!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
using MoonWorks.Math;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public enum AxisButtonCode
|
||||
{
|
||||
LeftX_Left,
|
||||
LeftX_Right,
|
||||
LeftY_Up,
|
||||
LeftY_Down,
|
||||
RightX_Left,
|
||||
RightX_Right,
|
||||
RightY_Up,
|
||||
RightY_Down
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
// Enum values are equivalent to SDL GameControllerAxis
|
||||
public enum AxisCode
|
||||
{
|
||||
LeftX,
|
||||
LeftY,
|
||||
RightX,
|
||||
RightY
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
// Enum values are equivalent to the SDL GameControllerButton value.
|
||||
public enum GamepadButtonCode
|
||||
{
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
Back,
|
||||
Guide,
|
||||
Start,
|
||||
LeftStick,
|
||||
RightStick,
|
||||
LeftShoulder,
|
||||
RightShoulder,
|
||||
DpadUp,
|
||||
DpadDown,
|
||||
DpadLeft,
|
||||
DpadRight
|
||||
}
|
||||
}
|
|
@ -1,30 +1,60 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public class ButtonState
|
||||
public struct ButtonState
|
||||
{
|
||||
private ButtonStatus ButtonStatus { get; set; }
|
||||
public ButtonStatus ButtonStatus { get; }
|
||||
|
||||
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;
|
||||
|
||||
internal void Update(bool isPressed)
|
||||
public ButtonState(ButtonStatus buttonStatus)
|
||||
{
|
||||
ButtonStatus = buttonStatus;
|
||||
}
|
||||
|
||||
internal ButtonState Update(bool isPressed)
|
||||
{
|
||||
if (isPressed)
|
||||
{
|
||||
if (ButtonStatus == ButtonStatus.Pressed)
|
||||
{
|
||||
ButtonStatus = ButtonStatus.Held;
|
||||
return new ButtonState(ButtonStatus.Held);
|
||||
}
|
||||
else if (ButtonStatus == ButtonStatus.Released)
|
||||
{
|
||||
ButtonStatus = ButtonStatus.Pressed;
|
||||
return new ButtonState(ButtonStatus.Pressed);
|
||||
}
|
||||
else if (ButtonStatus == ButtonStatus.Held)
|
||||
{
|
||||
return new ButtonState(ButtonStatus.Held);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return new ButtonState(ButtonStatus.Released);
|
||||
}
|
||||
|
||||
public static ButtonState operator |(ButtonState a, ButtonState b)
|
||||
{
|
||||
if (a.ButtonStatus == ButtonStatus.Released)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
internal enum ButtonStatus
|
||||
public enum ButtonStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the input is not pressed.
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public enum DeviceKind
|
||||
{
|
||||
None,
|
||||
Keyboard,
|
||||
Mouse,
|
||||
Gamepad,
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MoonWorks.Math;
|
||||
using SDL2;
|
||||
|
||||
|
@ -7,35 +8,241 @@ namespace MoonWorks.Input
|
|||
public class Gamepad
|
||||
{
|
||||
internal IntPtr Handle;
|
||||
internal int JoystickInstanceID;
|
||||
|
||||
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 int Slot { get; internal set; }
|
||||
|
||||
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 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; }
|
||||
|
||||
internal Gamepad(IntPtr handle)
|
||||
public Axis LeftX { get; }
|
||||
public Axis LeftY { get; }
|
||||
public Axis RightX { get; }
|
||||
public Axis RightY { get; }
|
||||
|
||||
public AxisButton LeftXLeft { get; }
|
||||
public AxisButton LeftXRight { get; }
|
||||
public AxisButton LeftYUp { get; }
|
||||
public AxisButton LeftYDown { get; }
|
||||
|
||||
public AxisButton RightXLeft { get; }
|
||||
public AxisButton RightXRight { get; }
|
||||
public AxisButton RightYUp { get; }
|
||||
public AxisButton RightYDown { get; }
|
||||
|
||||
public Trigger TriggerLeft { get; }
|
||||
public Trigger TriggerRight { get; }
|
||||
|
||||
public TriggerButton TriggerLeftButton { get; }
|
||||
public TriggerButton TriggerRightButton { get; }
|
||||
|
||||
public bool IsDummy => Handle == IntPtr.Zero;
|
||||
|
||||
public bool AnyPressed { get; private set; }
|
||||
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, false);
|
||||
LeftYDown = new AxisButton(LeftY, true);
|
||||
|
||||
RightXLeft = new AxisButton(RightX, false);
|
||||
RightXRight = new AxisButton(RightX, true);
|
||||
RightYUp = new AxisButton(RightY, false);
|
||||
RightYDown = new AxisButton(RightY, true);
|
||||
|
||||
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 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(
|
||||
|
@ -46,53 +253,37 @@ namespace MoonWorks.Input
|
|||
) == 0;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
public GamepadButton Button(GamepadButtonCode buttonCode)
|
||||
{
|
||||
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));
|
||||
|
||||
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);
|
||||
return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
|
||||
}
|
||||
|
||||
private bool IsPressed(SDL.SDL_GameControllerButton button)
|
||||
public AxisButton Button(AxisButtonCode axisButtonCode)
|
||||
{
|
||||
return MoonWorks.Conversions.ByteToBool(SDL.SDL_GameControllerGetButton(Handle, button));
|
||||
return AxisButtonCodeToAxisButton[axisButtonCode];
|
||||
}
|
||||
|
||||
private float UpdateAxis(SDL.SDL_GameControllerAxis axis)
|
||||
public TriggerButton Button(TriggerCode triggerCode)
|
||||
{
|
||||
var axisValue = SDL.SDL_GameControllerGetAxis(Handle, axis);
|
||||
return Normalize(axisValue, short.MinValue, short.MaxValue, -1, 1);
|
||||
return TriggerCodeToTriggerButton[triggerCode];
|
||||
}
|
||||
|
||||
// Triggers only go from 0 to short.MaxValue
|
||||
private float UpdateTrigger(SDL.SDL_GameControllerAxis trigger)
|
||||
/// <summary>
|
||||
/// Obtains the axis value given an AxisCode.
|
||||
/// </summary>
|
||||
/// <returns>A value between -1 and 1.</returns>
|
||||
public float AxisValue(AxisCode axisCode)
|
||||
{
|
||||
var triggerValue = SDL.SDL_GameControllerGetAxis(Handle, trigger);
|
||||
return Normalize(triggerValue, 0, short.MaxValue, 0, 1);
|
||||
return EnumToAxis[(SDL.SDL_GameControllerAxis) axisCode].Value;
|
||||
}
|
||||
|
||||
private float Normalize(float value, short min, short max, short newMin, short newMax)
|
||||
/// <summary>
|
||||
/// Obtains the trigger value given an TriggerCode.
|
||||
/// </summary>
|
||||
/// <returns>A value between 0 and 1.</returns>
|
||||
public float TriggerValue(TriggerCode triggerCode)
|
||||
{
|
||||
return ((value - min) * (newMax - newMin)) / (max - min) + newMin;
|
||||
return EnumToTrigger[(SDL.SDL_GameControllerAxis) triggerCode].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,116 @@
|
|||
using SDL2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class Inputs
|
||||
{
|
||||
public const int MAX_GAMEPADS = 4;
|
||||
|
||||
public Keyboard Keyboard { get; }
|
||||
public Mouse Mouse { get; }
|
||||
|
||||
List<Gamepad> gamepads = new List<Gamepad>();
|
||||
Gamepad[] gamepads;
|
||||
|
||||
public static event Action<char> TextInput;
|
||||
|
||||
public bool AnyPressed { get; private set; }
|
||||
public VirtualButton AnyPressedButton { get; private set; }
|
||||
|
||||
internal Inputs()
|
||||
{
|
||||
Keyboard = new Keyboard();
|
||||
Mouse = new Mouse();
|
||||
|
||||
for (int i = 0; i < SDL.SDL_NumJoysticks(); i++)
|
||||
gamepads = new Gamepad[MAX_GAMEPADS];
|
||||
|
||||
// initialize dummy controllers
|
||||
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
||||
{
|
||||
if (SDL.SDL_IsGameController(i) == SDL.SDL_bool.SDL_TRUE)
|
||||
{
|
||||
gamepads.Add(new Gamepad(SDL.SDL_GameControllerOpen(i)));
|
||||
}
|
||||
gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes that SDL_PumpEvents has been called!
|
||||
internal void Update()
|
||||
{
|
||||
AnyPressed = false;
|
||||
AnyPressedButton = default; // DeviceKind.None
|
||||
|
||||
Mouse.Wheel = 0;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool GamepadExists(int slot)
|
||||
{
|
||||
return slot < gamepads.Count;
|
||||
if (slot < 0 || slot >= MAX_GAMEPADS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !gamepads[slot].IsDummy;
|
||||
}
|
||||
|
||||
// From 0-4
|
||||
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))
|
||||
{
|
||||
gamepads[slot].Handle = SDL.SDL_GameControllerOpen(index);
|
||||
System.Console.WriteLine($"Gamepad added to slot {slot}!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
System.Console.WriteLine("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].Handle = IntPtr.Zero;
|
||||
System.Console.WriteLine($"Removing gamepad from slot {slot}!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnTextInput(char c)
|
||||
{
|
||||
if (TextInput != null)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
// Enum values are equivalent to the SDL Scancode value.
|
||||
public enum Keycode : int
|
||||
public enum KeyCode : int
|
||||
{
|
||||
Unknown = 0,
|
||||
A = 4,
|
|
@ -7,7 +7,12 @@ namespace MoonWorks.Input
|
|||
{
|
||||
public class Keyboard
|
||||
{
|
||||
private ButtonState[] Keys { get; }
|
||||
public bool AnyPressed { get; private set; }
|
||||
public KeyboardButton AnyPressedButton { get; private set; }
|
||||
|
||||
public IntPtr State { get; private set; }
|
||||
|
||||
private KeyboardButton[] Keys { get; }
|
||||
private int numKeys;
|
||||
|
||||
private static readonly char[] TextInputCharacters = new char[]
|
||||
|
@ -21,14 +26,14 @@ 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!
|
||||
};
|
||||
|
||||
|
@ -36,54 +41,69 @@ namespace MoonWorks.Input
|
|||
{
|
||||
SDL.SDL_GetKeyboardState(out numKeys);
|
||||
|
||||
Keys = new ButtonState[numKeys];
|
||||
foreach (Keycode keycode in Enum.GetValues(typeof(Keycode)))
|
||||
Keys = new KeyboardButton[numKeys];
|
||||
foreach (KeyCode keycode in Enum.GetValues(typeof(KeyCode)))
|
||||
{
|
||||
Keys[(int) keycode] = new ButtonState();
|
||||
Keys[(int) keycode] = new KeyboardButton(this, keycode);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
IntPtr keyboardState = SDL.SDL_GetKeyboardState(out _);
|
||||
AnyPressed = false;
|
||||
|
||||
foreach (int keycode in Enum.GetValues(typeof(Keycode)))
|
||||
State = SDL.SDL_GetKeyboardState(out _);
|
||||
|
||||
foreach (int keycode in Enum.GetValues(typeof(KeyCode)))
|
||||
{
|
||||
var keyDown = Marshal.ReadByte(keyboardState, keycode);
|
||||
Keys[keycode].Update(Conversions.ByteToBool(keyDown));
|
||||
var button = Keys[keycode];
|
||||
button.Update();
|
||||
|
||||
if (Conversions.ByteToBool(keyDown))
|
||||
if (button.IsPressed)
|
||||
{
|
||||
if (TextInputBindings.TryGetValue((Keycode) keycode, out var textIndex))
|
||||
if (TextInputBindings.TryGetValue((KeyCode) keycode, out var textIndex))
|
||||
{
|
||||
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
|
||||
}
|
||||
else if (IsDown(Keycode.LeftControl) && (Keycode) keycode == Keycode.V)
|
||||
else if (IsDown(KeyCode.LeftControl) && (KeyCode) keycode == KeyCode.V)
|
||||
{
|
||||
Inputs.OnTextInput(TextInputCharacters[6]);
|
||||
}
|
||||
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDown(Keycode keycode)
|
||||
public bool IsDown(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsDown;
|
||||
}
|
||||
|
||||
public bool IsPressed(Keycode keycode)
|
||||
public bool IsPressed(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsPressed;
|
||||
}
|
||||
|
||||
public bool IsHeld(Keycode keycode)
|
||||
public bool IsHeld(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsHeld;
|
||||
}
|
||||
|
||||
public bool IsReleased(Keycode keycode)
|
||||
public bool IsReleased(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsReleased;
|
||||
}
|
||||
|
||||
public KeyboardButton Button(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode];
|
||||
}
|
||||
|
||||
public ButtonState ButtonState(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].State;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
using SDL2;
|
||||
using System.Collections.Generic;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class Mouse
|
||||
{
|
||||
public ButtonState LeftButton { get; } = new ButtonState();
|
||||
public ButtonState MiddleButton { get; } = new ButtonState();
|
||||
public ButtonState RightButton { get; } = new ButtonState();
|
||||
public MouseButton LeftButton { get; }
|
||||
public MouseButton MiddleButton { get; }
|
||||
public MouseButton RightButton { get; }
|
||||
|
||||
public int X { get; private set; }
|
||||
public int Y { get; private set; }
|
||||
|
@ -15,6 +16,11 @@ namespace MoonWorks.Input
|
|||
|
||||
public int Wheel { get; internal set; }
|
||||
|
||||
public bool AnyPressed { get; private set; }
|
||||
public MouseButton AnyPressedButton { get; private set; }
|
||||
|
||||
public uint ButtonMask { get; private set; }
|
||||
|
||||
private bool relativeMode;
|
||||
public bool RelativeMode
|
||||
{
|
||||
|
@ -30,9 +36,37 @@ namespace MoonWorks.Input
|
|||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<MouseButtonCode, MouseButton> CodeToButton;
|
||||
|
||||
private IEnumerable<MouseButton> Buttons
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return LeftButton;
|
||||
yield return MiddleButton;
|
||||
yield return RightButton;
|
||||
}
|
||||
}
|
||||
|
||||
public 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);
|
||||
|
||||
CodeToButton = new Dictionary<MouseButtonCode, MouseButton>
|
||||
{
|
||||
{ MouseButtonCode.Left, LeftButton },
|
||||
{ MouseButtonCode.Right, RightButton },
|
||||
{ MouseButtonCode.Middle, MiddleButton }
|
||||
};
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
var buttonMask = SDL.SDL_GetMouseState(out var x, out var y);
|
||||
AnyPressed = false;
|
||||
|
||||
ButtonMask = SDL.SDL_GetMouseState(out var x, out var y);
|
||||
var _ = SDL.SDL_GetRelativeMouseState(out var deltaX, out var deltaY);
|
||||
|
||||
X = x;
|
||||
|
@ -40,14 +74,25 @@ namespace MoonWorks.Input
|
|||
DeltaX = deltaX;
|
||||
DeltaY = deltaY;
|
||||
|
||||
LeftButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_LMASK));
|
||||
MiddleButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_MMASK));
|
||||
RightButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_RMASK));
|
||||
LeftButton.Update();
|
||||
MiddleButton.Update();
|
||||
RightButton.Update();
|
||||
|
||||
foreach (var button in Buttons)
|
||||
{
|
||||
button.Update();
|
||||
|
||||
if (button.IsPressed)
|
||||
{
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPressed(uint buttonMask, uint buttonFlag)
|
||||
public ButtonState ButtonState(MouseButtonCode buttonCode)
|
||||
{
|
||||
return (buttonMask & buttonFlag) != 0;
|
||||
return CodeToButton[buttonCode].State;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public enum MouseButtonCode
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Middle
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using MoonWorks.Math;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
// Enum values correspond to SDL GameControllerAxis
|
||||
public enum TriggerCode
|
||||
{
|
||||
Left = 4,
|
||||
Right = 5
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public abstract class VirtualButton
|
||||
{
|
||||
public ButtonState State { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is pressed or held.
|
||||
/// </summary>
|
||||
public bool IsDown => State.IsDown;
|
||||
|
||||
/// <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 was pressed this exact frame.
|
||||
/// </summary>
|
||||
public bool IsPressed => State.IsPressed;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is not pressed.
|
||||
/// </summary>
|
||||
public bool IsReleased => State.IsReleased;
|
||||
|
||||
internal virtual void Update()
|
||||
{
|
||||
State = State.Update(CheckPressed());
|
||||
}
|
||||
|
||||
internal abstract bool CheckPressed();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public class AxisButton : VirtualButton
|
||||
{
|
||||
public Axis Parent { get; }
|
||||
public AxisButtonCode Code { get; }
|
||||
|
||||
private float threshold = 0.9f;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
public class EmptyButton : VirtualButton
|
||||
{
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class KeyboardButton : VirtualButton
|
||||
{
|
||||
Keyboard Parent;
|
||||
KeyCode KeyCode;
|
||||
|
||||
internal KeyboardButton(Keyboard parent, KeyCode keyCode)
|
||||
{
|
||||
Parent = parent;
|
||||
KeyCode = keyCode;
|
||||
}
|
||||
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return Conversions.ByteToBool(Marshal.ReadByte(Parent.State, (int) KeyCode));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,866 @@
|
|||
// This source is heavily borrowed from https://github.com/asik/FixedMath.Net
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MoonWorks.Math.Fixed
|
||||
{
|
||||
public struct Fix64 : IEquatable<Fix64>, IComparable<Fix64>
|
||||
{
|
||||
private readonly long RawValue;
|
||||
|
||||
const long MAX_VALUE = long.MaxValue;
|
||||
const long MIN_VALUE = long.MinValue;
|
||||
const int FRACTIONAL_PLACES = 32;
|
||||
const int NUM_BITS = 64;
|
||||
const long ONE = 1L << FRACTIONAL_PLACES;
|
||||
const long PI_TIMES_2 = 0x6487ED511;
|
||||
const long PI = 0x3243F6A88;
|
||||
const long PI_OVER_2 = 0x1921FB544;
|
||||
|
||||
public static readonly Fix64 MaxValue = new Fix64(MAX_VALUE);
|
||||
public static readonly Fix64 MinValue = new Fix64(MIN_VALUE);
|
||||
public static readonly Fix64 One = new Fix64(ONE);
|
||||
public static readonly Fix64 Zero = new Fix64(0);
|
||||
|
||||
public static readonly Fix64 Pi = new Fix64(PI);
|
||||
public static readonly Fix64 PiOver2 = new Fix64(PI_OVER_2);
|
||||
public static readonly Fix64 PiOver4 = PiOver2 / new Fix64(2);
|
||||
public static readonly Fix64 PiTimes2 = new Fix64(PI_TIMES_2);
|
||||
|
||||
const int LUT_SIZE = (int)(PI_OVER_2 >> 15);
|
||||
static readonly Fix64 LutInterval = (Fix64)(LUT_SIZE - 1) / PiOver2;
|
||||
|
||||
public bool IsFractional => (RawValue & 0x00000000FFFFFFFF) != 0;
|
||||
public bool IsIntegral => (RawValue & 0x00000000FFFFFFFF) == 0;
|
||||
|
||||
private Fix64(long value)
|
||||
{
|
||||
RawValue = value;
|
||||
}
|
||||
|
||||
public Fix64(int value)
|
||||
{
|
||||
RawValue = value * ONE;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a fractional Fix64 number of the value (numerator / denominator).
|
||||
/// </summary>
|
||||
public static Fix64 FromFraction(int numerator, int denominator)
|
||||
{
|
||||
return new Fix64(numerator) / new Fix64(denominator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fractional component of this Fix64 value.
|
||||
/// </summary>
|
||||
public static Fix64 Fractional(Fix64 number)
|
||||
{
|
||||
return new Fix64(number.RawValue & 0x00000000FFFFFFFF);
|
||||
}
|
||||
|
||||
public static Fix64 Random(System.Random random, int max)
|
||||
{
|
||||
return new Fix64(random.NextInt64(new Fix64(max).RawValue));
|
||||
}
|
||||
|
||||
public static Fix64 Random(System.Random random, Fix64 max)
|
||||
{
|
||||
return new Fix64(random.NextInt64(max.RawValue));
|
||||
}
|
||||
|
||||
public static Fix64 Random(System.Random random, Fix64 min, Fix64 max)
|
||||
{
|
||||
return new Fix64(random.NextInt64(min.RawValue, max.RawValue));
|
||||
}
|
||||
|
||||
// Max should be between 0.0 and 1.0.
|
||||
public static Fix64 RandomFraction(System.Random random, Fix64 max)
|
||||
{
|
||||
long fractionalPart = (max.RawValue & 0x00000000FFFFFFFF);
|
||||
long fractional = random.NextInt64(fractionalPart);
|
||||
|
||||
return new Fix64(fractional);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an int indicating the sign of a Fix64 number.
|
||||
/// </summary>
|
||||
/// <returns>1 if the value is positive, 0 if it is 0, and -1 if it is negative.</returns>
|
||||
public static int Sign(Fix64 value)
|
||||
{
|
||||
return
|
||||
value.RawValue < 0 ? -1 :
|
||||
value.RawValue > 0 ? 1 :
|
||||
0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the absolute value of a Fix64 number.
|
||||
/// </summary>
|
||||
public static Fix64 Abs(Fix64 value)
|
||||
{
|
||||
if (value.RawValue == MIN_VALUE)
|
||||
{
|
||||
return MaxValue;
|
||||
}
|
||||
|
||||
return FastAbs(value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Fix64 FastAbs(Fix64 value)
|
||||
{
|
||||
// branchless implementation, see http://www.strchr.com/optimized_abs_function
|
||||
var mask = value.RawValue >> 63;
|
||||
return new Fix64((value.RawValue + mask) ^ mask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the largest integral value less than or equal to the specified number.
|
||||
/// </summary>
|
||||
public static Fix64 Floor(Fix64 value)
|
||||
{
|
||||
// Zero out the fractional part.
|
||||
return new Fix64((long)((ulong)value.RawValue & 0xFFFFFFFF00000000));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the smallest integral value that is greater than or equal to the specified number.
|
||||
/// </summary>
|
||||
public static Fix64 Ceiling(Fix64 value)
|
||||
{
|
||||
return value.IsFractional ? Floor(value) + One : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds to the nearest integral value.
|
||||
/// If the value is halfway between an even and an uneven value, returns the even value.
|
||||
/// </summary>
|
||||
public static Fix64 Round(Fix64 value)
|
||||
{
|
||||
var fractionalPart = value.RawValue & 0x00000000FFFFFFFF;
|
||||
var integralPart = Floor(value);
|
||||
if (fractionalPart < 0x80000000)
|
||||
{
|
||||
return integralPart;
|
||||
}
|
||||
if (fractionalPart > 0x80000000)
|
||||
{
|
||||
return integralPart + One;
|
||||
}
|
||||
// if number is halfway between two values, round to the nearest even number
|
||||
// this is the method used by System.Math.Round().
|
||||
return (integralPart.RawValue & ONE) == 0
|
||||
? integralPart
|
||||
: integralPart + One;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a remainder value as defined by the IEEE remainder method.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Fix64 IEEERemainder(Fix64 dividend, Fix64 divisor)
|
||||
{
|
||||
//Formula taken from https://docs.microsoft.com/en-us/dotnet/api/system.math.ieeeremainder?view=net-6.0
|
||||
return dividend - (divisor * Round(dividend / divisor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the minimum of two given Fix64 values.
|
||||
/// </summary>
|
||||
public static Fix64 Min(Fix64 x, Fix64 y)
|
||||
{
|
||||
return (x < y) ? x : y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum of two given Fix64 values.
|
||||
/// </summary>
|
||||
public static Fix64 Max(Fix64 x, Fix64 y)
|
||||
{
|
||||
return (x > y) ? x : y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value that is neither greater than nor less than a given min and max value.
|
||||
/// </summary>
|
||||
public static Fix64 Clamp(Fix64 value, Fix64 min, Fix64 max)
|
||||
{
|
||||
return Fix64.Min(Fix64.Max(value, min), max);
|
||||
}
|
||||
|
||||
public static Fix64 Lerp(Fix64 value1, Fix64 value2, Fix64 amount)
|
||||
{
|
||||
return value1 + (value2 - value1) * amount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rescales a value within a given range to a new range.
|
||||
/// </summary>
|
||||
public static Fix64 Normalize(Fix64 value, Fix64 min, Fix64 max, Fix64 newMin, Fix64 newMax)
|
||||
{
|
||||
return ((value - min) * (newMax - newMin)) / (max - min) + newMin;
|
||||
}
|
||||
|
||||
// Trigonometry functions
|
||||
|
||||
/// <summary>
|
||||
/// Returns the square root of the given Fix64 value.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Throws if x is less than zero.</exception>
|
||||
public static Fix64 Sqrt(Fix64 x)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
if (xl < 0)
|
||||
{
|
||||
// We cannot represent infinities like Single and Double, and Sqrt is
|
||||
// mathematically undefined for x < 0. So we just throw an exception.
|
||||
throw new ArgumentOutOfRangeException("Negative value passed to Sqrt", "x");
|
||||
}
|
||||
|
||||
var num = (ulong)xl;
|
||||
var result = 0UL;
|
||||
|
||||
// second-to-top bit
|
||||
var bit = 1UL << (NUM_BITS - 2);
|
||||
|
||||
while (bit > num)
|
||||
{
|
||||
bit >>= 2;
|
||||
}
|
||||
|
||||
// The main part is executed twice, in order to avoid
|
||||
// using 128 bit values in computations.
|
||||
for (var i = 0; i < 2; ++i)
|
||||
{
|
||||
// First we get the top 48 bits of the answer.
|
||||
while (bit != 0)
|
||||
{
|
||||
if (num >= result + bit)
|
||||
{
|
||||
num -= result + bit;
|
||||
result = (result >> 1) + bit;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = result >> 1;
|
||||
}
|
||||
bit >>= 2;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
// Then process it again to get the lowest 16 bits.
|
||||
if (num > (1UL << (NUM_BITS / 2)) - 1)
|
||||
{
|
||||
// The remainder 'num' is too large to be shifted left
|
||||
// by 32, so we have to add 1 to result manually and
|
||||
// adjust 'num' accordingly.
|
||||
// num = a - (result + 0.5)^2
|
||||
// = num + result^2 - (result + 0.5)^2
|
||||
// = num - result - 0.5
|
||||
num -= result;
|
||||
num = (num << (NUM_BITS / 2)) - 0x80000000UL;
|
||||
result = (result << (NUM_BITS / 2)) + 0x80000000UL;
|
||||
}
|
||||
else
|
||||
{
|
||||
num <<= (NUM_BITS / 2);
|
||||
result <<= (NUM_BITS / 2);
|
||||
}
|
||||
|
||||
bit = 1UL << (NUM_BITS / 2 - 2);
|
||||
}
|
||||
}
|
||||
// Finally, if next bit would have been 1, round the result upwards.
|
||||
if (num > result)
|
||||
{
|
||||
++result;
|
||||
}
|
||||
return new Fix64((long)result);
|
||||
}
|
||||
|
||||
private static long ClampSinValue(long angle, out bool flipHorizontal, out bool flipVertical)
|
||||
{
|
||||
var largePI = 7244019458077122842;
|
||||
// Obtained from ((Fix64)1686629713.065252369824872831112M).m_rawValue
|
||||
// This is (2^29)*PI, where 29 is the largest N such that (2^N)*PI < MaxValue.
|
||||
// The idea is that this number contains way more precision than PI_TIMES_2,
|
||||
// and (((x % (2^29*PI)) % (2^28*PI)) % ... (2^1*PI) = x % (2 * PI)
|
||||
// In practice this gives us an error of about 1,25e-9 in the worst case scenario (Sin(MaxValue))
|
||||
// Whereas simply doing x % PI_TIMES_2 is the 2e-3 range.
|
||||
|
||||
var clamped2Pi = angle;
|
||||
for (int i = 0; i < 29; ++i)
|
||||
{
|
||||
clamped2Pi %= (largePI >> i);
|
||||
}
|
||||
if (angle < 0)
|
||||
{
|
||||
clamped2Pi += PI_TIMES_2;
|
||||
}
|
||||
|
||||
// The LUT contains values for 0 - PiOver2; every other value must be obtained by
|
||||
// vertical or horizontal mirroring
|
||||
flipVertical = clamped2Pi >= PI;
|
||||
// obtain (angle % PI) from (angle % 2PI) - much faster than doing another modulo
|
||||
var clampedPi = clamped2Pi;
|
||||
while (clampedPi >= PI)
|
||||
{
|
||||
clampedPi -= PI;
|
||||
}
|
||||
flipHorizontal = clampedPi >= PI_OVER_2;
|
||||
// obtain (angle % PI_OVER_2) from (angle % PI) - much faster than doing another modulo
|
||||
var clampedPiOver2 = clampedPi;
|
||||
if (clampedPiOver2 >= PI_OVER_2)
|
||||
{
|
||||
clampedPiOver2 -= PI_OVER_2;
|
||||
}
|
||||
return clampedPiOver2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts degrees to radians.
|
||||
/// </summary>
|
||||
public static Fix64 ToRadians(Fix64 degrees)
|
||||
{
|
||||
return degrees * (Pi / new Fix64(180));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
public static Fix64 ToDegrees(Fix64 radians)
|
||||
{
|
||||
return radians * (new Fix64(180) / Pi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sine of the specified angle.
|
||||
/// </summary>
|
||||
public static Fix64 Sin(Fix64 x)
|
||||
{
|
||||
var clampedL = ClampSinValue(x.RawValue, out var flipHorizontal, out var flipVertical);
|
||||
var clamped = new Fix64(clampedL);
|
||||
|
||||
// Find the two closest values in the LUT and perform linear interpolation
|
||||
// This is what kills the performance of this function on x86 - x64 is fine though
|
||||
var rawIndex = FastMul(clamped, LutInterval);
|
||||
var roundedIndex = Round(rawIndex);
|
||||
var indexError = FastSub(rawIndex, roundedIndex);
|
||||
|
||||
var nearestValue = new Fix64(Fix64Lut.Sin[flipHorizontal ?
|
||||
Fix64Lut.Sin.Length - 1 - (int)roundedIndex :
|
||||
(int)roundedIndex]);
|
||||
var secondNearestValue = new Fix64(Fix64Lut.Sin[flipHorizontal ?
|
||||
Fix64Lut.Sin.Length - 1 - (int)roundedIndex - Sign(indexError) :
|
||||
(int)roundedIndex + Sign(indexError)]);
|
||||
|
||||
var delta = FastMul(indexError, FastAbs(FastSub(nearestValue, secondNearestValue))).RawValue;
|
||||
var interpolatedValue = nearestValue.RawValue + (flipHorizontal ? -delta : delta);
|
||||
var finalValue = flipVertical ? -interpolatedValue : interpolatedValue;
|
||||
return new Fix64(finalValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cosine of the specified angle.
|
||||
/// </summary>
|
||||
public static Fix64 Cos(Fix64 x)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
var rawAngle = xl + (xl > 0 ? -PI - PI_OVER_2 : PI_OVER_2);
|
||||
return Sin(new Fix64(rawAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the tangent of the specified angle.
|
||||
/// </summary>
|
||||
public static Fix64 Tan(Fix64 x)
|
||||
{
|
||||
var clampedPi = x.RawValue % PI;
|
||||
var flip = false;
|
||||
if (clampedPi < 0)
|
||||
{
|
||||
clampedPi = -clampedPi;
|
||||
flip = true;
|
||||
}
|
||||
if (clampedPi > PI_OVER_2)
|
||||
{
|
||||
flip = !flip;
|
||||
clampedPi = PI_OVER_2 - (clampedPi - PI_OVER_2);
|
||||
}
|
||||
|
||||
var clamped = new Fix64(clampedPi);
|
||||
|
||||
// Find the two closest values in the LUT and perform linear interpolation
|
||||
var rawIndex = FastMul(clamped, LutInterval);
|
||||
var roundedIndex = Round(rawIndex);
|
||||
var indexError = FastSub(rawIndex, roundedIndex);
|
||||
|
||||
var nearestValue = new Fix64(Fix64Lut.Tan[(int)roundedIndex]);
|
||||
var secondNearestValue = new Fix64(Fix64Lut.Tan[(int)roundedIndex + Sign(indexError)]);
|
||||
|
||||
var delta = FastMul(indexError, FastAbs(FastSub(nearestValue, secondNearestValue))).RawValue;
|
||||
var interpolatedValue = nearestValue.RawValue + delta;
|
||||
var finalValue = flip ? -interpolatedValue : interpolatedValue;
|
||||
return new Fix64(finalValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle whose tangent is the specified number.
|
||||
/// </summary>
|
||||
public static Fix64 Atan(Fix64 z)
|
||||
{
|
||||
if (z.RawValue == 0) return Zero;
|
||||
|
||||
// Force positive values for argument
|
||||
// Atan(-z) = -Atan(z).
|
||||
var neg = z.RawValue < 0;
|
||||
if (neg)
|
||||
{
|
||||
z = -z;
|
||||
}
|
||||
|
||||
Fix64 result;
|
||||
var two = (Fix64)2;
|
||||
var three = (Fix64)3;
|
||||
|
||||
bool invert = z > One;
|
||||
if (invert) z = One / z;
|
||||
|
||||
result = One;
|
||||
var term = One;
|
||||
|
||||
var zSq = z * z;
|
||||
var zSq2 = zSq * two;
|
||||
var zSqPlusOne = zSq + One;
|
||||
var zSq12 = zSqPlusOne * two;
|
||||
var dividend = zSq2;
|
||||
var divisor = zSqPlusOne * three;
|
||||
|
||||
for (var i = 2; i < 30; ++i)
|
||||
{
|
||||
term *= dividend / divisor;
|
||||
result += term;
|
||||
|
||||
dividend += zSq2;
|
||||
divisor += zSq12;
|
||||
|
||||
if (term.RawValue == 0) break;
|
||||
}
|
||||
|
||||
result = result * z / zSqPlusOne;
|
||||
|
||||
if (invert)
|
||||
{
|
||||
result = PiOver2 - result;
|
||||
}
|
||||
|
||||
if (neg)
|
||||
{
|
||||
result = -result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle whose tangent is the quotient of two specified numbers.
|
||||
/// </summary>
|
||||
public static Fix64 Atan2(Fix64 y, Fix64 x)
|
||||
{
|
||||
var yl = y.RawValue;
|
||||
var xl = x.RawValue;
|
||||
if (xl == 0)
|
||||
{
|
||||
if (yl > 0)
|
||||
{
|
||||
return PiOver2;
|
||||
}
|
||||
if (yl == 0)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
return -PiOver2;
|
||||
}
|
||||
Fix64 atan;
|
||||
var z = y / x;
|
||||
|
||||
// Deal with overflow
|
||||
if (One + Fix64.FromFraction(28, 100) * z * z == MaxValue)
|
||||
{
|
||||
return y < Zero ? -PiOver2 : PiOver2;
|
||||
}
|
||||
|
||||
if (Abs(z) < One)
|
||||
{
|
||||
atan = z / (One + Fix64.FromFraction(28, 100) * z * z);
|
||||
if (xl < 0)
|
||||
{
|
||||
if (yl < 0)
|
||||
{
|
||||
return atan - Pi;
|
||||
}
|
||||
return atan + Pi;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
atan = PiOver2 - z / (z * z + Fix64.FromFraction(28, 100));
|
||||
if (yl < 0)
|
||||
{
|
||||
return atan - Pi;
|
||||
}
|
||||
}
|
||||
return atan;
|
||||
}
|
||||
|
||||
// Operators
|
||||
|
||||
public static Fix64 operator +(Fix64 x, Fix64 y)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
var yl = y.RawValue;
|
||||
var sum = xl + yl;
|
||||
// if signs of operands are equal and signs of sum and x are different
|
||||
if (((~(xl ^ yl) & (xl ^ sum)) & MIN_VALUE) != 0)
|
||||
{
|
||||
sum = xl > 0 ? MAX_VALUE : MIN_VALUE;
|
||||
}
|
||||
return new Fix64(sum);
|
||||
}
|
||||
|
||||
public static Fix64 operator -(Fix64 x, Fix64 y)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
var yl = y.RawValue;
|
||||
var diff = xl - yl;
|
||||
// if signs of operands are different and signs of sum and x are different
|
||||
if ((((xl ^ yl) & (xl ^ diff)) & MIN_VALUE) != 0)
|
||||
{
|
||||
diff = xl < 0 ? MIN_VALUE : MAX_VALUE;
|
||||
}
|
||||
return new Fix64(diff);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Fix64 FastSub(Fix64 x, Fix64 y)
|
||||
{
|
||||
return new Fix64(x.RawValue - y.RawValue);
|
||||
}
|
||||
|
||||
private static long AddOverflowHelper(long x, long y, ref bool overflow)
|
||||
{
|
||||
var sum = x + y;
|
||||
// x + y overflows if sign(x) ^ sign(y) != sign(sum)
|
||||
overflow |= ((x ^ y ^ sum) & MIN_VALUE) != 0;
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static Fix64 operator *(Fix64 x, Fix64 y)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
var yl = y.RawValue;
|
||||
|
||||
var xlo = (ulong)(xl & 0x00000000FFFFFFFF);
|
||||
var xhi = xl >> FRACTIONAL_PLACES;
|
||||
var ylo = (ulong)(yl & 0x00000000FFFFFFFF);
|
||||
var yhi = yl >> FRACTIONAL_PLACES;
|
||||
|
||||
var lolo = xlo * ylo;
|
||||
var lohi = (long)xlo * yhi;
|
||||
var hilo = xhi * (long)ylo;
|
||||
var hihi = xhi * yhi;
|
||||
|
||||
var loResult = lolo >> FRACTIONAL_PLACES;
|
||||
var midResult1 = lohi;
|
||||
var midResult2 = hilo;
|
||||
var hiResult = hihi << FRACTIONAL_PLACES;
|
||||
|
||||
bool overflow = false;
|
||||
var sum = AddOverflowHelper((long)loResult, midResult1, ref overflow);
|
||||
sum = AddOverflowHelper(sum, midResult2, ref overflow);
|
||||
sum = AddOverflowHelper(sum, hiResult, ref overflow);
|
||||
|
||||
bool opSignsEqual = ((xl ^ yl) & MIN_VALUE) == 0;
|
||||
|
||||
// if signs of operands are equal and sign of result is negative,
|
||||
// then multiplication overflowed positively
|
||||
// the reverse is also true
|
||||
if (opSignsEqual)
|
||||
{
|
||||
if (sum < 0 || (overflow && xl > 0))
|
||||
{
|
||||
return MaxValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sum > 0)
|
||||
{
|
||||
return MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
// if the top 32 bits of hihi (unused in the result) are neither all 0s or 1s,
|
||||
// then this means the result overflowed.
|
||||
var topCarry = hihi >> FRACTIONAL_PLACES;
|
||||
if (topCarry != 0 && topCarry != -1 /*&& xl != -17 && yl != -17*/)
|
||||
{
|
||||
return opSignsEqual ? MaxValue : MinValue;
|
||||
}
|
||||
|
||||
// If signs differ, both operands' magnitudes are greater than 1,
|
||||
// and the result is greater than the negative operand, then there was negative overflow.
|
||||
if (!opSignsEqual)
|
||||
{
|
||||
long posOp, negOp;
|
||||
if (xl > yl)
|
||||
{
|
||||
posOp = xl;
|
||||
negOp = yl;
|
||||
}
|
||||
else
|
||||
{
|
||||
posOp = yl;
|
||||
negOp = xl;
|
||||
}
|
||||
if (sum > negOp && negOp < -ONE && posOp > ONE)
|
||||
{
|
||||
return MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
return new Fix64(sum);
|
||||
}
|
||||
|
||||
private static Fix64 FastMul(Fix64 x, Fix64 y)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
var yl = y.RawValue;
|
||||
|
||||
var xlo = (ulong)(xl & 0x00000000FFFFFFFF);
|
||||
var xhi = xl >> FRACTIONAL_PLACES;
|
||||
var ylo = (ulong)(yl & 0x00000000FFFFFFFF);
|
||||
var yhi = yl >> FRACTIONAL_PLACES;
|
||||
|
||||
var lolo = xlo * ylo;
|
||||
var lohi = (long)xlo * yhi;
|
||||
var hilo = xhi * (long)ylo;
|
||||
var hihi = xhi * yhi;
|
||||
|
||||
var loResult = lolo >> FRACTIONAL_PLACES;
|
||||
var midResult1 = lohi;
|
||||
var midResult2 = hilo;
|
||||
var hiResult = hihi << FRACTIONAL_PLACES;
|
||||
|
||||
var sum = (long)loResult + midResult1 + midResult2 + hiResult;
|
||||
return new Fix64(sum);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int CountLeadingZeroes(ulong x)
|
||||
{
|
||||
int result = 0;
|
||||
while ((x & 0xF000000000000000) == 0) { result += 4; x <<= 4; }
|
||||
while ((x & 0x8000000000000000) == 0) { result += 1; x <<= 1; }
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Fix64 operator /(Fix64 x, Fix64 y)
|
||||
{
|
||||
var xl = x.RawValue;
|
||||
var yl = y.RawValue;
|
||||
|
||||
if (yl == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
var remainder = (ulong)(xl >= 0 ? xl : -xl);
|
||||
var divider = (ulong)(yl >= 0 ? yl : -yl);
|
||||
var quotient = 0UL;
|
||||
var bitPos = NUM_BITS / 2 + 1;
|
||||
|
||||
|
||||
// If the divider is divisible by 2^n, take advantage of it.
|
||||
while ((divider & 0xF) == 0 && bitPos >= 4)
|
||||
{
|
||||
divider >>= 4;
|
||||
bitPos -= 4;
|
||||
}
|
||||
|
||||
while (remainder != 0 && bitPos >= 0)
|
||||
{
|
||||
int shift = CountLeadingZeroes(remainder);
|
||||
if (shift > bitPos)
|
||||
{
|
||||
shift = bitPos;
|
||||
}
|
||||
remainder <<= shift;
|
||||
bitPos -= shift;
|
||||
|
||||
var div = remainder / divider;
|
||||
remainder = remainder % divider;
|
||||
quotient += div << bitPos;
|
||||
|
||||
// Detect overflow
|
||||
if ((div & ~(0xFFFFFFFFFFFFFFFF >> bitPos)) != 0)
|
||||
{
|
||||
return ((xl ^ yl) & MIN_VALUE) == 0 ? MaxValue : MinValue;
|
||||
}
|
||||
|
||||
remainder <<= 1;
|
||||
--bitPos;
|
||||
}
|
||||
|
||||
// rounding
|
||||
++quotient;
|
||||
var result = (long)(quotient >> 1);
|
||||
if (((xl ^ yl) & MIN_VALUE) != 0)
|
||||
{
|
||||
result = -result;
|
||||
}
|
||||
|
||||
return new Fix64(result);
|
||||
}
|
||||
|
||||
public static Fix64 operator %(Fix64 x, Fix64 y)
|
||||
{
|
||||
return new Fix64(
|
||||
x.RawValue == MIN_VALUE & y.RawValue == -1 ?
|
||||
0 :
|
||||
x.RawValue % y.RawValue);
|
||||
}
|
||||
|
||||
public static Fix64 operator -(Fix64 x)
|
||||
{
|
||||
return x.RawValue == MIN_VALUE ? MaxValue : new Fix64(-x.RawValue);
|
||||
}
|
||||
|
||||
public static bool operator ==(Fix64 x, Fix64 y)
|
||||
{
|
||||
return x.RawValue == y.RawValue;
|
||||
}
|
||||
|
||||
public static bool operator !=(Fix64 x, Fix64 y)
|
||||
{
|
||||
return x.RawValue != y.RawValue;
|
||||
}
|
||||
|
||||
public static bool operator >(Fix64 x, Fix64 y)
|
||||
{
|
||||
return x.RawValue > y.RawValue;
|
||||
}
|
||||
|
||||
public static bool operator <(Fix64 x, Fix64 y)
|
||||
{
|
||||
return x.RawValue < y.RawValue;
|
||||
}
|
||||
|
||||
public static bool operator >(Fix64 x, int y)
|
||||
{
|
||||
return x > ((Fix64) y);
|
||||
}
|
||||
|
||||
public static bool operator <(Fix64 x, int y)
|
||||
{
|
||||
return x < ((Fix64) y);
|
||||
}
|
||||
|
||||
public static bool operator >=(Fix64 x, Fix64 y)
|
||||
{
|
||||
return x.RawValue >= y.RawValue;
|
||||
}
|
||||
|
||||
public static bool operator <=(Fix64 x, Fix64 y)
|
||||
{
|
||||
return x.RawValue <= y.RawValue;
|
||||
}
|
||||
|
||||
public static bool operator >=(Fix64 x, int y)
|
||||
{
|
||||
return x >= ((Fix64) y);
|
||||
}
|
||||
|
||||
public static bool operator <=(Fix64 x, int y)
|
||||
{
|
||||
return x <= ((Fix64) y);
|
||||
}
|
||||
|
||||
// Casting
|
||||
|
||||
public static explicit operator Fix64(long value)
|
||||
{
|
||||
return new Fix64(value * ONE);
|
||||
}
|
||||
|
||||
public static explicit operator long(Fix64 value)
|
||||
{
|
||||
return value.RawValue >> FRACTIONAL_PLACES;
|
||||
}
|
||||
|
||||
public static explicit operator Fix64(float value)
|
||||
{
|
||||
return new Fix64((long)(value * ONE));
|
||||
}
|
||||
|
||||
public static explicit operator float(Fix64 value)
|
||||
{
|
||||
return (float)value.RawValue / ONE;
|
||||
}
|
||||
|
||||
public static explicit operator Fix64(double value)
|
||||
{
|
||||
return new Fix64((long)(value * ONE));
|
||||
}
|
||||
|
||||
public static explicit operator double(Fix64 value)
|
||||
{
|
||||
return (double)value.RawValue / ONE;
|
||||
}
|
||||
|
||||
public static explicit operator Fix64(decimal value)
|
||||
{
|
||||
return new Fix64((long)(value * ONE));
|
||||
}
|
||||
|
||||
public static explicit operator decimal(Fix64 value)
|
||||
{
|
||||
return (decimal)value.RawValue / ONE;
|
||||
}
|
||||
|
||||
public int CompareTo(Fix64 other)
|
||||
{
|
||||
return RawValue.CompareTo(other.RawValue);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Fix64 fix && RawValue == fix.RawValue;
|
||||
}
|
||||
|
||||
public bool Equals(Fix64 other)
|
||||
{
|
||||
return RawValue == other.RawValue;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return RawValue.GetHashCode();
|
||||
}
|
||||
|
||||
// FIXME: can we avoid this cast?
|
||||
public override string ToString()
|
||||
{
|
||||
// Up to 10 decimal places
|
||||
return ((decimal)this).ToString("0.##########");
|
||||
}
|
||||
|
||||
public string ToString(System.Globalization.CultureInfo ci)
|
||||
{
|
||||
return ((decimal) this).ToString("0.##########", ci);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,846 @@
|
|||
/* 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
|
@ -0,0 +1,889 @@
|
|||
#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>
|
||||
/// Scales the quaternion magnitude to unit length.
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
{
|
||||
Fix64 num = Fix64.One / (Fix64.Sqrt(
|
||||
(X * X) +
|
||||
(Y * Y) +
|
||||
(Z * Z) +
|
||||
(W * W)
|
||||
));
|
||||
this.X *= num;
|
||||
this.Y *= num;
|
||||
this.Z *= num;
|
||||
this.W *= num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="String"/> representation of this <see cref="Quaternion"/> in the format:
|
||||
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>] W:[<see cref="W"/>]}
|
||||
/// </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 num = Fix64.One / (Fix64.Sqrt(
|
||||
(quaternion.X * quaternion.X) +
|
||||
(quaternion.Y * quaternion.Y) +
|
||||
(quaternion.Z * quaternion.Z) +
|
||||
(quaternion.W * quaternion.W)
|
||||
));
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
namespace MoonWorks.Math.Fixed
|
||||
{
|
||||
public struct Transform2D : System.IEquatable<Transform2D>
|
||||
{
|
||||
public Vector2 Position { get; }
|
||||
public Fix64 Rotation { get; }
|
||||
public Vector2 Scale { get; }
|
||||
|
||||
private bool transformMatrixCalculated;
|
||||
private Matrix3x2 transformMatrix;
|
||||
|
||||
public Matrix3x2 TransformMatrix
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!transformMatrixCalculated)
|
||||
{
|
||||
transformMatrix = CreateTransformMatrix(Position, Rotation, Scale);
|
||||
transformMatrixCalculated = true;
|
||||
}
|
||||
|
||||
return transformMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAxisAligned => Rotation % Fix64.PiOver2 == Fix64.Zero;
|
||||
public bool IsUniformScale => Scale.X == Scale.Y;
|
||||
|
||||
public static readonly Transform2D Identity = new Transform2D(Vector2.Zero, Fix64.Zero, Vector2.One);
|
||||
|
||||
public Transform2D(Vector2 position)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = Fix64.Zero;
|
||||
Scale = Vector2.One;
|
||||
transformMatrixCalculated = false;
|
||||
transformMatrix = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public Transform2D(Vector2 position, Fix64 rotation)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = Vector2.One;
|
||||
transformMatrixCalculated = false;
|
||||
transformMatrix = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public Transform2D(Vector2 position, Fix64 rotation, Vector2 scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
transformMatrixCalculated = false;
|
||||
transformMatrix = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public Transform2D Compose(Transform2D other)
|
||||
{
|
||||
return new Transform2D(Position + other.Position, Rotation + other.Rotation, Scale * other.Scale);
|
||||
}
|
||||
|
||||
private static Matrix3x2 CreateTransformMatrix(Vector2 position, Fix64 rotation, Vector2 scale)
|
||||
{
|
||||
return
|
||||
Matrix3x2.CreateScale(scale) *
|
||||
Matrix3x2.CreateRotation(rotation) *
|
||||
Matrix3x2.CreateTranslation(position);
|
||||
}
|
||||
|
||||
public bool Equals(Transform2D other)
|
||||
{
|
||||
return
|
||||
Position == other.Position &&
|
||||
Rotation == other.Rotation &&
|
||||
Scale == other.Scale;
|
||||
}
|
||||
|
||||
|
||||
public override bool Equals(System.Object other)
|
||||
{
|
||||
if (other is Transform2D otherTransform)
|
||||
{
|
||||
return Equals(otherTransform);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Position, Rotation, Scale);
|
||||
}
|
||||
|
||||
public static bool operator ==(Transform2D a, Transform2D b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Transform2D a, Transform2D b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,838 @@
|
|||
#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 a unit vector with the same direction.
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
{
|
||||
Fix64 val = Fix64.One / Fix64.Sqrt((X * X) + (Y * Y));
|
||||
X *= val;
|
||||
Y *= val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
||||
/// </summary>
|
||||
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 val = Fix64.One / Fix64.Sqrt((value.X * value.X) + (value.Y * value.Y));
|
||||
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
|
@ -21,7 +21,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
using System.Text;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a viewing frustum for intersection operations.
|
|
@ -21,7 +21,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a sphere in 3D-space for bounding operations.
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines how the bounding volumes intersects or contain one another.
|
|
@ -10,7 +10,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure encapsulating a 3x2 matrix.
|
|
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the right-handed 4x4 floating point matrix, which can store translation, scale and rotation information.
|
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the intersection between a <see cref="Plane"/> and a bounding volume.
|
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 2D-point.
|
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// An efficient mathematical representation for three dimensional rotations.
|
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 2D-rectangle.
|
|
@ -0,0 +1,105 @@
|
|||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
public struct Transform2D : System.IEquatable<Transform2D>
|
||||
{
|
||||
public Vector2 Position { get; }
|
||||
public float Rotation { get; }
|
||||
public Vector2 Scale { get; }
|
||||
|
||||
private bool transformMatrixCalculated;
|
||||
private Matrix3x2 transformMatrix;
|
||||
|
||||
public Matrix3x2 TransformMatrix
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!transformMatrixCalculated)
|
||||
{
|
||||
transformMatrix = CreateTransformMatrix(Position, Rotation, Scale);
|
||||
transformMatrixCalculated = true;
|
||||
}
|
||||
|
||||
return transformMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAxisAligned => Rotation % MathHelper.PiOver2 == 0;
|
||||
public bool IsUniformScale => Scale.X == Scale.Y;
|
||||
|
||||
public static readonly Transform2D Identity = new Transform2D(Vector2.Zero, 0, Vector2.One);
|
||||
|
||||
public Transform2D(Vector2 position)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = 0;
|
||||
Scale = Vector2.One;
|
||||
transformMatrixCalculated = false;
|
||||
transformMatrix = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public Transform2D(Vector2 position, float rotation)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = Vector2.One;
|
||||
transformMatrixCalculated = false;
|
||||
transformMatrix = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public Transform2D(Vector2 position, float rotation, Vector2 scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
transformMatrixCalculated = false;
|
||||
transformMatrix = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public Transform2D Compose(Transform2D other)
|
||||
{
|
||||
return new Transform2D(Position + other.Position, Rotation + other.Rotation, Scale * other.Scale);
|
||||
}
|
||||
|
||||
private static Matrix3x2 CreateTransformMatrix(Vector2 position, float rotation, Vector2 scale)
|
||||
{
|
||||
return
|
||||
Matrix3x2.CreateScale(scale) *
|
||||
Matrix3x2.CreateRotation(rotation) *
|
||||
Matrix3x2.CreateTranslation(position);
|
||||
}
|
||||
|
||||
public bool Equals(Transform2D other)
|
||||
{
|
||||
return
|
||||
Position == other.Position &&
|
||||
Rotation == other.Rotation &&
|
||||
Scale == other.Scale;
|
||||
}
|
||||
|
||||
|
||||
public override bool Equals(System.Object other)
|
||||
{
|
||||
if (other is Transform2D otherTransform)
|
||||
{
|
||||
return Equals(otherTransform);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(Position, Rotation, Scale);
|
||||
}
|
||||
|
||||
public static bool operator ==(Transform2D a, Transform2D b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Transform2D a, Transform2D b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 2D-vector.
|
||||
|
@ -204,6 +204,14 @@ namespace MoonWorks.Math
|
|||
Y *= val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
||||
/// </summary>
|
||||
public float Angle()
|
||||
{
|
||||
return MathF.Atan2(Y, X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="String"/> representation of this <see cref="Vector2"/> in the format:
|
||||
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>]}
|
||||
|
@ -877,6 +885,20 @@ namespace MoonWorks.Math
|
|||
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>
|
||||
|
@ -985,6 +1007,19 @@ namespace MoonWorks.Math
|
|||
);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
@ -1053,6 +1088,19 @@ namespace MoonWorks.Math
|
|||
}
|
||||
}
|
||||
|
||||
/// <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
|
|
@ -22,7 +22,7 @@ using System.Text;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 3D-vector.
|
|
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 4D-vector.
|
|
@ -280,6 +280,11 @@ namespace MoonWorks.Math
|
|||
return result;
|
||||
}
|
||||
|
||||
public static float Quantize(float value, float step)
|
||||
{
|
||||
return (float) System.Math.Floor(value / step) * step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
|
@ -331,6 +336,61 @@ 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
|
||||
|
|
|
@ -195,6 +195,8 @@ namespace MoonWorks
|
|||
NativeLibrary.SetDllImportResolver(typeof(SDL2.SDL).Assembly, MapAndLoad);
|
||||
NativeLibrary.SetDllImportResolver(typeof(RefreshCS.Refresh).Assembly, MapAndLoad);
|
||||
NativeLibrary.SetDllImportResolver(typeof(FAudio).Assembly, MapAndLoad);
|
||||
NativeLibrary.SetDllImportResolver(typeof(WellspringCS.Wellspring).Assembly, MapAndLoad);
|
||||
NativeLibrary.SetDllImportResolver(typeof(Theorafile).Assembly, MapAndLoad);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace MoonWorks.Window
|
||||
namespace MoonWorks
|
||||
{
|
||||
public enum ScreenMode
|
||||
{
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,9 @@
|
|||
#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);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using MoonWorks.Audio;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
public unsafe class StreamingSoundTheora : StreamingSound
|
||||
{
|
||||
private IntPtr VideoHandle;
|
||||
protected override int BUFFER_SIZE => 8192;
|
||||
|
||||
internal StreamingSoundTheora(
|
||||
AudioDevice device,
|
||||
IntPtr videoHandle,
|
||||
int channels,
|
||||
uint sampleRate
|
||||
) : base(
|
||||
device,
|
||||
3, /* float type */
|
||||
32, /* size of float */
|
||||
(ushort) (4 * channels),
|
||||
(ushort) channels,
|
||||
sampleRate
|
||||
) {
|
||||
VideoHandle = videoHandle;
|
||||
}
|
||||
|
||||
protected override unsafe void FillBuffer(
|
||||
void* buffer,
|
||||
int bufferLengthInBytes,
|
||||
out int filledLengthInBytes,
|
||||
out bool reachedEnd
|
||||
) {
|
||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||
|
||||
int samples = Theorafile.tf_readaudio(
|
||||
VideoHandle,
|
||||
(IntPtr) buffer,
|
||||
lengthInFloats
|
||||
);
|
||||
|
||||
filledLengthInBytes = samples * sizeof(float);
|
||||
reachedEnd = Theorafile.tf_eos(VideoHandle) == 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
/* Heavily based on https://github.com/FNA-XNA/FNA/blob/master/src/Media/Xiph/VideoPlayer.cs */
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Audio;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
public enum VideoState
|
||||
{
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped
|
||||
}
|
||||
|
||||
public unsafe class Video : IDisposable
|
||||
{
|
||||
internal IntPtr Handle;
|
||||
|
||||
public bool Loop { get; private set; }
|
||||
public float Volume {
|
||||
get => volume;
|
||||
set
|
||||
{
|
||||
volume = value;
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.Volume = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
public float PlaybackSpeed { get; set; }
|
||||
public double FramesPerSecond => fps;
|
||||
private VideoState State = VideoState.Stopped;
|
||||
|
||||
private double fps;
|
||||
private int yWidth;
|
||||
private int yHeight;
|
||||
private int uvWidth;
|
||||
private int uvHeight;
|
||||
|
||||
private void* yuvData = null;
|
||||
private int yuvDataLength;
|
||||
private int currentFrame;
|
||||
|
||||
private GraphicsDevice GraphicsDevice;
|
||||
private Texture RenderTexture = null;
|
||||
private Texture yTexture = null;
|
||||
private Texture uTexture = null;
|
||||
private Texture vTexture = null;
|
||||
private Sampler LinearSampler;
|
||||
|
||||
private AudioDevice AudioDevice = null;
|
||||
private StreamingSoundTheora audioStream = null;
|
||||
private float volume = 1.0f;
|
||||
|
||||
private Stopwatch timer;
|
||||
private double lastTimestamp;
|
||||
private double timeElapsed;
|
||||
|
||||
private bool disposed;
|
||||
|
||||
/* TODO: is there some way for us to load the data into memory? */
|
||||
public Video(GraphicsDevice graphicsDevice, AudioDevice audioDevice, string filename)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
AudioDevice = audioDevice;
|
||||
|
||||
if (!System.IO.File.Exists(filename))
|
||||
{
|
||||
throw new ArgumentException("Video file not found!");
|
||||
}
|
||||
|
||||
if (Theorafile.tf_fopen(filename, out Handle) < 0)
|
||||
{
|
||||
throw new ArgumentException("Invalid video file!");
|
||||
}
|
||||
|
||||
Theorafile.th_pixel_fmt format;
|
||||
Theorafile.tf_videoinfo(
|
||||
Handle,
|
||||
out yWidth,
|
||||
out yHeight,
|
||||
out fps,
|
||||
out format
|
||||
);
|
||||
|
||||
if (format == Theorafile.th_pixel_fmt.TH_PF_420)
|
||||
{
|
||||
uvWidth = yWidth / 2;
|
||||
uvHeight = yHeight / 2;
|
||||
}
|
||||
else if (format == Theorafile.th_pixel_fmt.TH_PF_422)
|
||||
{
|
||||
uvWidth = yWidth / 2;
|
||||
uvHeight = yHeight;
|
||||
}
|
||||
else if (format == Theorafile.th_pixel_fmt.TH_PF_444)
|
||||
{
|
||||
uvWidth = yWidth;
|
||||
uvHeight = yHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unrecognized YUV format!");
|
||||
}
|
||||
|
||||
yuvDataLength = (
|
||||
(yWidth * yHeight) +
|
||||
(uvWidth * uvHeight * 2)
|
||||
);
|
||||
|
||||
yuvData = NativeMemory.Alloc((nuint) yuvDataLength);
|
||||
|
||||
InitializeTheoraStream();
|
||||
|
||||
if (Theorafile.tf_hasvideo(Handle) == 1)
|
||||
{
|
||||
RenderTexture = Texture.CreateTexture2D(
|
||||
GraphicsDevice,
|
||||
(uint) yWidth,
|
||||
(uint) yHeight,
|
||||
TextureFormat.R8G8B8A8,
|
||||
TextureUsageFlags.ColorTarget | TextureUsageFlags.Sampler
|
||||
);
|
||||
|
||||
yTexture = Texture.CreateTexture2D(
|
||||
GraphicsDevice,
|
||||
(uint) yWidth,
|
||||
(uint) yHeight,
|
||||
TextureFormat.R8,
|
||||
TextureUsageFlags.Sampler
|
||||
);
|
||||
|
||||
uTexture = Texture.CreateTexture2D(
|
||||
GraphicsDevice,
|
||||
(uint) uvWidth,
|
||||
(uint) uvHeight,
|
||||
TextureFormat.R8,
|
||||
TextureUsageFlags.Sampler
|
||||
);
|
||||
|
||||
vTexture = Texture.CreateTexture2D(
|
||||
GraphicsDevice,
|
||||
(uint) uvWidth,
|
||||
(uint) uvHeight,
|
||||
TextureFormat.R8,
|
||||
TextureUsageFlags.Sampler
|
||||
);
|
||||
|
||||
LinearSampler = new Sampler(GraphicsDevice, SamplerCreateInfo.LinearClamp);
|
||||
}
|
||||
|
||||
timer = new Stopwatch();
|
||||
}
|
||||
|
||||
public void Play(bool loop = false)
|
||||
{
|
||||
if (State == VideoState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Loop = loop;
|
||||
timer.Start();
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.Play();
|
||||
}
|
||||
|
||||
State = VideoState.Playing;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (State != VideoState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.Pause();
|
||||
}
|
||||
|
||||
State = VideoState.Paused;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (State == VideoState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
Theorafile.tf_reset(Handle);
|
||||
lastTimestamp = 0;
|
||||
timeElapsed = 0;
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.StopImmediate();
|
||||
audioStream.Dispose();
|
||||
audioStream = null;
|
||||
}
|
||||
|
||||
State = VideoState.Stopped;
|
||||
}
|
||||
|
||||
public Texture GetTexture()
|
||||
{
|
||||
if (RenderTexture == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (State == VideoState.Stopped)
|
||||
{
|
||||
return RenderTexture;
|
||||
}
|
||||
|
||||
timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed;
|
||||
lastTimestamp = timer.Elapsed.TotalMilliseconds;
|
||||
|
||||
int thisFrame = ((int) (timeElapsed / (1000.0 / FramesPerSecond)));
|
||||
if (thisFrame > currentFrame)
|
||||
{
|
||||
if (Theorafile.tf_readvideo(
|
||||
Handle,
|
||||
(IntPtr) yuvData,
|
||||
thisFrame - currentFrame
|
||||
) == 1 || currentFrame == -1) {
|
||||
UpdateTexture();
|
||||
}
|
||||
|
||||
currentFrame = thisFrame;
|
||||
}
|
||||
|
||||
bool ended = Theorafile.tf_eos(Handle) == 1;
|
||||
if (ended)
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
audioStream.Stop();
|
||||
audioStream.Dispose();
|
||||
audioStream = null;
|
||||
}
|
||||
|
||||
Theorafile.tf_reset(Handle);
|
||||
|
||||
if (Loop)
|
||||
{
|
||||
// Start over!
|
||||
InitializeTheoraStream();
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
State = VideoState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
return RenderTexture;
|
||||
}
|
||||
|
||||
private void UpdateTexture()
|
||||
{
|
||||
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
|
||||
|
||||
commandBuffer.SetTextureDataYUV(
|
||||
yTexture,
|
||||
uTexture,
|
||||
vTexture,
|
||||
(IntPtr) yuvData,
|
||||
(uint) yuvDataLength
|
||||
);
|
||||
|
||||
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 void InitializeTheoraStream()
|
||||
{
|
||||
// Grab the first video frame ASAP.
|
||||
while (Theorafile.tf_readvideo(Handle, (IntPtr) yuvData, 1) == 0);
|
||||
|
||||
// Grab the first bit of audio. We're trying to start the decoding ASAP.
|
||||
if (AudioDevice != null && Theorafile.tf_hasaudio(Handle) == 1)
|
||||
{
|
||||
int channels, sampleRate;
|
||||
Theorafile.tf_audioinfo(Handle, out channels, out sampleRate);
|
||||
audioStream = new StreamingSoundTheora(AudioDevice, Handle, channels, (uint) sampleRate);
|
||||
}
|
||||
|
||||
currentFrame = -1;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
RenderTexture.Dispose();
|
||||
yTexture.Dispose();
|
||||
uTexture.Dispose();
|
||||
vTexture.Dispose();
|
||||
}
|
||||
|
||||
// free unmanaged resources (unmanaged objects)
|
||||
Theorafile.tf_close(ref Handle);
|
||||
NativeMemory.Free(yuvData);
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Video()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
using System;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Window
|
||||
namespace MoonWorks
|
||||
{
|
||||
public class OSWindow : IDisposable
|
||||
public class Window : IDisposable
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
public ScreenMode ScreenMode { get; }
|
||||
public uint Width { get; }
|
||||
public uint Height { get; }
|
||||
public ScreenMode ScreenMode { get; private set; }
|
||||
public uint Width { get; private set; }
|
||||
public uint Height { get; private set; }
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public OSWindow(WindowCreateInfo windowCreateInfo)
|
||||
public Window(WindowCreateInfo windowCreateInfo)
|
||||
{
|
||||
var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
|
||||
|
||||
|
@ -25,6 +25,16 @@ namespace MoonWorks.Window
|
|||
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
if (windowCreateInfo.SystemResizable)
|
||||
{
|
||||
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
if (windowCreateInfo.StartMaximized)
|
||||
{
|
||||
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED;
|
||||
}
|
||||
|
||||
ScreenMode = windowCreateInfo.ScreenMode;
|
||||
|
||||
Handle = SDL.SDL_CreateWindow(
|
||||
|
@ -53,6 +63,8 @@ namespace MoonWorks.Window
|
|||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
ScreenMode = screenMode;
|
||||
|
||||
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
|
||||
}
|
||||
|
||||
|
@ -65,6 +77,14 @@ namespace MoonWorks.Window
|
|||
public void SetWindowSize(uint width, uint height)
|
||||
{
|
||||
SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
internal void SizeChanged(uint width, uint height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
|
@ -82,7 +102,7 @@ namespace MoonWorks.Window
|
|||
}
|
||||
}
|
||||
|
||||
~OSWindow()
|
||||
~Window()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
|
@ -1,10 +0,0 @@
|
|||
namespace MoonWorks.Window
|
||||
{
|
||||
public struct WindowCreateInfo
|
||||
{
|
||||
public string WindowTitle;
|
||||
public uint WindowWidth;
|
||||
public uint WindowHeight;
|
||||
public ScreenMode ScreenMode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
namespace MoonWorks
|
||||
{
|
||||
public struct WindowCreateInfo
|
||||
{
|
||||
public string WindowTitle;
|
||||
public uint WindowWidth;
|
||||
public uint WindowHeight;
|
||||
public ScreenMode ScreenMode;
|
||||
public bool SystemResizable;
|
||||
public bool StartMaximized;
|
||||
|
||||
public WindowCreateInfo(
|
||||
string windowTitle,
|
||||
uint windowWidth,
|
||||
uint windowHeight,
|
||||
ScreenMode screenMode,
|
||||
bool systemResizable = false,
|
||||
bool startMaximized = false
|
||||
) {
|
||||
WindowTitle = windowTitle;
|
||||
WindowWidth = windowWidth;
|
||||
WindowHeight = windowHeight;
|
||||
ScreenMode = screenMode;
|
||||
SystemResizable = systemResizable;
|
||||
StartMaximized = startMaximized;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue