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"] [submodule "lib/RefreshCS"]
path = lib/RefreshCS path = lib/RefreshCS
url = https://gitea.moonside.games/MoonsideGames/RefreshCS.git 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
@ -14,6 +14,8 @@
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" /> <ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
<ProjectReference Include=".\lib\RefreshCS\RefreshCS.csproj" /> <ProjectReference Include=".\lib\RefreshCS\RefreshCS.csproj" />
<ProjectReference Include=".\lib\FAudio\csharp\FAudio-CS.Core.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>
<ItemGroup> <ItemGroup>
@ -21,4 +23,13 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
</ItemGroup> </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> </Project>

View File

@ -11,4 +11,12 @@
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/> <dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/> <dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/> <dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
<dllmap dll="Wellspring" os="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> </configuration>

View File

@ -14,6 +14,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudi
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\RefreshCS\RefreshCS.csproj", "{AD7C94E4-0AFA-44CA-889C-110142369893}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\RefreshCS\RefreshCS.csproj", "{AD7C94E4-0AFA-44CA-889C-110142369893}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 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}.Debug|x64.Build.0 = Debug|x64
{AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.ActiveCfg = Release|x64 {AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.ActiveCfg = Release|x64
{AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -43,4 +51,7 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3D68FAA-3165-43C7-95B3-D845F0DAA918} SolutionGuid = {C3D68FAA-3165-43C7-95B3-D845F0DAA918}
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0DD7B866-773C-4A86-8580-F436DAA28989} = {69D3788D-6C57-44F7-A912-B201AE6D7C04}
EndGlobalSection
EndGlobal 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. 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 ## Dependencies
* [SDL2](https://github.com/flibitijibibo/SDL2-CS) - Window management, Input * [SDL2](https://github.com/flibitijibibo/SDL2-CS) - Window management, Input
* [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics * [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics
* [FAudio](https://github.com/FNA-XNA/FAudio) - Audio * [FAudio](https://github.com/FNA-XNA/FAudio) - Audio
* [Wellspring](https://gitea.moonside.games/MoonsideGames/Wellspring) - Font Rendering
* [Theorafile](https://github.com/FNA-XNA/Theorafile) - Compressed Video
Prebuilt dependencies can be obtained here: http://moonside.games/files/moonlibs.tar.bz2
## License ## 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; IntPtr chainPtr;
chainPtr = Marshal.AllocHGlobal( chainPtr = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioEffectChain>() sizeof(FAudio.FAudioEffectChain)
); );
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr; FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
reverbChain->EffectCount = 1; reverbChain->EffectCount = 1;
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal( reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioEffectDescriptor>() sizeof(FAudio.FAudioEffectDescriptor)
); );
FAudio.FAudioEffectDescriptor* reverbDescriptor = FAudio.FAudioEffectDescriptor* reverbDescriptor =
@ -146,7 +146,7 @@ namespace MoonWorks.Audio
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC // Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
IntPtr reverbParamsPtr = Marshal.AllocHGlobal( IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
Marshal.SizeOf<FAudio.FAudioFXReverbParameters>() sizeof(FAudio.FAudioFXReverbParameters)
); );
FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr; FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
@ -176,7 +176,7 @@ namespace MoonWorks.Audio
ReverbVoice, ReverbVoice,
0, 0,
reverbParamsPtr, reverbParamsPtr,
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(), (uint) sizeof(FAudio.FAudioFXReverbParameters),
0 0
); );
Marshal.FreeHGlobal(reverbParamsPtr); Marshal.FreeHGlobal(reverbParamsPtr);
@ -187,7 +187,7 @@ namespace MoonWorks.Audio
{ {
SendCount = 2, SendCount = 2,
pSends = Marshal.AllocHGlobal( pSends = Marshal.AllocHGlobal(
2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>() 2 * sizeof(FAudio.FAudioSendDescriptor)
) )
}; };
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends; FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
@ -197,7 +197,12 @@ namespace MoonWorks.Audio
sendDesc[1].pOutputVoice = ReverbVoice; 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--) for (var i = streamingSounds.Count - 1; i >= 0; i--)
{ {
@ -240,16 +245,16 @@ namespace MoonWorks.Audio
{ {
if (disposing) 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); FAudio.FAudioVoice_DestroyVoice(ReverbVoice);

View File

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

View File

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

View File

@ -1,20 +1,18 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MoonWorks.Math;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
public abstract class SoundInstance : AudioResource public abstract class SoundInstance : AudioResource
{ {
internal IntPtr Handle { get; } internal IntPtr Handle;
internal FAudio.FAudioWaveFormatEx Format { get; } internal FAudio.FAudioWaveFormatEx Format;
public bool Loop { get; }
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings; 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; private float _pan = 0;
public float Pan public float Pan
@ -33,7 +31,7 @@ namespace MoonWorks.Audio
_pan = 1f; _pan = 1f;
} }
if (is3D) { return; } if (Is3D) { return; }
SetPanMatrixCoefficients(); SetPanMatrixCoefficients();
FAudio.FAudioVoice_SetOutputMatrix( FAudio.FAudioVoice_SetOutputMatrix(
@ -53,7 +51,7 @@ namespace MoonWorks.Audio
get => _pitch; get => _pitch;
set set
{ {
_pitch = MathHelper.Clamp(value, -1f, 1f); _pitch = Math.MathHelper.Clamp(value, -1f, 1f);
UpdatePitch(); UpdatePitch();
} }
} }
@ -163,17 +161,17 @@ namespace MoonWorks.Audio
public SoundInstance( public SoundInstance(
AudioDevice device, AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels, ushort channels,
uint samplesPerSecond, uint samplesPerSecond
bool is3D,
bool loop
) : base(device) ) : base(device)
{ {
var blockAlign = (ushort) (4 * channels);
var format = new FAudio.FAudioWaveFormatEx var format = new FAudio.FAudioWaveFormatEx
{ {
wFormatTag = 3, wFormatTag = formatTag,
wBitsPerSample = 32, wBitsPerSample = bitsPerSample,
nChannels = channels, nChannels = channels,
nBlockAlign = blockAlign, nBlockAlign = blockAlign,
nSamplesPerSec = samplesPerSecond, nSamplesPerSec = samplesPerSecond,
@ -184,8 +182,8 @@ namespace MoonWorks.Audio
FAudio.FAudio_CreateSourceVoice( FAudio.FAudio_CreateSourceVoice(
Device.Handle, Device.Handle,
out var handle, out Handle,
ref format, ref Format,
FAudio.FAUDIO_VOICE_USEFILTER, FAudio.FAUDIO_VOICE_USEFILTER,
FAudio.FAUDIO_DEFAULT_FREQ_RATIO, FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
IntPtr.Zero, IntPtr.Zero,
@ -193,28 +191,28 @@ namespace MoonWorks.Audio
IntPtr.Zero IntPtr.Zero
); );
if (handle == IntPtr.Zero) if (Handle == IntPtr.Zero)
{ {
Logger.LogError("SoundInstance failed to initialize!"); Logger.LogError("SoundInstance failed to initialize!");
return; return;
} }
Handle = handle;
this.is3D = is3D;
InitDSPSettings(Format.nChannels); InitDSPSettings(Format.nChannels);
// FIXME: not everything should be running through reverb...
/*
FAudio.FAudioVoice_SetOutputVoices( FAudio.FAudioVoice_SetOutputVoices(
handle, Handle,
ref Device.ReverbSends ref Device.ReverbSends
); );
*/
Loop = loop;
State = SoundState.Stopped; State = SoundState.Stopped;
} }
public void Apply3D(AudioListener listener, AudioEmitter emitter) public void Apply3D(AudioListener listener, AudioEmitter emitter)
{ {
is3D = true; Is3D = true;
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar; emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount; emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount;
@ -240,7 +238,8 @@ namespace MoonWorks.Audio
public abstract void Play(); public abstract void Play();
public abstract void Pause(); public abstract void Pause();
public abstract void Stop(bool immediate); public abstract void Stop();
public abstract void StopImmediate();
private void InitDSPSettings(uint srcChannels) private void InitDSPSettings(uint srcChannels)
{ {
@ -271,7 +270,7 @@ namespace MoonWorks.Audio
{ {
float doppler; float doppler;
float dopplerScale = Device.DopplerScale; float dopplerScale = Device.DopplerScale;
if (!is3D || dopplerScale == 0.0f) if (!Is3D || dopplerScale == 0.0f)
{ {
doppler = 1.0f; doppler = 1.0f;
} }
@ -343,8 +342,7 @@ namespace MoonWorks.Audio
protected override void Destroy() protected override void Destroy()
{ {
Stop(true); StopImmediate();
FAudio.FAudioVoice_DestroyVoice(Handle); FAudio.FAudioVoice_DestroyVoice(Handle);
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients); 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; using System.Runtime.InteropServices;
namespace MoonWorks.Audio namespace MoonWorks.Audio
@ -6,12 +8,17 @@ namespace MoonWorks.Audio
public class StaticSound : AudioResource public class StaticSound : AudioResource
{ {
internal FAudio.FAudioBuffer Handle; internal FAudio.FAudioBuffer Handle;
public ushort FormatTag { get; }
public ushort BitsPerSample { get; }
public ushort Channels { get; } public ushort Channels { get; }
public uint SamplesPerSecond { get; } public uint SamplesPerSecond { get; }
public ushort BlockAlign { get; }
public uint LoopStart { get; set; } = 0; public uint LoopStart { get; set; } = 0;
public uint LoopLength { get; set; } = 0; public uint LoopLength { get; set; } = 0;
private Stack<StaticSoundInstance> Instances = new Stack<StaticSoundInstance>();
public static StaticSound LoadOgg(AudioDevice device, string filePath) public static StaticSound LoadOgg(AudioDevice device, string filePath)
{ {
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); 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( public StaticSound(
AudioDevice device, AudioDevice device,
ushort channels, ushort channels,
@ -52,6 +247,9 @@ namespace MoonWorks.Audio
uint bufferLength /* in floats */ uint bufferLength /* in floats */
) : base(device) ) : base(device)
{ {
FormatTag = 3;
BitsPerSample = 32;
BlockAlign = (ushort) (4 * channels);
Channels = channels; Channels = channels;
SamplesPerSecond = samplesPerSecond; SamplesPerSecond = samplesPerSecond;
@ -69,9 +267,23 @@ namespace MoonWorks.Audio
LoopLength = 0; 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() protected override void Destroy()

View File

@ -6,6 +6,8 @@ namespace MoonWorks.Audio
{ {
public StaticSound Parent { get; } public StaticSound Parent { get; }
public bool Loop { get; set; }
private SoundState _state = SoundState.Stopped; private SoundState _state = SoundState.Stopped;
public override SoundState State public override SoundState State
{ {
@ -18,7 +20,7 @@ namespace MoonWorks.Audio
); );
if (state.BuffersQueued == 0) if (state.BuffersQueued == 0)
{ {
Stop(true); StopImmediate();
} }
return _state; return _state;
@ -32,10 +34,8 @@ namespace MoonWorks.Audio
internal StaticSoundInstance( internal StaticSoundInstance(
AudioDevice device, AudioDevice device,
StaticSound parent, StaticSound parent
bool is3D, ) : base(device, parent.FormatTag, parent.BitsPerSample, parent.BlockAlign, parent.Channels, parent.SamplesPerSecond)
bool loop
) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop)
{ {
Parent = parent; 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_Stop(Handle, 0, 0);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle); FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
State = SoundState.Stopped; State = SoundState.Stopped;
} }
else
public void Seek(uint sampleFrame)
{ {
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0); if (State == SoundState.Playing)
} {
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
}
Parent.Handle.PlayBegin = sampleFrame;
}
public void Free()
{
Parent.FreeInstance(this);
} }
} }
} }

View File

@ -1,28 +1,38 @@
using System; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
/// <summary> /// <summary>
/// For streaming long playback. /// 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> /// </summary>
public abstract class StreamingSound : SoundInstance public abstract class StreamingSound : SoundInstance
{ {
private readonly List<IntPtr> queuedBuffers = new List<IntPtr>(); private const int BUFFER_COUNT = 3;
private readonly List<uint> queuedSizes = new List<uint>(); private readonly IntPtr[] buffers;
private const int MINIMUM_BUFFER_CHECK = 3; private int nextBufferIndex = 0;
private uint queuedBufferCount = 0;
protected abstract int BUFFER_SIZE { get; }
public int PendingBufferCount => queuedBuffers.Count; public unsafe StreamingSound(
public StreamingSound(
AudioDevice device, AudioDevice device,
ushort formatTag,
ushort bitsPerSample,
ushort blockAlign,
ushort channels, ushort channels,
uint samplesPerSecond, uint samplesPerSecond
bool is3D, ) : base(device, formatTag, bitsPerSample, blockAlign, channels, samplesPerSecond)
bool loop {
) : base(device, channels, samplesPerSecond, is3D, loop) { } 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() public override void Play()
{ {
@ -32,6 +42,7 @@ namespace MoonWorks.Audio
} }
State = SoundState.Playing; State = SoundState.Playing;
Update(); Update();
FAudio.FAudioSourceVoice_Start(Handle, 0, 0); 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) State = SoundState.Stopped;
}
public override void StopImmediate()
{ {
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0); FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle); FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
ClearBuffers(); ClearBuffers();
}
State = SoundState.Stopped; State = SoundState.Stopped;
} }
internal void Update() internal unsafe void Update()
{ {
if (State != SoundState.Playing) if (State != SoundState.Playing)
{ {
@ -70,68 +83,45 @@ namespace MoonWorks.Audio
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
); );
while (PendingBufferCount > state.BuffersQueued) queuedBufferCount = state.BuffersQueued;
lock (queuedBuffers)
{
Marshal.FreeHGlobal(queuedBuffers[0]);
queuedBuffers.RemoveAt(0);
}
QueueBuffers(); QueueBuffers();
} }
protected void QueueBuffers() protected void QueueBuffers()
{ {
for ( for (int i = 0; i < BUFFER_COUNT - queuedBufferCount; i += 1)
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
i > 0;
i -= 1
)
{ {
AddBuffer(); AddBuffer();
} }
} }
protected void ClearBuffers() protected unsafe void ClearBuffers()
{ {
lock (queuedBuffers) nextBufferIndex = 0;
{ queuedBufferCount = 0;
foreach (IntPtr buf in queuedBuffers)
{
Marshal.FreeHGlobal(buf);
}
queuedBuffers.Clear();
queuedSizes.Clear();
}
} }
protected void AddBuffer() protected unsafe void AddBuffer()
{ {
AddBuffer( var buffer = buffers[nextBufferIndex];
out var buffer, nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
out var bufferOffset,
out var bufferLength, FillBuffer(
out var reachedEnd (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)
{
queuedBuffers.Add(next);
if (State != SoundState.Stopped)
{
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
{ {
AudioBytes = lengthInBytes, AudioBytes = (uint) filledLengthInBytes,
pAudioData = next, pAudioData = (IntPtr) buffer,
PlayLength = ( PlayLength = (
lengthInBytes / (uint) (filledLengthInBytes /
Format.nChannels / Format.nChannels /
(uint) (Format.wBitsPerSample / 8) (uint) (Format.wBitsPerSample / 8))
) )
}; };
@ -140,39 +130,36 @@ namespace MoonWorks.Audio
ref buf, ref buf,
IntPtr.Zero IntPtr.Zero
); );
}
else queuedBufferCount += 1;
{
queuedSizes.Add(lengthInBytes);
}
}
/* We have reached the end of the file, what do we do? */ /* We have reached the end of the file, what do we do? */
if (reachedEnd) if (reachedEnd)
{ {
if (Loop) OnReachedEnd();
{
SeekStart();
}
else
{
Stop(false);
}
} }
} }
protected abstract void AddBuffer( protected virtual void OnReachedEnd()
out float[] buffer, {
out uint bufferOffset, /* in floats */ Stop();
out uint bufferLength, /* in floats */ }
protected unsafe abstract void FillBuffer(
void* buffer,
int bufferLengthInBytes, /* in bytes */
out int filledLengthInBytes, /* in bytes */
out bool reachedEnd out bool reachedEnd
); );
protected abstract void SeekStart(); protected unsafe override void Destroy()
protected 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;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
namespace MoonWorks.Audio namespace MoonWorks.Audio
{ {
public class StreamingSoundOgg : StreamingSound public class StreamingSoundOgg : StreamingSoundSeekable
{ {
// FIXME: what should this value be? private IntPtr VorbisHandle;
public const int BUFFER_SIZE = 1024 * 128; private IntPtr FileDataPtr;
private FAudio.stb_vorbis_info Info;
internal IntPtr FileHandle { get; } protected override int BUFFER_SIZE => 32768;
internal FAudio.stb_vorbis_info Info { get; }
private readonly float[] buffer; public unsafe static StreamingSoundOgg Load(AudioDevice device, string filePath)
public override SoundState State { get; protected set; }
public static StreamingSoundOgg Load(
AudioDevice device,
string filePath,
bool is3D = false,
bool loop = false
)
{ {
var fileHandle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); 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) if (error != 0)
{ {
NativeMemory.Free(fileDataPtr);
Logger.LogError("Error opening OGG file!"); Logger.LogError("Error opening OGG file!");
Logger.LogError("Error: " + error);
throw new AudioLoadException("Error opening OGG file!"); throw new AudioLoadException("Error opening OGG file!");
} }
var info = FAudio.stb_vorbis_get_info(vorbisHandle);
var info = FAudio.stb_vorbis_get_info(fileHandle);
return new StreamingSoundOgg( return new StreamingSoundOgg(
device, device,
fileHandle, (IntPtr) fileDataPtr,
info, vorbisHandle,
is3D, info
loop
); );
} }
internal StreamingSoundOgg( internal StreamingSoundOgg(
AudioDevice device, AudioDevice device,
IntPtr fileHandle, IntPtr fileDataPtr, // MUST BE A NATIVE MEMORY HANDLE!!
FAudio.stb_vorbis_info info, IntPtr vorbisHandle,
bool is3D, FAudio.stb_vorbis_info info
bool loop ) : base(
) : base(device, (ushort) info.channels, info.sample_rate, is3D, loop) 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; Info = info;
buffer = new float[BUFFER_SIZE];
device.AddDynamicSoundInstance(this);
} }
protected override void AddBuffer( public override void Seek(uint sampleFrame)
out float[] buffer, {
out uint bufferOffset, FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
out uint bufferLength, }
protected unsafe override void FillBuffer(
void* buffer,
int bufferLengthInBytes,
out int filledLengthInBytes,
out bool reachedEnd out bool reachedEnd
) )
{ {
buffer = this.buffer; var lengthInFloats = bufferLengthInBytes / sizeof(float);
/* NOTE: this function returns samples per channel, not total samples */ /* NOTE: this function returns samples per channel, not total samples */
var samples = FAudio.stb_vorbis_get_samples_float_interleaved( var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
FileHandle, VorbisHandle,
Info.channels, Info.channels,
buffer, (IntPtr) buffer,
buffer.Length lengthInFloats
); );
var sampleCount = samples * Info.channels; var sampleCount = samples * Info.channels;
bufferOffset = 0; reachedEnd = sampleCount < lengthInFloats;
bufferLength = (uint) sampleCount; filledLengthInBytes = sampleCount * sizeof(float);
reachedEnd = sampleCount < buffer.Length;
} }
protected override void SeekStart() protected unsafe override void Destroy()
{ {
FAudio.stb_vorbis_seek_start(FileHandle); FAudio.stb_vorbis_close(VorbisHandle);
} NativeMemory.Free((void*) FileDataPtr);
protected override void Destroy()
{
FAudio.stb_vorbis_close(FileHandle);
} }
} }
} }

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.Audio;
using MoonWorks.Graphics; using MoonWorks.Graphics;
using MoonWorks.Input; using MoonWorks.Input;
using MoonWorks.Window;
using System.Text; using System.Text;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
@ -13,14 +12,13 @@ namespace MoonWorks
public abstract class Game public abstract class Game
{ {
public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100); public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
public TimeSpan Timestep { get; private set; }
private bool quit = false; private bool quit = false;
bool debugMode;
private Stopwatch gameTimer; private Stopwatch gameTimer;
private TimeSpan timestep;
private long previousTicks = 0; 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 // 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 PREVIOUS_SLEEP_TIME_COUNT = 128;
private const int SLEEP_TIME_MASK = PREVIOUS_SLEEP_TIME_COUNT - 1; private const int SLEEP_TIME_MASK = PREVIOUS_SLEEP_TIME_COUNT - 1;
@ -28,7 +26,10 @@ namespace MoonWorks
private int sleepTimeIndex = 0; private int sleepTimeIndex = 0;
private TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1); 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 GraphicsDevice GraphicsDevice { get; }
public AudioDevice AudioDevice { get; } public AudioDevice AudioDevice { get; }
public Inputs Inputs { get; } public Inputs Inputs { get; }
@ -44,13 +45,21 @@ namespace MoonWorks
public Game( public Game(
WindowCreateInfo windowCreateInfo, WindowCreateInfo windowCreateInfo,
PresentMode presentMode, PresentMode presentMode,
FramerateSettings framerateSettings,
int targetTimestep = 60, int targetTimestep = 60,
bool debugMode = false bool debugMode = false
) )
{ {
timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep); Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
gameTimer = Stopwatch.StartNew(); 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) for (int i = 0; i < previousSleepTimes.Length; i += 1)
{ {
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1); previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
@ -66,7 +75,7 @@ namespace MoonWorks
Inputs = new Inputs(); Inputs = new Inputs();
Window = new OSWindow(windowCreateInfo); Window = new Window(windowCreateInfo);
GraphicsDevice = new GraphicsDevice( GraphicsDevice = new GraphicsDevice(
Window.Handle, Window.Handle,
@ -75,22 +84,51 @@ namespace MoonWorks
); );
AudioDevice = new AudioDevice(); AudioDevice = new AudioDevice();
this.debugMode = debugMode;
} }
public void Run() public void Run()
{ {
while (!quit) while (!quit)
{
Tick();
}
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(); AdvanceElapsedTime();
/* We want to wait until the next frame, if (FramerateCapped)
{
/* We want to wait until the framerate cap,
* but we don't want to oversleep. Requesting repeated 1ms sleeps and * 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 * seeing how long we actually slept for lets us estimate the worst case
* sleep precision so we don't oversleep the next frame. * sleep precision so we don't oversleep the next frame.
*/ */
while (accumulatedElapsedTime + worstCaseSleepPrecision < timestep) while (accumulatedDrawTime + worstCaseSleepPrecision < FramerateCapTimeSpan)
{ {
System.Threading.Thread.Sleep(1); System.Threading.Thread.Sleep(1);
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime(); TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
@ -102,51 +140,39 @@ namespace MoonWorks
* SpinWait(1) works by pausing the thread for very short intervals, so it is * 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. * an efficient and time-accurate way to wait out the rest of the time.
*/ */
while (accumulatedElapsedTime < timestep) while (accumulatedDrawTime < FramerateCapTimeSpan)
{ {
System.Threading.Thread.SpinWait(1); System.Threading.Thread.SpinWait(1);
AdvanceElapsedTime(); AdvanceElapsedTime();
} }
}
// Now that we are going to perform an update, let's handle SDL events. // Now that we are going to perform an update, let's handle SDL events.
HandleSDLEvents(); HandleSDLEvents();
// Do not let any step take longer than our maximum. // Do not let any step take longer than our maximum.
if (accumulatedElapsedTime > MAX_DELTA_TIME) if (accumulatedUpdateTime > MAX_DELTA_TIME)
{ {
accumulatedElapsedTime = MAX_DELTA_TIME; accumulatedUpdateTime = MAX_DELTA_TIME;
} }
if (!quit) if (!quit)
{ {
while (accumulatedElapsedTime >= timestep) while (accumulatedUpdateTime >= Timestep)
{ {
Inputs.Mouse.Wheel = 0;
Inputs.Update(); Inputs.Update();
AudioDevice.Update(); AudioDevice.Update();
Update(timestep); Update(Timestep);
accumulatedElapsedTime -= timestep; accumulatedUpdateTime -= Timestep;
} }
var alpha = accumulatedElapsedTime / timestep; var alpha = accumulatedUpdateTime / Timestep;
Draw(timestep, alpha);
Draw(alpha);
accumulatedDrawTime -= FramerateCapTimeSpan;
} }
GraphicsDevice.SubmitDestroyCommandBuffer();
}
OnDestroy();
AudioDevice.Dispose();
GraphicsDevice.Dispose();
Window.Dispose();
SDL.SDL_Quit();
} }
private void HandleSDLEvents() private void HandleSDLEvents()
@ -166,19 +192,43 @@ namespace MoonWorks
case SDL.SDL_EventType.SDL_MOUSEWHEEL: case SDL.SDL_EventType.SDL_MOUSEWHEEL:
Inputs.Mouse.Wheel += _event.wheel.y; Inputs.Mouse.Wheel += _event.wheel.y;
break; 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 private void HandleTextInput(SDL.SDL_Event evt)
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)
{ {
// Based on the SDL2# LPUtf8StrMarshaler // Based on the SDL2# LPUtf8StrMarshaler
unsafe 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() private TimeSpan AdvanceElapsedTime()
{ {
long currentTicks = gameTimer.Elapsed.Ticks; long currentTicks = gameTimer.Elapsed.Ticks;
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks); TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
accumulatedElapsedTime += timeAdvanced; accumulatedUpdateTime += timeAdvanced;
accumulatedDrawTime += timeAdvanced;
previousTicks = currentTicks; previousTicks = currentTicks;
return timeAdvanced; return timeAdvanced;
} }

View File

@ -1,14 +1,22 @@
namespace MoonWorks.Graphics using System;
namespace MoonWorks.Graphics
{ {
public struct TextureSamplerBinding public struct TextureSamplerBinding
{ {
public Texture Texture; public IntPtr TextureHandle;
public Sampler Sampler; public IntPtr SamplerHandle;
public TextureSamplerBinding(Texture texture, Sampler sampler) public TextureSamplerBinding(Texture texture, Sampler sampler)
{ {
Texture = texture; TextureHandle = texture.Handle;
Sampler = sampler; SamplerHandle = sampler.Handle;
}
public TextureSamplerBinding(IntPtr textureHandle, IntPtr samplerHandle)
{
TextureHandle = textureHandle;
SamplerHandle = samplerHandle;
} }
} }
} }

View File

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

View File

@ -1,7 +1,4 @@
using System; using System;
using System.Runtime.InteropServices;
using MoonWorks.Math;
using MoonWorks.Window;
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
@ -22,6 +19,8 @@ namespace MoonWorks.Graphics
Handle = handle; Handle = handle;
} }
// FIXME: we can probably use the NativeMemory functions to not have to generate arrays here
/// <summary> /// <summary>
/// Begins a render pass. /// Begins a render pass.
/// All render state, resource binding, and draw commands must be made within 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( Refresh.Refresh_BeginRenderPass(
Device.Handle, Device.Handle,
Handle, Handle,
colorAttachmentInfos[0].renderTarget.TextureSlice.Rectangle.ToRefresh(), IntPtr.Zero,
(IntPtr) pColorAttachmentInfos, (IntPtr) pColorAttachmentInfos,
(uint) colorAttachmentInfos.Length, (uint) colorAttachmentInfos.Length,
IntPtr.Zero IntPtr.Zero
@ -106,7 +105,7 @@ namespace MoonWorks.Graphics
Refresh.Refresh_BeginRenderPass( Refresh.Refresh_BeginRenderPass(
Device.Handle, Device.Handle,
Handle, Handle,
colorAttachmentInfos[0].renderTarget.TextureSlice.Rectangle.ToRefresh(), IntPtr.Zero,
pColorAttachmentInfos, pColorAttachmentInfos,
(uint) colorAttachmentInfos.Length, (uint) colorAttachmentInfos.Length,
&refreshDepthStencilAttachmentInfo &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> /// <summary>
/// Binds vertex buffers to be used by subsequent draw calls. /// Binds vertex buffers to be used by subsequent draw calls.
/// </summary> /// </summary>
@ -400,8 +423,8 @@ namespace MoonWorks.Graphics
for (var i = 0; i < length; i += 1) for (var i = 0; i < length; i += 1)
{ {
texturePtrs[i] = textureSamplerBindings[i].Texture.Handle; texturePtrs[i] = textureSamplerBindings[i].TextureHandle;
samplerPtrs[i] = textureSamplerBindings[i].Sampler.Handle; samplerPtrs[i] = textureSamplerBindings[i].SamplerHandle;
} }
Refresh.Refresh_BindVertexSamplers( Refresh.Refresh_BindVertexSamplers(
@ -438,8 +461,19 @@ namespace MoonWorks.Graphics
for (var i = 0; i < length; i += 1) for (var i = 0; i < length; i += 1)
{ {
texturePtrs[i] = textureSamplerBindings[i].Texture.Handle; #if DEBUG
samplerPtrs[i] = textureSamplerBindings[i].Sampler.Handle; 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( Refresh.Refresh_BindFragmentSamplers(
@ -475,7 +509,7 @@ namespace MoonWorks.Graphics
Device.Handle, Device.Handle,
Handle, Handle,
(IntPtr) ptr, (IntPtr) ptr,
(uint) (uniforms.Length * Marshal.SizeOf<T>()) (uint) (uniforms.Length * sizeof(T))
); );
} }
} }
@ -494,7 +528,7 @@ namespace MoonWorks.Graphics
Device.Handle, Device.Handle,
Handle, Handle,
(IntPtr) ptr, (IntPtr) ptr,
(uint) (uniforms.Length * Marshal.SizeOf<T>()) (uint) (uniforms.Length * sizeof(T))
); );
} }
} }
@ -513,49 +547,11 @@ namespace MoonWorks.Graphics
Device.Handle, Device.Handle,
Handle, Handle,
(IntPtr) ptr, (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> /// <summary>
/// Draws using instanced rendering. /// Draws using instanced rendering.
/// It is an error to call this method unless two vertex buffers have been bound. /// It is an error to call this method unless two vertex buffers have been bound.
@ -651,126 +647,35 @@ namespace MoonWorks.Graphics
} }
/// <summary> /// <summary>
/// Prepares a texture to be presented to a window. /// Acquires a swapchain texture.
/// This particular variant of this method will present to the entire window area. /// 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> /// </summary>
/// <param name="texture">The texture to present.</param> public Texture? AcquireSwapchainTexture(
/// <param name="filter">The filter to use when the texture size differs from the window size.</param> Window window
public void QueuePresent(
Texture texture,
Filter filter,
OSWindow window
) )
{ {
var refreshTextureSlice = new Refresh.TextureSlice var texturePtr = Refresh.Refresh_AcquireSwapchainTexture(
{
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, Device.Handle,
Handle, Handle,
refreshTextureSlice, window.Handle,
IntPtr.Zero, out var width,
(Refresh.Filter) filter, out var height
window.Handle
); );
if (texturePtr == IntPtr.Zero)
{
return null;
} }
/// <summary> return new Texture(
/// Prepares a texture slice to be presented to a window. Device,
/// This particular variant of this method will present to the entire window area. texturePtr,
/// </summary> Device.GetSwapchainFormat(window),
/// <param name="textureSlice">The texture slice to present.</param> width,
/// <param name="filter">The filter to use when the texture size differs from the window size.</param> height
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
); );
} }
@ -838,7 +743,7 @@ namespace MoonWorks.Graphics
uint numElements uint numElements
) where T : unmanaged ) where T : unmanaged
{ {
var elementSize = Marshal.SizeOf<T>(); var elementSize = sizeof(T);
fixed (T* ptr = &data[0]) 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> /// <summary>
/// Asynchronously copies data into a texture. /// Asynchronously copies data into a texture.
/// </summary> /// </summary>
@ -871,7 +792,7 @@ namespace MoonWorks.Graphics
/// <param name="data">An array of data to copy into the texture.</param> /// <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 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]) fixed (T* ptr = &data[0])
{ {
@ -912,6 +833,26 @@ namespace MoonWorks.Graphics
SetTextureData(new TextureSlice(texture), dataPtr, dataLengthInBytes); 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> /// <summary>
/// Performs an asynchronous texture-to-texture copy on the GPU. /// Performs an asynchronous texture-to-texture copy on the GPU.
/// </summary> /// </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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
@ -8,10 +9,14 @@ namespace MoonWorks.Graphics
{ {
public IntPtr Handle { get; } 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; } public bool IsDisposed { get; private set; }
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>(); 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( public GraphicsDevice(
IntPtr deviceWindowHandle, IntPtr deviceWindowHandle,
@ -29,6 +34,26 @@ namespace MoonWorks.Graphics
presentationParameters, presentationParameters,
Conversions.BoolToByte(debugMode) 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() public CommandBuffer AcquireCommandBuffer()
@ -57,24 +82,9 @@ namespace MoonWorks.Graphics
Refresh.Refresh_Wait(Handle); Refresh.Refresh_Wait(Handle);
} }
internal void SubmitDestroyCommandBuffer() public TextureFormat GetSwapchainFormat(Window window)
{ {
if (resourcesToDestroy.Count > 0) return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
{
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);
} }
internal void AddResourceReference(WeakReference<GraphicsResource> resourceReference) 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) protected virtual void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
@ -112,7 +127,6 @@ namespace MoonWorks.Graphics
resources.Clear(); resources.Clear();
} }
SubmitDestroyCommandBuffer();
Refresh.Refresh_DestroyDevice(Handle); Refresh.Refresh_DestroyDevice(Handle);
} }

View File

@ -8,26 +8,28 @@ namespace MoonWorks.Graphics
public IntPtr Handle { get; protected set; } public IntPtr Handle { get; protected set; }
public bool IsDisposed { get; private 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; private WeakReference<GraphicsResource> selfReference;
public GraphicsResource(GraphicsDevice device) public GraphicsResource(GraphicsDevice device, bool trackResource = true)
{ {
Device = device; Device = device;
if (trackResource)
{
selfReference = new WeakReference<GraphicsResource>(this); selfReference = new WeakReference<GraphicsResource>(this);
Device.AddResourceReference(selfReference); Device.AddResourceReference(selfReference);
} }
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
Device.PrepareDestroyResource(this, QueueDestroyFunction);
if (selfReference != null) if (selfReference != null)
{ {
QueueDestroyFunction(Device.Handle, Handle);
Device.RemoveResourceReference(selfReference); Device.RemoveResourceReference(selfReference);
selfReference = null; selfReference = null;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,12 +60,14 @@ namespace MoonWorks.Graphics
public enum TextureFormat public enum TextureFormat
{ {
R8G8B8A8, R8G8B8A8,
B8G8R8A8,
R5G6B5, R5G6B5,
A1R5G5B5, A1R5G5B5,
B4G4R4A4, B4G4R4A4,
BC1, BC1,
BC2, BC2,
BC3, BC3,
BC7,
R8G8_SNORM, R8G8_SNORM,
R8G8B8A8_SNORM, R8G8B8A8_SNORM,
A2R10G10B10, A2R10G10B10,
@ -123,7 +125,8 @@ namespace MoonWorks.Graphics
public enum VertexElementFormat public enum VertexElementFormat
{ {
Single, UInt,
Float,
Vector2, Vector2,
Vector3, Vector3,
Vector4, Vector4,
@ -146,16 +149,14 @@ namespace MoonWorks.Graphics
public enum FillMode public enum FillMode
{ {
Fill, Fill,
Line, Line
Point
} }
public enum CullMode public enum CullMode
{ {
None, None,
Front, Front,
Back, Back
FrontAndBack
} }
public enum FrontFace public enum FrontFace
@ -197,26 +198,6 @@ namespace MoonWorks.Graphics
Max Max
} }
public enum LogicOp
{
Clear,
And,
AndReverse,
Copy,
AndInverted,
NoOp,
Xor,
Or,
Nor,
Equivalent,
Invert,
OrReverse,
CopyInverted,
OrInverted,
Nand,
Set
}
public enum BlendFactor public enum BlendFactor
{ {
Zero, Zero,
@ -231,8 +212,6 @@ namespace MoonWorks.Graphics
OneMinusDestinationAlpha, OneMinusDestinationAlpha,
ConstantColor, ConstantColor,
OneMinusConstantColor, OneMinusConstantColor,
ConstantAlpha,
OneMinusConstantAlpha,
SourceAlphaSaturate, SourceAlphaSaturate,
SourceOneColor, SourceOneColor,
OneMinusSourceOneColor, OneMinusSourceOneColor,
@ -263,17 +242,10 @@ namespace MoonWorks.Graphics
None = 0 None = 0
} }
public enum ShaderStageType
{
Vertex,
Fragment
}
public enum Filter public enum Filter
{ {
Nearest, Nearest,
Linear, Linear
Cubic
} }
public enum SamplerMipmapMode public enum SamplerMipmapMode

View File

@ -12,6 +12,12 @@ namespace MoonWorks.Graphics
public float Depth; public float Depth;
public uint Stencil; public uint Stencil;
public DepthStencilValue(float depth, uint stencil)
{
Depth = depth;
Stencil = stencil;
}
// FIXME: can we do an unsafe cast somehow? // FIXME: can we do an unsafe cast somehow?
public Refresh.DepthStencilValue ToRefresh() public Refresh.DepthStencilValue ToRefresh()
{ {
@ -31,6 +37,22 @@ namespace MoonWorks.Graphics
public int W; public int W;
public int H; 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? // FIXME: can we do an unsafe cast somehow?
public Refresh.Rect ToRefresh() public Refresh.Rect ToRefresh()
{ {
@ -53,6 +75,49 @@ namespace MoonWorks.Graphics
public float H; public float H;
public float MinDepth; public float MinDepth;
public float MaxDepth; 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)] [StructLayout(LayoutKind.Sequential)]
@ -61,6 +126,17 @@ namespace MoonWorks.Graphics
public uint Binding; public uint Binding;
public uint Stride; public uint Stride;
public VertexInputRate InputRate; 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)] [StructLayout(LayoutKind.Sequential)]
@ -70,6 +146,28 @@ namespace MoonWorks.Graphics
public uint Binding; public uint Binding;
public VertexElementFormat Format; public VertexElementFormat Format;
public uint Offset; public uint Offset;
public static VertexAttribute Create<T>(
string fieldName,
uint location,
uint binding = 0
)
{
var fieldInfo = typeof(T).GetField(fieldName);
if (fieldInfo == null)
{
throw new System.ArgumentException("Field not recognized!");
}
return new VertexAttribute
{
Binding = binding,
Location = location,
Format = Conversions.TypeToVertexElementFormat(fieldInfo.FieldType),
Offset = (uint) Marshal.OffsetOf<T>(fieldName)
};
}
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@ -102,25 +200,57 @@ namespace MoonWorks.Graphics
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentInfo public struct ColorAttachmentInfo
{ {
public RenderTarget renderTarget; public Texture Texture;
public Color clearColor; public uint Depth;
public LoadOp loadOp; public uint Layer;
public StoreOp storeOp; 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() public Refresh.ColorAttachmentInfo ToRefresh()
{ {
return new Refresh.ColorAttachmentInfo return new Refresh.ColorAttachmentInfo
{ {
renderTarget = renderTarget.Handle, texture = Texture.Handle,
depth = Depth,
layer = Layer,
level = Level,
sampleCount = (Refresh.SampleCount) SampleCount,
clearColor = new Refresh.Vec4 clearColor = new Refresh.Vec4
{ {
x = clearColor.R / 255f, x = ClearColor.R / 255f,
y = clearColor.G / 255f, y = ClearColor.G / 255f,
z = clearColor.B / 255f, z = ClearColor.B / 255f,
w = clearColor.A / 255f w = ClearColor.A / 255f
}, },
loadOp = (Refresh.LoadOp) loadOp, loadOp = (Refresh.LoadOp) LoadOp,
storeOp = (Refresh.StoreOp) storeOp storeOp = (Refresh.StoreOp) StoreOp
}; };
} }
} }
@ -128,23 +258,65 @@ namespace MoonWorks.Graphics
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public struct DepthStencilAttachmentInfo public struct DepthStencilAttachmentInfo
{ {
public RenderTarget depthStencilTarget; public Texture Texture;
public DepthStencilValue depthStencilValue; public uint Depth;
public LoadOp loadOp; public uint Layer;
public StoreOp storeOp; public uint Level;
public LoadOp stencilLoadOp; public DepthStencilValue DepthStencilClearValue;
public StoreOp stencilStoreOp; 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() public Refresh.DepthStencilAttachmentInfo ToRefresh()
{ {
return new Refresh.DepthStencilAttachmentInfo return new Refresh.DepthStencilAttachmentInfo
{ {
depthStencilTarget = depthStencilTarget.Handle, texture = Texture.Handle,
depthStencilValue = depthStencilValue.ToRefresh(), depth = Depth,
loadOp = (Refresh.LoadOp) loadOp, layer = Layer,
storeOp = (Refresh.StoreOp) storeOp, level = Level,
stencilLoadOp = (Refresh.LoadOp) stencilLoadOp, depthStencilClearValue = DepthStencilClearValue.ToRefresh(),
stencilStoreOp = (Refresh.StoreOp) stencilStoreOp 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)] [StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentDescription public struct ColorAttachmentDescription
{ {
public TextureFormat format; public TextureFormat Format;
public SampleCount sampleCount; 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> /// </summary>
public class Buffer : GraphicsResource 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> /// <summary>
/// Creates a buffer. /// Creates a buffer.
@ -28,6 +54,7 @@ namespace MoonWorks.Graphics
(Refresh.BufferUsageFlags) usageFlags, (Refresh.BufferUsageFlags) usageFlags,
sizeInBytes sizeInBytes
); );
Size = sizeInBytes;
} }
/// <summary> /// <summary>

View File

@ -6,40 +6,30 @@ namespace MoonWorks.Graphics
{ {
public class ComputePipeline : GraphicsResource 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( public unsafe ComputePipeline(
GraphicsDevice device, GraphicsDevice device,
ShaderStageState computeShaderState, ComputeShaderInfo computeShaderInfo
uint bufferBindingCount,
uint imageBindingCount
) : base(device) ) : base(device)
{ {
var computePipelineLayoutCreateInfo = new Refresh.ComputePipelineLayoutCreateInfo var refreshComputeShaderInfo = new Refresh.ComputeShaderInfo
{ {
bufferBindingCount = bufferBindingCount, entryPointName = computeShaderInfo.EntryPointName,
imageBindingCount = imageBindingCount shaderModule = computeShaderInfo.ShaderModule.Handle,
}; uniformBufferSize = computeShaderInfo.UniformBufferSize,
bufferBindingCount = computeShaderInfo.BufferBindingCount,
var computePipelineCreateInfo = new Refresh.ComputePipelineCreateInfo imageBindingCount = computeShaderInfo.ImageBindingCount
{
pipelineLayoutCreateInfo = computePipelineLayoutCreateInfo,
computeShaderState = new Refresh.ShaderStageState
{
entryPointName = computeShaderState.EntryPointName,
shaderModule = computeShaderState.ShaderModule.Handle,
uniformBufferSize = computeShaderState.UniformBufferSize
}
}; };
Handle = Refresh.Refresh_CreateComputePipeline( Handle = Refresh.Refresh_CreateComputePipeline(
device.Handle, device.Handle,
computePipelineCreateInfo refreshComputeShaderInfo
); );
ComputeShaderState = computeShaderState; ComputeShaderInfo = computeShaderInfo;
} }
} }
} }

View File

@ -10,27 +10,25 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public class GraphicsPipeline : GraphicsResource 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 GraphicsShaderInfo VertexShaderInfo { get; }
public ShaderStageState FragmentShaderState { get; } public GraphicsShaderInfo FragmentShaderInfo { get; }
public unsafe GraphicsPipeline( public unsafe GraphicsPipeline(
GraphicsDevice device, GraphicsDevice device,
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
) : base(device) ) : base(device)
{ {
ColorBlendState colorBlendState = graphicsPipelineCreateInfo.ColorBlendState;
DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState; DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState;
ShaderStageState vertexShaderState = graphicsPipelineCreateInfo.VertexShaderState; GraphicsShaderInfo vertexShaderInfo = graphicsPipelineCreateInfo.VertexShaderInfo;
ShaderStageState fragmentShaderState = graphicsPipelineCreateInfo.FragmentShaderState; GraphicsShaderInfo fragmentShaderInfo = graphicsPipelineCreateInfo.FragmentShaderInfo;
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState; MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
GraphicsPipelineLayoutInfo pipelineLayoutInfo = graphicsPipelineCreateInfo.PipelineLayoutInfo;
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState; RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType; PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState; VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
ViewportState viewportState = graphicsPipelineCreateInfo.ViewportState;
GraphicsPipelineAttachmentInfo attachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo; GraphicsPipelineAttachmentInfo attachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
BlendConstants blendConstants = graphicsPipelineCreateInfo.BlendConstants;
var vertexAttributesHandle = GCHandle.Alloc( var vertexAttributesHandle = GCHandle.Alloc(
vertexInputState.VertexAttributes, vertexInputState.VertexAttributes,
@ -40,44 +38,24 @@ namespace MoonWorks.Graphics
vertexInputState.VertexBindings, vertexInputState.VertexBindings,
GCHandleType.Pinned 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[ 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].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
colorAttachmentDescriptions[i].sampleCount = (Refresh.SampleCount) attachmentInfo.colorAttachmentDescriptions[i].sampleCount; colorAttachmentDescriptions[i].sampleCount = (Refresh.SampleCount) attachmentInfo.ColorAttachmentDescriptions[i].SampleCount;
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
} }
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo; Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
refreshGraphicsPipelineCreateInfo.colorBlendState.logicOpEnable = Conversions.BoolToByte(colorBlendState.LogicOpEnable); refreshGraphicsPipelineCreateInfo.blendConstants[0] = blendConstants.R;
refreshGraphicsPipelineCreateInfo.colorBlendState.logicOp = (Refresh.LogicOp) colorBlendState.LogicOp; refreshGraphicsPipelineCreateInfo.blendConstants[1] = blendConstants.G;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendStates = (IntPtr) colorTargetBlendStates; refreshGraphicsPipelineCreateInfo.blendConstants[2] = blendConstants.B;
refreshGraphicsPipelineCreateInfo.colorBlendState.blendStateCount = (uint) colorBlendState.ColorTargetBlendStates.Length; refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A;
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.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh(); refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp; refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp;
@ -89,20 +67,19 @@ namespace MoonWorks.Graphics
refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds; refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds;
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable); refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable);
refreshGraphicsPipelineCreateInfo.vertexShaderState.entryPointName = vertexShaderState.EntryPointName; refreshGraphicsPipelineCreateInfo.vertexShaderInfo.entryPointName = vertexShaderInfo.EntryPointName;
refreshGraphicsPipelineCreateInfo.vertexShaderState.shaderModule = vertexShaderState.ShaderModule.Handle; refreshGraphicsPipelineCreateInfo.vertexShaderInfo.shaderModule = vertexShaderInfo.ShaderModule.Handle;
refreshGraphicsPipelineCreateInfo.vertexShaderState.uniformBufferSize = vertexShaderState.UniformBufferSize; refreshGraphicsPipelineCreateInfo.vertexShaderInfo.uniformBufferSize = vertexShaderInfo.UniformBufferSize;
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.samplerBindingCount = vertexShaderInfo.SamplerBindingCount;
refreshGraphicsPipelineCreateInfo.fragmentShaderState.entryPointName = fragmentShaderState.EntryPointName; refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.entryPointName = fragmentShaderInfo.EntryPointName;
refreshGraphicsPipelineCreateInfo.fragmentShaderState.shaderModule = fragmentShaderState.ShaderModule.Handle; refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.shaderModule = fragmentShaderInfo.ShaderModule.Handle;
refreshGraphicsPipelineCreateInfo.fragmentShaderState.uniformBufferSize = fragmentShaderState.UniformBufferSize; refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.uniformBufferSize = fragmentShaderInfo.UniformBufferSize;
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.samplerBindingCount = fragmentShaderInfo.SamplerBindingCount;
refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount) multisampleState.MultisampleCount; refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount) multisampleState.MultisampleCount;
refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask; refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask;
refreshGraphicsPipelineCreateInfo.pipelineLayoutCreateInfo.vertexSamplerBindingCount = pipelineLayoutInfo.VertexSamplerBindingCount;
refreshGraphicsPipelineCreateInfo.pipelineLayoutCreateInfo.fragmentSamplerBindingCount = pipelineLayoutInfo.FragmentSamplerBindingCount;
refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode) rasterizerState.CullMode; refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode) rasterizerState.CullMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp; refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp;
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor; refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
@ -111,34 +88,26 @@ namespace MoonWorks.Graphics
refreshGraphicsPipelineCreateInfo.rasterizerState.depthClampEnable = Conversions.BoolToByte(rasterizerState.DepthClampEnable); refreshGraphicsPipelineCreateInfo.rasterizerState.depthClampEnable = Conversions.BoolToByte(rasterizerState.DepthClampEnable);
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode; refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode;
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace; refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace;
refreshGraphicsPipelineCreateInfo.rasterizerState.lineWidth = rasterizerState.LineWidth;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject(); refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length; refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length;
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject(); refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject();
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindingCount = (uint) vertexInputState.VertexBindings.Length; 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.primitiveType = (Refresh.PrimitiveType) primitiveType;
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentCount = attachmentInfo.colorAttachmentCount; refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentCount = (uint) attachmentInfo.ColorAttachmentDescriptions.Length;
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentDescriptions = (IntPtr) colorAttachmentDescriptions; refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentDescriptions = (IntPtr) colorAttachmentDescriptions;
refreshGraphicsPipelineCreateInfo.attachmentInfo.depthStencilFormat = (Refresh.TextureFormat) attachmentInfo.depthStencilFormat; refreshGraphicsPipelineCreateInfo.attachmentInfo.depthStencilFormat = (Refresh.TextureFormat) attachmentInfo.DepthStencilFormat;
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.hasDepthStencilAttachment); refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment);
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo); Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
vertexAttributesHandle.Free(); vertexAttributesHandle.Free();
vertexBindingsHandle.Free(); vertexBindingsHandle.Free();
viewportHandle.Free();
scissorHandle.Free();
VertexShaderState = vertexShaderState; VertexShaderInfo = vertexShaderInfo;
FragmentShaderState = fragmentShaderState; 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> /// </summary>
public class Sampler : GraphicsResource 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( public Sampler(
GraphicsDevice device, GraphicsDevice device,

View File

@ -1,5 +1,6 @@
using RefreshCS; using RefreshCS;
using System; using System;
using System.IO;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
{ {
@ -8,19 +9,33 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public class ShaderModule : GraphicsResource 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) 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; Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
shaderModuleCreateInfo.codeSize = (UIntPtr) (bytecode.Length * sizeof(uint)); shaderModuleCreateInfo.codeSize = (UIntPtr) bytecode.Length;
shaderModuleCreateInfo.byteCode = (IntPtr) ptr; 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;
using System.IO;
using RefreshCS; using RefreshCS;
namespace MoonWorks.Graphics namespace MoonWorks.Graphics
@ -17,7 +18,7 @@ namespace MoonWorks.Graphics
public SampleCount SampleCount { get; } public SampleCount SampleCount { get; }
public TextureUsageFlags UsageFlags { 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> /// <summary>
/// Loads a PNG from a file path. /// Loads a PNG from a file path.
@ -56,11 +57,62 @@ namespace MoonWorks.Graphics
return texture; 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]) 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; IsCube = textureCreateInfo.IsCube;
SampleCount = textureCreateInfo.SampleCount; SampleCount = textureCreateInfo.SampleCount;
LevelCount = textureCreateInfo.LevelCount; LevelCount = textureCreateInfo.LevelCount;
SampleCount = textureCreateInfo.SampleCount;
UsageFlags = textureCreateInfo.UsageFlags; 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 namespace MoonWorks.Graphics
{ {
public struct ColorTargetBlendState public struct ColorAttachmentBlendState
{ {
/// <summary> /// <summary>
/// If disabled, no blending will occur. /// If disabled, no blending will occur.
@ -43,7 +43,7 @@ namespace MoonWorks.Graphics
/// </summary> /// </summary>
public BlendFactor SourceColorBlendFactor; public BlendFactor SourceColorBlendFactor;
public static readonly ColorTargetBlendState Additive = new ColorTargetBlendState public static readonly ColorAttachmentBlendState Additive = new ColorAttachmentBlendState
{ {
BlendEnable = true, BlendEnable = true,
AlphaBlendOp = BlendOp.Add, AlphaBlendOp = BlendOp.Add,
@ -55,7 +55,7 @@ namespace MoonWorks.Graphics
DestinationAlphaBlendFactor = BlendFactor.One DestinationAlphaBlendFactor = BlendFactor.One
}; };
public static readonly ColorTargetBlendState AlphaBlend = new ColorTargetBlendState public static readonly ColorAttachmentBlendState AlphaBlend = new ColorAttachmentBlendState
{ {
BlendEnable = true, BlendEnable = true,
AlphaBlendOp = BlendOp.Add, AlphaBlendOp = BlendOp.Add,
@ -67,7 +67,7 @@ namespace MoonWorks.Graphics
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
}; };
public static readonly ColorTargetBlendState NonPremultiplied = new ColorTargetBlendState public static readonly ColorAttachmentBlendState NonPremultiplied = new ColorAttachmentBlendState
{ {
BlendEnable = true, BlendEnable = true,
AlphaBlendOp = BlendOp.Add, AlphaBlendOp = BlendOp.Add,
@ -79,7 +79,7 @@ namespace MoonWorks.Graphics
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
}; };
public static readonly ColorTargetBlendState Opaque = new ColorTargetBlendState public static readonly ColorAttachmentBlendState Opaque = new ColorAttachmentBlendState
{ {
BlendEnable = true, BlendEnable = true,
AlphaBlendOp = BlendOp.Add, AlphaBlendOp = BlendOp.Add,
@ -91,21 +91,21 @@ namespace MoonWorks.Graphics
DestinationAlphaBlendFactor = BlendFactor.Zero DestinationAlphaBlendFactor = BlendFactor.Zero
}; };
public static readonly ColorTargetBlendState None = new ColorTargetBlendState public static readonly ColorAttachmentBlendState None = new ColorAttachmentBlendState
{ {
BlendEnable = false, BlendEnable = false,
ColorWriteMask = ColorComponentFlags.RGBA ColorWriteMask = ColorComponentFlags.RGBA
}; };
public static readonly ColorTargetBlendState Disable = new ColorTargetBlendState public static readonly ColorAttachmentBlendState Disable = new ColorAttachmentBlendState
{ {
BlendEnable = false, BlendEnable = false,
ColorWriteMask = ColorComponentFlags.None ColorWriteMask = ColorComponentFlags.None
}; };
public Refresh.ColorTargetBlendState ToRefreshColorTargetBlendState() public Refresh.ColorAttachmentBlendState ToRefresh()
{ {
return new Refresh.ColorTargetBlendState return new Refresh.ColorAttachmentBlendState
{ {
blendEnable = Conversions.BoolToByte(BlendEnable), blendEnable = Conversions.BoolToByte(BlendEnable),
alphaBlendOp = (Refresh.BlendOp) AlphaBlendOp, 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> /// </summary>
public struct GraphicsPipelineAttachmentInfo public struct GraphicsPipelineAttachmentInfo
{ {
public ColorAttachmentDescription[] colorAttachmentDescriptions; public ColorAttachmentDescription[] ColorAttachmentDescriptions;
public uint colorAttachmentCount; public bool HasDepthStencilAttachment;
public bool hasDepthStencilAttachment; public TextureFormat DepthStencilFormat;
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 struct GraphicsPipelineCreateInfo
{ {
public ColorBlendState ColorBlendState;
public DepthStencilState DepthStencilState; public DepthStencilState DepthStencilState;
public ShaderStageState VertexShaderState; public GraphicsShaderInfo VertexShaderInfo;
public ShaderStageState FragmentShaderState; public GraphicsShaderInfo FragmentShaderInfo;
public MultisampleState MultisampleState; public MultisampleState MultisampleState;
public GraphicsPipelineLayoutInfo PipelineLayoutInfo;
public RasterizerState RasterizerState; public RasterizerState RasterizerState;
public PrimitiveType PrimitiveType; public PrimitiveType PrimitiveType;
public VertexInputState VertexInputState; public VertexInputState VertexInputState;
public ViewportState ViewportState;
public GraphicsPipelineAttachmentInfo AttachmentInfo; 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> /// </summary>
public FrontFace FrontFace; 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 public static readonly RasterizerState CW_CullFront = new RasterizerState
{ {
CullMode = CullMode.Front, CullMode = CullMode.Front,
FrontFace = FrontFace.Clockwise, FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CW_CullBack = new RasterizerState public static readonly RasterizerState CW_CullBack = new RasterizerState
@ -60,8 +54,7 @@
CullMode = CullMode.Back, CullMode = CullMode.Back,
FrontFace = FrontFace.Clockwise, FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CW_CullNone = new RasterizerState public static readonly RasterizerState CW_CullNone = new RasterizerState
@ -69,8 +62,7 @@
CullMode = CullMode.None, CullMode = CullMode.None,
FrontFace = FrontFace.Clockwise, FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CW_Wireframe = new RasterizerState public static readonly RasterizerState CW_Wireframe = new RasterizerState
@ -78,8 +70,7 @@
CullMode = CullMode.None, CullMode = CullMode.None,
FrontFace = FrontFace.Clockwise, FrontFace = FrontFace.Clockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CCW_CullFront = new RasterizerState public static readonly RasterizerState CCW_CullFront = new RasterizerState
@ -87,8 +78,7 @@
CullMode = CullMode.Front, CullMode = CullMode.Front,
FrontFace = FrontFace.CounterClockwise, FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CCW_CullBack = new RasterizerState public static readonly RasterizerState CCW_CullBack = new RasterizerState
@ -96,8 +86,7 @@
CullMode = CullMode.Back, CullMode = CullMode.Back,
FrontFace = FrontFace.CounterClockwise, FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CCW_CullNone = new RasterizerState public static readonly RasterizerState CCW_CullNone = new RasterizerState
@ -105,8 +94,7 @@
CullMode = CullMode.None, CullMode = CullMode.None,
FrontFace = FrontFace.CounterClockwise, FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
public static readonly RasterizerState CCW_Wireframe = new RasterizerState public static readonly RasterizerState CCW_Wireframe = new RasterizerState
@ -114,8 +102,7 @@
CullMode = CullMode.None, CullMode = CullMode.None,
FrontFace = FrontFace.CounterClockwise, FrontFace = FrontFace.CounterClockwise,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
DepthBiasEnable = false, DepthBiasEnable = false
LineWidth = 1f
}; };
} }
} }

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 VertexBinding[] VertexBindings;
public VertexAttribute[] VertexAttributes; 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; 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 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 IsPressed => ButtonStatus == ButtonStatus.Pressed;
public bool IsHeld => ButtonStatus == ButtonStatus.Held; public bool IsHeld => ButtonStatus == ButtonStatus.Held;
public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held; public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held;
public bool IsReleased => ButtonStatus == ButtonStatus.Released; 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 (isPressed)
{ {
if (ButtonStatus == ButtonStatus.Pressed) if (ButtonStatus == ButtonStatus.Pressed)
{ {
ButtonStatus = ButtonStatus.Held; return new ButtonState(ButtonStatus.Held);
} }
else if (ButtonStatus == ButtonStatus.Released) else if (ButtonStatus == ButtonStatus.Released)
{ {
ButtonStatus = ButtonStatus.Pressed; return new ButtonState(ButtonStatus.Pressed);
} }
else if (ButtonStatus == ButtonStatus.Held)
{
return new ButtonState(ButtonStatus.Held);
}
}
return new ButtonState(ButtonStatus.Released);
}
public static ButtonState operator |(ButtonState a, ButtonState b)
{
if (a.ButtonStatus == ButtonStatus.Released)
{
return b;
}
else if (a.ButtonStatus == ButtonStatus.Pressed)
{
if (b.ButtonStatus == ButtonStatus.Held)
{
return new ButtonState(ButtonStatus.Held);
} }
else else
{ {
ButtonStatus = ButtonStatus.Released; return a;
}
}
else // held
{
return a;
} }
} }
} }

View File

@ -1,6 +1,6 @@
namespace MoonWorks.Input namespace MoonWorks.Input
{ {
internal enum ButtonStatus public enum ButtonStatus
{ {
/// <summary> /// <summary>
/// Indicates that the input is not pressed. /// 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;
using System.Collections.Generic;
using MoonWorks.Math; using MoonWorks.Math;
using SDL2; using SDL2;
@ -7,35 +8,241 @@ namespace MoonWorks.Input
public class Gamepad public class Gamepad
{ {
internal IntPtr Handle; internal IntPtr Handle;
internal int JoystickInstanceID;
public ButtonState A { get; } = new ButtonState(); public int Slot { get; internal set; }
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 float LeftX { get; private set; } public GamepadButton A { get; }
public float LeftY { get; private set; } public GamepadButton B { get; }
public float RightX { get; private set; } public GamepadButton X { get; }
public float RightY { get; private set; } public GamepadButton Y { get; }
public float TriggerLeft { get; private set; } public GamepadButton Back { get; }
public float TriggerRight { get; private set; } 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; 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) public bool SetVibration(float leftMotor, float rightMotor, uint durationInMilliseconds)
{ {
return SDL.SDL_GameControllerRumble( return SDL.SDL_GameControllerRumble(
@ -46,53 +253,37 @@ namespace MoonWorks.Input
) == 0; ) == 0;
} }
internal void Update() public GamepadButton Button(GamepadButtonCode buttonCode)
{ {
A.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A)); return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
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);
} }
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 TriggerCodeToTriggerButton[triggerCode];
return Normalize(axisValue, short.MinValue, short.MaxValue, -1, 1);
} }
// Triggers only go from 0 to short.MaxValue /// <summary>
private float UpdateTrigger(SDL.SDL_GameControllerAxis trigger) /// 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 EnumToAxis[(SDL.SDL_GameControllerAxis) axisCode].Value;
return Normalize(triggerValue, 0, short.MaxValue, 0, 1);
} }
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 SDL2;
using System; using System;
using System.Collections.Generic;
namespace MoonWorks.Input namespace MoonWorks.Input
{ {
public class Inputs public class Inputs
{ {
public const int MAX_GAMEPADS = 4;
public Keyboard Keyboard { get; } public Keyboard Keyboard { get; }
public Mouse Mouse { get; } public Mouse Mouse { get; }
List<Gamepad> gamepads = new List<Gamepad>(); Gamepad[] gamepads;
public static event Action<char> TextInput; public static event Action<char> TextInput;
public bool AnyPressed { get; private set; }
public VirtualButton AnyPressedButton { get; private set; }
internal Inputs() internal Inputs()
{ {
Keyboard = new Keyboard(); Keyboard = new Keyboard();
Mouse = new Mouse(); 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[slot] = new Gamepad(IntPtr.Zero, slot);
{
gamepads.Add(new Gamepad(SDL.SDL_GameControllerOpen(i)));
}
} }
} }
// Assumes that SDL_PumpEvents has been called! // Assumes that SDL_PumpEvents has been called!
internal void Update() internal void Update()
{ {
AnyPressed = false;
AnyPressedButton = default; // DeviceKind.None
Mouse.Wheel = 0;
Keyboard.Update(); Keyboard.Update();
if (Keyboard.AnyPressed)
{
AnyPressed = true;
AnyPressedButton = Keyboard.AnyPressedButton;
}
Mouse.Update(); Mouse.Update();
if (Mouse.AnyPressed)
{
AnyPressed = true;
AnyPressedButton = Mouse.AnyPressedButton;
}
foreach (var gamepad in gamepads) foreach (var gamepad in gamepads)
{ {
gamepad.Update(); gamepad.Update();
if (gamepad.AnyPressed)
{
AnyPressed = true;
AnyPressedButton = gamepad.AnyPressedButton;
}
} }
} }
public bool GamepadExists(int slot) 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) public Gamepad GetGamepad(int slot)
{ {
return gamepads[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) internal static void OnTextInput(char c)
{ {
if (TextInput != null) if (TextInput != null)

View File

@ -1,7 +1,7 @@
namespace MoonWorks.Input namespace MoonWorks.Input
{ {
// Enum values are equivalent to the SDL Scancode value. // Enum values are equivalent to the SDL Scancode value.
public enum Keycode : int public enum KeyCode : int
{ {
Unknown = 0, Unknown = 0,
A = 4, A = 4,

View File

@ -7,7 +7,12 @@ namespace MoonWorks.Input
{ {
public class Keyboard 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 int numKeys;
private static readonly char[] TextInputCharacters = new char[] private static readonly char[] TextInputCharacters = new char[]
@ -21,14 +26,14 @@ namespace MoonWorks.Input
(char) 22 // Ctrl+V (Paste) (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.Home, 0 },
{ Keycode.End, 1 }, { KeyCode.End, 1 },
{ Keycode.Backspace, 2 }, { KeyCode.Backspace, 2 },
{ Keycode.Tab, 3 }, { KeyCode.Tab, 3 },
{ Keycode.Return, 4 }, { KeyCode.Return, 4 },
{ Keycode.Delete, 5 } { KeyCode.Delete, 5 }
// Ctrl+V is special! // Ctrl+V is special!
}; };
@ -36,54 +41,69 @@ namespace MoonWorks.Input
{ {
SDL.SDL_GetKeyboardState(out numKeys); SDL.SDL_GetKeyboardState(out numKeys);
Keys = new ButtonState[numKeys]; Keys = new KeyboardButton[numKeys];
foreach (Keycode keycode in Enum.GetValues(typeof(Keycode))) foreach (KeyCode keycode in Enum.GetValues(typeof(KeyCode)))
{ {
Keys[(int) keycode] = new ButtonState(); Keys[(int) keycode] = new KeyboardButton(this, keycode);
} }
} }
internal void Update() internal void Update()
{ {
IntPtr keyboardState = SDL.SDL_GetKeyboardState(out _); AnyPressed = false;
foreach (int keycode in Enum.GetValues(typeof(Keycode))) State = SDL.SDL_GetKeyboardState(out _);
{
var keyDown = Marshal.ReadByte(keyboardState, keycode);
Keys[keycode].Update(Conversions.ByteToBool(keyDown));
if (Conversions.ByteToBool(keyDown)) foreach (int keycode in Enum.GetValues(typeof(KeyCode)))
{ {
if (TextInputBindings.TryGetValue((Keycode) keycode, out var textIndex)) var button = Keys[keycode];
button.Update();
if (button.IsPressed)
{
if (TextInputBindings.TryGetValue((KeyCode) keycode, out var textIndex))
{ {
Inputs.OnTextInput(TextInputCharacters[(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]); Inputs.OnTextInput(TextInputCharacters[6]);
} }
AnyPressed = true;
AnyPressedButton = button;
} }
} }
} }
public bool IsDown(Keycode keycode) public bool IsDown(KeyCode keycode)
{ {
return Keys[(int) keycode].IsDown; return Keys[(int) keycode].IsDown;
} }
public bool IsPressed(Keycode keycode) public bool IsPressed(KeyCode keycode)
{ {
return Keys[(int) keycode].IsPressed; return Keys[(int) keycode].IsPressed;
} }
public bool IsHeld(Keycode keycode) public bool IsHeld(KeyCode keycode)
{ {
return Keys[(int) keycode].IsHeld; return Keys[(int) keycode].IsHeld;
} }
public bool IsReleased(Keycode keycode) public bool IsReleased(KeyCode keycode)
{ {
return Keys[(int) keycode].IsReleased; return Keys[(int) keycode].IsReleased;
} }
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 namespace MoonWorks.Input
{ {
public class Mouse public class Mouse
{ {
public ButtonState LeftButton { get; } = new ButtonState(); public MouseButton LeftButton { get; }
public ButtonState MiddleButton { get; } = new ButtonState(); public MouseButton MiddleButton { get; }
public ButtonState RightButton { get; } = new ButtonState(); public MouseButton RightButton { get; }
public int X { get; private set; } public int X { get; private set; }
public int Y { get; private set; } public int Y { get; private set; }
@ -15,6 +16,11 @@ namespace MoonWorks.Input
public int Wheel { get; internal set; } 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; private bool relativeMode;
public 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() 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); var _ = SDL.SDL_GetRelativeMouseState(out var deltaX, out var deltaY);
X = x; X = x;
@ -40,14 +74,25 @@ namespace MoonWorks.Input
DeltaX = deltaX; DeltaX = deltaX;
DeltaY = deltaY; DeltaY = deltaY;
LeftButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_LMASK)); LeftButton.Update();
MiddleButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_MMASK)); MiddleButton.Update();
RightButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_RMASK)); 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 #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
[Serializable] [Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")] [DebuggerDisplay("{DebugDisplayString,nq}")]

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
using System.Text; using System.Text;
#endregion #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
/// <summary> /// <summary>
/// Defines a viewing frustum for intersection operations. /// Defines a viewing frustum for intersection operations.

View File

@ -21,7 +21,7 @@ using System.Diagnostics;
#endregion #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
/// <summary> /// <summary>
/// Describes a sphere in 3D-space for bounding operations. /// Describes a sphere in 3D-space for bounding operations.

View File

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

View File

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

View File

@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
/// <summary> /// <summary>
/// Represents the right-handed 4x4 floating point matrix, which can store translation, scale and rotation information. /// 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 #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
[Serializable] [Serializable]
[DebuggerDisplay("{DebugDisplayString,nq}")] [DebuggerDisplay("{DebugDisplayString,nq}")]

View File

@ -14,7 +14,7 @@
#endregion #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
/// <summary> /// <summary>
/// Defines the intersection between a <see cref="Plane"/> and a bounding volume. /// Defines the intersection between a <see cref="Plane"/> and a bounding volume.

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
/// <summary> /// <summary>
/// Describes a 2D-rectangle. /// 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 #endregion
namespace MoonWorks.Math namespace MoonWorks.Math.Float
{ {
/// <summary> /// <summary>
/// Describes a 2D-vector. /// Describes a 2D-vector.
@ -204,6 +204,14 @@ namespace MoonWorks.Math
Y *= val; Y *= val;
} }
/// <summary>
/// Turns this <see cref="Vector2"/> to an angle in radians.
/// </summary>
public float Angle()
{
return MathF.Atan2(Y, X);
}
/// <summary> /// <summary>
/// Returns a <see cref="String"/> representation of this <see cref="Vector2"/> in the format: /// Returns a <see cref="String"/> representation of this <see cref="Vector2"/> in the format:
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>]} /// {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); 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> /// <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. /// 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> /// </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> /// <summary>
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix4x4"/>. /// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix4x4"/>.
/// </summary> /// </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 #endregion
#region Public Static Operators #region Public Static Operators

View File

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

View File

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

View File

@ -280,6 +280,11 @@ namespace MoonWorks.Math
return result; return result;
} }
public static float Quantize(float value, float step)
{
return (float) System.Math.Floor(value / step) * step;
}
/// <summary> /// <summary>
/// Converts radians to degrees. /// Converts radians to degrees.
/// </summary> /// </summary>
@ -331,6 +336,61 @@ namespace MoonWorks.Math
return angle; 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 #endregion
#region Internal Static Methods #region Internal Static Methods

View File

@ -195,6 +195,8 @@ namespace MoonWorks
NativeLibrary.SetDllImportResolver(typeof(SDL2.SDL).Assembly, MapAndLoad); NativeLibrary.SetDllImportResolver(typeof(SDL2.SDL).Assembly, MapAndLoad);
NativeLibrary.SetDllImportResolver(typeof(RefreshCS.Refresh).Assembly, MapAndLoad); NativeLibrary.SetDllImportResolver(typeof(RefreshCS.Refresh).Assembly, MapAndLoad);
NativeLibrary.SetDllImportResolver(typeof(FAudio).Assembly, MapAndLoad); NativeLibrary.SetDllImportResolver(typeof(FAudio).Assembly, MapAndLoad);
NativeLibrary.SetDllImportResolver(typeof(WellspringCS.Wellspring).Assembly, MapAndLoad);
NativeLibrary.SetDllImportResolver(typeof(Theorafile).Assembly, MapAndLoad);
} }
#endregion #endregion

View File

@ -1,4 +1,4 @@
namespace MoonWorks.Window namespace MoonWorks
{ {
public enum ScreenMode 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 System;
using SDL2; using SDL2;
namespace MoonWorks.Window namespace MoonWorks
{ {
public class OSWindow : IDisposable public class Window : IDisposable
{ {
internal IntPtr Handle { get; } internal IntPtr Handle { get; }
public ScreenMode ScreenMode { get; } public ScreenMode ScreenMode { get; private set; }
public uint Width { get; } public uint Width { get; private set; }
public uint Height { get; } public uint Height { get; private set; }
private bool IsDisposed; private bool IsDisposed;
public OSWindow(WindowCreateInfo windowCreateInfo) public Window(WindowCreateInfo windowCreateInfo)
{ {
var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN; var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
@ -25,6 +25,16 @@ namespace MoonWorks.Window
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; 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; ScreenMode = windowCreateInfo.ScreenMode;
Handle = SDL.SDL_CreateWindow( Handle = SDL.SDL_CreateWindow(
@ -53,6 +63,8 @@ namespace MoonWorks.Window
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
} }
ScreenMode = screenMode;
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag); SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
} }
@ -65,6 +77,14 @@ namespace MoonWorks.Window
public void SetWindowSize(uint width, uint height) public void SetWindowSize(uint width, uint height)
{ {
SDL.SDL_SetWindowSize(Handle, (int) width, (int) 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) 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 // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false); 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;
}
}
}