Compare commits

...

113 Commits

Author SHA1 Message Date
cosmonaut c649b24dad Game.Timestep is now public 2022-08-11 21:55:12 -07:00
cosmonaut f06ad263b7 add Fixed.Vector2.Lerp 2022-08-09 17:14:38 -07:00
cosmonaut 28e3479444 remove decimal cast from Fix64.Atan2 2022-08-05 16:56:26 -07:00
cosmonaut 72ad850ab4 fix unnecessary double cast 2022-08-03 16:34:15 -07:00
cosmonaut 9cd6e6cc7b add Vector2.Rotate methods 2022-08-03 16:15:47 -07:00
cosmonaut 9c8ec7ad5a ButtonState now has a public constructor 2022-08-03 10:13:20 -07:00
cosmonaut 0b0a6feff5 make note of Theorafile dependency 2022-08-02 16:57:07 -07:00
cosmonaut 1e6455d070 fix StreamingSoundOgg seek 2022-08-02 14:15:16 -07:00
cosmonaut efb9893aef Theora video support + audio improvements (#20)
- `SoundInstance.Play` no longer takes a loop parameter
- `SoundInstance.Stop` is split into `Stop` and `StopImmediate` instead of taking an immediate parameter
- Added `StreamingSoundSeekable` to better support streaming audio that does not support seek
- `StreamingSound` no longer has a Loop property, but `StreamingSoundSeekable` does
- abstract `StreamingSound.AddBuffer` renamed to `FillBuffer`
- `FillBuffer` is now provided with a native buffer to avoid an extra data copy
- `StreamingSound` buffer implementation optimized to avoid repeated alloc/frees

- added `Video` class which can load and play Theora (.ogv) streaming video/audio

Reviewed-on: MoonsideGames/MoonWorks#20
2022-08-02 21:04:12 +00:00
cosmonaut 5a5fbc0c77 handle OS window size changes 2022-07-28 16:06:50 -07:00
cosmonaut 40a2b31e90 add StartMaximized flag to WindowCreateInfo 2022-07-28 16:06:37 -07:00
cosmonaut 424f410688 add SystemResizable flag to WindowCreateInfo 2022-07-26 19:08:21 -07:00
cosmonaut d6bd11d63f add Documentation to README 2022-07-13 12:33:08 -07:00
cosmonaut f6fc80804e improve gamepad init and support hotplugging 2022-07-13 11:53:09 -07:00
cosmonaut 646e5e9283 empty virtual button 2022-07-12 17:57:37 -07:00
cosmonaut ab619192a6 combine button states 2022-07-12 17:57:27 -07:00
cosmonaut d07a722fb1 VirtualButton system 2022-07-12 16:09:23 -07:00
cosmonaut 3543f074f4 fix erroneous Normalize 2022-07-12 16:06:13 -07:00
cosmonaut d3e124cea1 reset AnyPressedButton at start of Update 2022-07-08 16:52:37 -07:00
cosmonaut 1cf3a13541 Inputs.AnyPressed implementation 2022-07-08 16:47:12 -07:00
cosmonaut db5ca97726 Packer.TextBounds 2022-06-30 16:24:28 -07:00
cosmonaut d190df55b2 update wellspring 2022-06-30 13:25:52 -07:00
cosmonaut 1aa522ffa1 avoid TextBatch.Draw smashing the stack with big strings 2022-06-30 13:24:51 -07:00
cosmonaut a9b3b53ce9 add optional Destroy method to Game 2022-06-23 23:59:23 -07:00
cosmonaut d32bbcd537 radian and degree conversions on Fix64 2022-06-21 11:20:32 -07:00
cosmonaut b23792acd0 remove empty struct constructors for language compatibility 2022-06-20 13:04:44 -07:00
cosmonaut 97fad21c0c update Wellspring 2022-06-17 14:09:23 -07:00
cosmonaut 2f5d25b458 Fix64 random fix 2022-06-16 00:51:02 -07:00
cosmonaut ba662d7c3e add Fix64 random range 2022-06-14 18:25:37 -07:00
cosmonaut c1085db9c5 add store ops to depth stencil attach info struct 2022-06-08 17:15:14 -07:00
cosmonaut bb0b6daa91 Fix64.Fractional 2022-06-07 17:01:44 -07:00
cosmonaut 13519c3150 fix stencil clear 2022-06-06 18:44:03 -07:00
cosmonaut 6ab7a2f722 read ShaderModules from stream 2022-06-06 10:42:05 -07:00
cosmonaut 436affe5de remove some unnecessary qualifiers 2022-06-05 12:29:35 -07:00
darkerbit ff544140e0 add file drop API 2022-06-05 12:27:46 -07:00
cosmonaut a10f18b4eb update README dependencies 2022-06-04 16:09:56 -07:00
cosmonaut 26e7e5c809 move mouse wheel reset to sensible location 2022-06-04 15:51:19 -07:00
cosmonaut d2a51ce524 decouple fixed timestep from draw, add frame cap 2022-06-04 15:48:55 -07:00
cosmonaut 5a9709c843 revert Game and GameState change 2022-06-01 18:31:22 -07:00
cosmonaut 0e8188682e game state now changes on next update 2022-05-23 19:12:27 -07:00
cosmonaut 9862bfd0a0 TextureSamplerBinding now stores IntPtrs 2022-05-23 19:12:17 -07:00
cosmonaut 4756fe2b14 add SetMasteringVolume method to AudioDevice 2022-05-23 19:11:48 -07:00
cosmonaut e5c72c6f46 add lerp to Fix64 2022-05-18 18:28:53 -07:00
cosmonaut 318ca22021 add default case for clarity 2022-05-15 12:16:41 -07:00
cosmonaut 547f7a388e add Fix64 random methods 2022-05-15 12:16:41 -07:00
cosmonaut dd3fb75905 fix DDS mip level loader 2022-05-14 10:31:56 -07:00
cosmonaut 2bd88e1dc1 add a few useful functions 2022-05-13 16:26:26 -07:00
cosmonaut 66d363459b improve DDS format support 2022-05-12 11:21:07 -07:00
cosmonaut b22d3bed30 add DDS and BC7 support 2022-05-11 21:22:20 -07:00
cosmonaut 0d93207ae9 Fixed Point Math (#19)
Reviewed-on: MoonsideGames/MoonWorks#19
2022-05-11 17:34:09 +00:00
cosmonaut 5e2368bc7d update wellspring 2022-05-02 10:12:38 -07:00
cosmonaut a0082bcec6 change Marshal.SizeOf to sizeof 2022-04-27 14:14:15 -07:00
cosmonaut 810f270a41 blittable button identifiers 2022-04-27 11:37:35 -07:00
cosmonaut 27e0404ec0 expose Line properties + no-ID retrieve 2022-04-21 16:04:40 -07:00
cosmonaut be4b5cf2c7 fix audio not being disposed in correct order 2022-04-20 14:57:24 -07:00
cosmonaut 83eb268a4a add audio Seek functionality 2022-04-20 14:29:46 -07:00
cosmonaut 985e096a7b add Update and Remove to SpatialHash2D 2022-04-20 10:57:16 -07:00
cosmonaut dccd81e029 change collision API to support multi shapes 2022-04-18 11:33:40 -07:00
cosmonaut 65568ea234 Font rendering update 2022-04-13 15:10:23 -07:00
cosmonaut b49dc3720a update Wellspring 2022-04-13 10:11:07 -07:00
cosmonaut 61a6d0bdc0 Font Rendering (#18)
Adds a font rendering system based on Wellspring.

Reviewed-on: MoonsideGames/MoonWorks#18
2022-04-13 03:06:14 +00:00
cosmonaut 72c9dd4bda close vorbis stream before freeing data 2022-04-08 00:17:58 -07:00
cosmonaut 412f0ca179 game state system 2022-04-08 00:03:42 -07:00
cosmonaut b252d0eb92 forgot that C# arrays are not just memory 2022-04-07 15:11:14 -07:00
cosmonaut ba66ed4225 stream ogg from memory instead of disk 2022-04-07 14:19:43 -07:00
cosmonaut 5e2b8de2d3 Spatial Hash Retrieve returns collision groups 2022-04-06 20:25:46 -07:00
cosmonaut 35ded250ed some more collision fast paths 2022-04-05 17:31:27 -07:00
cosmonaut f8146b799a store rectangle properties as ints 2022-04-05 16:35:55 -07:00
cosmonaut 379bdcdcb1 bump moonworks target to .NET 6 2022-04-05 16:24:43 -07:00
cosmonaut 4b4abaab01 fix AABB2D transform method 2022-04-05 16:06:34 -07:00
cosmonaut 6a1fa004d6 set loop on play instead of construct 2022-04-05 16:05:42 -07:00
cosmonaut b1b6b84809 WAV static sounds + static sound instance pool 2022-04-04 23:33:36 -07:00
cosmonaut 08a3c01f66 add Vector2.Angle 2022-03-31 15:31:53 -07:00
cosmonaut ec5160c060 add int variant to MathHelper.Approach 2022-03-31 14:51:18 -07:00
cosmonaut c96c7a0d90 MathHelper.Approach should not return delta 2022-03-29 00:11:50 -07:00
cosmonaut f0d3dfccf9 add collision mask system to spatial hash 2022-03-25 15:11:38 -07:00
cosmonaut 6ea3e24a91 Refresh 1.5.0 2022-03-24 20:39:51 -07:00
cosmonaut 9f4b69e6aa keyboard uses Button instead of ButtonState 2022-03-24 16:41:00 -07:00
cosmonaut 80c34c6b16 Collision API (#17) 2022-03-24 06:07:34 +00:00
cosmonaut cc876b2132 GetGamepad no longer throws if slot is empty 2022-03-18 12:11:00 -07:00
cosmonaut 71d9f8f4fe remove unnecessary setters and add doc comments 2022-03-18 11:58:10 -07:00
cosmonaut 5050a9369d Input API rework 2022-03-18 11:46:44 -07:00
cosmonaut 3623e6b07c add debug exception for binding a null texture 2022-03-17 14:42:43 -07:00
cosmonaut 1b5221f2c7 vertex format ABI break 2022-03-17 14:42:30 -07:00
cosmonaut 0fb7e98cb5 add Vector2.Transform Matrix3x2 overload 2022-03-15 23:02:38 -07:00
cosmonaut 5424d05d63 RasterizerState ABI break 2022-03-14 10:48:31 -07:00
cosmonaut e7addb953f Refresh 1.3.0 2022-03-10 10:28:46 -08:00
cosmonaut 2a9286f31e Clear and AcquireSwapchainTexture ABI break 2022-03-10 10:25:41 -08:00
cosmonaut 8f9aaf6d61 ButtonState is now a struct 2022-03-07 10:54:52 -08:00
cosmonaut cf2d8473a1 renderArea NULL inputs 2022-03-06 22:33:12 -08:00
cosmonaut b5b0f35b50 SetViewport and SetScissor doc comments 2022-03-04 14:14:22 -08:00
cosmonaut 527f47436a Refresh 1.2.0 2022-03-04 13:21:52 -08:00
cosmonaut 40d9cdd33a add element-wise SetBufferData overload 2022-03-04 10:00:29 -08:00
cosmonaut c4d2e3b8ee Refresh 1.1.0 2022-03-03 17:35:38 -08:00
cosmonaut a413863cf9 add Size property to Buffer 2022-03-03 17:18:38 -08:00
cosmonaut 17333cfb67 destroy update 2022-03-03 17:16:39 -08:00
cosmonaut 42754ef80d Refresh 1.0.0 2022-03-02 16:06:54 -08:00
cosmonaut 111df04c0f fix some Window stuff 2022-03-02 14:46:55 -08:00
cosmonaut 9c83423c79 add buffer creation convenience method 2022-03-02 14:29:43 -08:00
cosmonaut 7d3a7901b2 convenience constructors for graphics state 2022-03-02 13:57:30 -08:00
cosmonaut 278db7d55b fix a few missing renames 2022-03-02 11:45:37 -08:00
cosmonaut 774028a013 D3D compatibility ABI break 2022-03-02 11:42:26 -08:00
cosmonaut f6369b6bce add some notes to AcquireSwapchainTexture 2022-03-02 10:13:52 -08:00
cosmonaut c34f74a99d do not destroy untracked resource 2022-03-02 09:58:32 -08:00
cosmonaut d6606d90f6 AcquireSwapchainTexture nullable 2022-03-02 09:39:23 -08:00
cosmonaut ef10be4e9d add some shortcut defaults to structs 2022-03-01 23:32:44 -08:00
cosmonaut 1fa73f0275 more presentation API improvements 2022-03-01 23:21:42 -08:00
cosmonaut 81c882bd48 presentation ABI break 2022-03-01 22:57:10 -08:00
cosmonaut 7328cbc13d add some constructors to reduce boilerplate 2022-02-25 18:01:22 -08:00
cosmonaut 32b269526f Blend state ABI break 2022-02-25 17:50:08 -08:00
cosmonaut 9028a8b1a0 remove Window namespace 2022-02-25 13:23:31 -08:00
cosmonaut edd21ec573 remove count from managed GraphicsPipelineAttachmentInfo 2022-02-25 11:10:35 -08:00
Caleb Cornett 2469cf530a Capitalized attachment info struct member names 2022-02-25 11:17:37 -05:00
152 changed files with 64004 additions and 971 deletions

6
.gitmodules vendored
View File

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

View File

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

View File

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

View File

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

View File

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

1
lib/Theorafile Submodule

@ -0,0 +1 @@
Subproject commit dd8c7fa69e678b6182cdaa71458ad08dd31c65da

1
lib/WellspringCS Submodule

@ -0,0 +1 @@
Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97

View File

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

View File

@ -1,6 +1,6 @@
using System;
using System.Runtime.InteropServices;
using MoonWorks.Math;
using MoonWorks.Math.Float;
namespace MoonWorks.Audio
{

View File

@ -1,5 +1,5 @@
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
namespace MoonWorks.Audio
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

14
src/FramerateSettings.cs Normal file
View File

@ -0,0 +1,14 @@
namespace MoonWorks
{
public enum FramerateMode
{
Uncapped,
Capped
}
public struct FramerateSettings
{
public FramerateMode Mode;
public int Cap;
}
}

View File

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

View File

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

View File

@ -19,6 +19,7 @@ using System;
using System.Diagnostics;
using System.Text;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

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

View File

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

44
src/Graphics/Font/Font.cs Normal file
View File

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

109
src/Graphics/Font/Packer.cs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -16,7 +16,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -16,7 +16,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -16,7 +16,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -14,7 +14,7 @@
#endregion
using MoonWorks.Math;
using MoonWorks.Math.Float;
namespace MoonWorks.Graphics
{

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

@ -17,6 +17,7 @@
#region Using Statements
using System;
using MoonWorks.Math;
using MoonWorks.Math.Float;
#endregion
namespace MoonWorks.Graphics

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

37
src/Input/Axis.cs Normal file
View File

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

View File

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

11
src/Input/AxisCode.cs Normal file
View File

@ -0,0 +1,11 @@
namespace MoonWorks.Input
{
// Enum values are equivalent to SDL GameControllerAxis
public enum AxisCode
{
LeftX,
LeftY,
RightX,
RightY
}
}

22
src/Input/ButtonCode.cs Normal file
View File

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

View File

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

View File

@ -1,6 +1,6 @@
namespace MoonWorks.Input
{
internal enum ButtonStatus
public enum ButtonStatus
{
/// <summary>
/// Indicates that the input is not pressed.

10
src/Input/DeviceKind.cs Normal file
View File

@ -0,0 +1,10 @@
namespace MoonWorks.Input
{
public enum DeviceKind
{
None,
Keyboard,
Mouse,
Gamepad,
}
}

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

@ -0,0 +1,9 @@
namespace MoonWorks.Input
{
public enum MouseButtonCode
{
Left,
Right,
Middle
}
}

37
src/Input/Trigger.cs Normal file
View File

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

9
src/Input/TriggerCode.cs Normal file
View File

@ -0,0 +1,9 @@
namespace MoonWorks.Input
{
// Enum values correspond to SDL GameControllerAxis
public enum TriggerCode
{
Left = 4,
Right = 5
}
}

View File

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

View File

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

View File

@ -0,0 +1,10 @@
namespace MoonWorks.Input
{
public class EmptyButton : VirtualButton
{
internal override bool CheckPressed()
{
return false;
}
}
}

View File

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

View File

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

View File

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

View File

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

866
src/Math/Fixed/Fix64.cs Normal file
View File

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

25745
src/Math/Fixed/Fix64SinLut.cs Normal file

File diff suppressed because it is too large Load Diff

25745
src/Math/Fixed/Fix64TanLut.cs Normal file

File diff suppressed because it is too large Load Diff

846
src/Math/Fixed/Matrix3x2.cs Normal file
View File

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

1692
src/Math/Fixed/Matrix4x4.cs Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

838
src/Math/Fixed/Vector2.cs Normal file
View File

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

1136
src/Math/Fixed/Vector3.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]

View File

@ -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.

View File

@ -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.

View File

@ -14,7 +14,7 @@
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// Defines how the bounding volumes intersects or contain one another.

View File

@ -10,7 +10,7 @@
using System;
using System.Globalization;
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// A structure encapsulating a 3x2 matrix.

View File

@ -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.

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]

View File

@ -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.

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// Describes a 2D-point.

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// An efficient mathematical representation for three dimensional rotations.

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
[Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")]

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// Describes a 2D-rectangle.

View File

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

View File

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

View File

@ -22,7 +22,7 @@ using System.Text;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// Describes a 3D-vector.

View File

@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math
namespace MoonWorks.Math.Float
{
/// <summary>
/// Describes a 4D-vector.

View File

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

View File

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

View File

@ -1,4 +1,4 @@
namespace MoonWorks.Window
namespace MoonWorks
{
public enum ScreenMode
{

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

357
src/Video/Video.cs Normal file
View File

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

View File

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

View File

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

28
src/WindowCreateInfo.cs Normal file
View File

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