Compare commits
312 Commits
Author | SHA1 | Date |
---|---|---|
cosmonaut | bb7e45b9a3 | |
cosmonaut | 4cedf768f7 | |
cosmonaut | d986b3013f | |
cosmonaut | 42e3ac91af | |
cosmonaut | 0df2944ccf | |
cosmonaut | e50fb472b1 | |
cosmonaut | df3f38a67b | |
Evan Hemsley | eaa9266521 | |
cosmonaut | 4dbd5a2cbe | |
cosmonaut | 2e890fd696 | |
cosmonaut | 385783a846 | |
cosmonaut | 450b08cbd8 | |
cosmonaut | 528fb7ac7c | |
cosmonaut | fcd08fe231 | |
cosmonaut | e961a18a83 | |
cosmonaut | 772a0378bb | |
cosmonaut | 40fb313d12 | |
cosmonaut | a736ed031d | |
cosmonaut | b2a0ca3515 | |
cosmonaut | 36a88afe52 | |
cosmonaut | 6c93350f7f | |
cosmonaut | 352bb34f82 | |
Evan Hemsley | de7d76c03d | |
cosmonaut | 18d92aeec8 | |
cosmonaut | e616b0fa62 | |
cosmonaut | 1d27a9e4a4 | |
cosmonaut | 514a0bed29 | |
cosmonaut | 78252d1f6c | |
cosmonaut | daae1a34b9 | |
cosmonaut | 2e5657789c | |
cosmonaut | 0c76c568a4 | |
cosmonaut | abdcac1608 | |
cosmonaut | d8064862bf | |
cosmonaut | b223c31c8b | |
cosmonaut | dd79090028 | |
cosmonaut | 653f90c29f | |
cosmonaut | 402c26131d | |
cosmonaut | b026b9e81f | |
cosmonaut | e0f05881b0 | |
cosmonaut | 7e18764942 | |
cosmonaut | 1bff459be6 | |
cosmonaut | be77e8bad1 | |
cosmonaut | 7f6b6a7bae | |
cosmonaut | 1de3c73bb7 | |
cosmonaut | e77c87c772 | |
cosmonaut | 088e7c4b6f | |
cosmonaut | f298a5ec11 | |
cosmonaut | 0cd2c799ee | |
cosmonaut | 81cd397013 | |
cosmonaut | e73c7ede55 | |
cosmonaut | 83f1cc24db | |
cosmonaut | 1d86d0c210 | |
cosmonaut | dbbd6540ab | |
cosmonaut | 74ae295036 | |
cosmonaut | 36ddb03d8f | |
cosmonaut | bf3ad0c8b0 | |
cosmonaut | f761d4f76e | |
cosmonaut | 4c731401ff | |
cosmonaut | 071518732e | |
cosmonaut | 1adb76d5c7 | |
cosmonaut | 5ff7da927a | |
cosmonaut | 0fd3365d1d | |
cosmonaut | affb592c15 | |
cosmonaut | 7e79e4a11d | |
cosmonaut | fc0937b2ff | |
cosmonaut | c83997609f | |
cosmonaut | b65d4e391c | |
cosmonaut | 56bab545ba | |
cosmonaut | 2ae116c72b | |
cosmonaut | bd3e70b096 | |
cosmonaut | b1fe7f96b2 | |
cosmonaut | 00366cc9d4 | |
cosmonaut | 3bc25bc3a1 | |
cosmonaut | 496eb670ab | |
cosmonaut | 00f4bfdeae | |
cosmonaut | adeba633e5 | |
cosmonaut | 300ef9f88e | |
cosmonaut | 76684eaa33 | |
cosmonaut | c037b4cb69 | |
cosmonaut | 5df08727c1 | |
cosmonaut | 537517afb9 | |
cosmonaut | bd405dfbf0 | |
cosmonaut | 2d7bb24b5c | |
cosmonaut | 0ea60a376b | |
cosmonaut | e3c2f0e119 | |
cosmonaut | 3584e670ee | |
cosmonaut | 5a2f7eadb8 | |
cosmonaut | 1e3f04235e | |
cosmonaut | dd06205399 | |
cosmonaut | 1cf04a7279 | |
cosmonaut | 3bd435746b | |
cosmonaut | 8134761e44 | |
cosmonaut | 8209051a3c | |
cosmonaut | 80f3711f4c | |
cosmonaut | 3a6b73e637 | |
cosmonaut | 12e7e6b9c1 | |
cosmonaut | 455f4048df | |
cosmonaut | 1f0e3b5040 | |
cosmonaut | f8b14ea94f | |
cosmonaut | 472da0edd2 | |
cosmonaut | bd825b6c91 | |
cosmonaut | 515c2ebbca | |
cosmonaut | 86322e9373 | |
cosmonaut | e9aacb44da | |
cosmonaut | 5baa1d7b40 | |
cosmonaut | f673803c37 | |
cosmonaut | 0f78cd1a0c | |
cosmonaut | 36ce74b58a | |
cosmonaut | 40d12357c0 | |
evan | e52fe60657 | |
TheSpydog | b39526ca90 | |
TheSpydog | 88d9119830 | |
TheSpydog | 05de9a4066 | |
TheSpydog | b1d30a9e6c | |
TheSpydog | 030745361b | |
cosmonaut | c43df10c2a | |
cosmonaut | 95981f0f03 | |
cosmonaut | 703b694bf6 | |
cosmonaut | fe561b61ef | |
TheSpydog | 230c1b41b4 | |
cosmonaut | 1fb3e5adf0 | |
TheSpydog | 6f8858c8b7 | |
TheSpydog | f96298f991 | |
cosmonaut | d76633bdfc | |
cosmonaut | debb76f62a | |
cosmonaut | ca61e94b13 | |
cosmonaut | 36e6c6f332 | |
cosmonaut | 916962da6c | |
cosmonaut | cfd52b00bd | |
cosmonaut | b0e1ad3cf8 | |
cosmonaut | 2c4e1b972a | |
cosmonaut | 751b8310ce | |
cosmonaut | 474b8fe37d | |
cosmonaut | bc41d2c079 | |
cosmonaut | d5ddd44bd3 | |
cosmonaut | de9d13757a | |
cosmonaut | ca9fb45536 | |
cosmonaut | faf81bceed | |
cosmonaut | fa992652b1 | |
marpe | c4bae77408 | |
cosmonaut | ecf1a8ed55 | |
cosmonaut | 970517c3c2 | |
cosmonaut | f3c5bbf902 | |
cosmonaut | 02b20f79f5 | |
TheSpydog | c2ef78d136 | |
TheSpydog | 5533eeb2fd | |
cosmonaut | 934f3fd623 | |
TheSpydog | 02b0c12ad8 | |
TheSpydog | 1b38f8606b | |
cosmonaut | db38ada410 | |
cosmonaut | bd98ae8441 | |
cosmonaut | 59190e619d | |
cosmonaut | ea86212199 | |
cosmonaut | 66c6ceec04 | |
cosmonaut | dfddc24d0e | |
cosmonaut | b66e077376 | |
cosmonaut | 07c0b1b9a2 | |
TheSpydog | 3ffdf8a929 | |
cosmonaut | bace9f570d | |
cosmonaut | 32f80282a0 | |
cosmonaut | 23252a149f | |
cosmonaut | 80f19e4521 | |
cosmonaut | 7ea9a6aea3 | |
cosmonaut | d5a7daa524 | |
cosmonaut | 5c080a4c42 | |
TheSpydog | eb8c350a47 | |
cosmonaut | 0933b8e70f | |
cosmonaut | 1af16231ce | |
cosmonaut | 0c6bb538fb | |
cosmonaut | db66fbf115 | |
cosmonaut | f93be99545 | |
TheSpydog | ebfd4fd457 | |
cosmonaut | b380707462 | |
cosmonaut | 491eafac76 | |
cosmonaut | 0dddf2a0af | |
cosmonaut | 15aefc2212 | |
cosmonaut | a427b79510 | |
TheSpydog | 49f852a822 | |
cosmonaut | c649b24dad | |
cosmonaut | f06ad263b7 | |
cosmonaut | 28e3479444 | |
cosmonaut | 72ad850ab4 | |
cosmonaut | 9cd6e6cc7b | |
cosmonaut | 9c8ec7ad5a | |
cosmonaut | 0b0a6feff5 | |
cosmonaut | 1e6455d070 | |
cosmonaut | efb9893aef | |
cosmonaut | 5a5fbc0c77 | |
cosmonaut | 40a2b31e90 | |
cosmonaut | 424f410688 | |
cosmonaut | d6bd11d63f | |
cosmonaut | f6fc80804e | |
cosmonaut | 646e5e9283 | |
cosmonaut | ab619192a6 | |
cosmonaut | d07a722fb1 | |
cosmonaut | 3543f074f4 | |
cosmonaut | d3e124cea1 | |
cosmonaut | 1cf3a13541 | |
cosmonaut | db5ca97726 | |
cosmonaut | d190df55b2 | |
cosmonaut | 1aa522ffa1 | |
cosmonaut | a9b3b53ce9 | |
cosmonaut | d32bbcd537 | |
cosmonaut | b23792acd0 | |
cosmonaut | 97fad21c0c | |
cosmonaut | 2f5d25b458 | |
cosmonaut | ba662d7c3e | |
cosmonaut | c1085db9c5 | |
cosmonaut | bb0b6daa91 | |
cosmonaut | 13519c3150 | |
cosmonaut | 6ab7a2f722 | |
cosmonaut | 436affe5de | |
darkerbit | ff544140e0 | |
cosmonaut | a10f18b4eb | |
cosmonaut | 26e7e5c809 | |
cosmonaut | d2a51ce524 | |
cosmonaut | 5a9709c843 | |
cosmonaut | 0e8188682e | |
cosmonaut | 9862bfd0a0 | |
cosmonaut | 4756fe2b14 | |
cosmonaut | e5c72c6f46 | |
cosmonaut | 318ca22021 | |
cosmonaut | 547f7a388e | |
cosmonaut | dd3fb75905 | |
cosmonaut | 2bd88e1dc1 | |
cosmonaut | 66d363459b | |
cosmonaut | b22d3bed30 | |
cosmonaut | 0d93207ae9 | |
cosmonaut | 5e2368bc7d | |
cosmonaut | a0082bcec6 | |
cosmonaut | 810f270a41 | |
cosmonaut | 27e0404ec0 | |
cosmonaut | be4b5cf2c7 | |
cosmonaut | 83eb268a4a | |
cosmonaut | 985e096a7b | |
cosmonaut | dccd81e029 | |
cosmonaut | 65568ea234 | |
cosmonaut | b49dc3720a | |
cosmonaut | 61a6d0bdc0 | |
cosmonaut | 72c9dd4bda | |
cosmonaut | 412f0ca179 | |
cosmonaut | b252d0eb92 | |
cosmonaut | ba66ed4225 | |
cosmonaut | 5e2b8de2d3 | |
cosmonaut | 35ded250ed | |
cosmonaut | f8146b799a | |
cosmonaut | 379bdcdcb1 | |
cosmonaut | 4b4abaab01 | |
cosmonaut | 6a1fa004d6 | |
cosmonaut | b1b6b84809 | |
cosmonaut | 08a3c01f66 | |
cosmonaut | ec5160c060 | |
cosmonaut | c96c7a0d90 | |
cosmonaut | f0d3dfccf9 | |
cosmonaut | 6ea3e24a91 | |
cosmonaut | 9f4b69e6aa | |
cosmonaut | 80c34c6b16 | |
cosmonaut | cc876b2132 | |
cosmonaut | 71d9f8f4fe | |
cosmonaut | 5050a9369d | |
cosmonaut | 3623e6b07c | |
cosmonaut | 1b5221f2c7 | |
cosmonaut | 0fb7e98cb5 | |
cosmonaut | 5424d05d63 | |
cosmonaut | e7addb953f | |
cosmonaut | 2a9286f31e | |
cosmonaut | 8f9aaf6d61 | |
cosmonaut | cf2d8473a1 | |
cosmonaut | b5b0f35b50 | |
cosmonaut | 527f47436a | |
cosmonaut | 40d9cdd33a | |
cosmonaut | c4d2e3b8ee | |
cosmonaut | a413863cf9 | |
cosmonaut | 17333cfb67 | |
cosmonaut | 42754ef80d | |
cosmonaut | 111df04c0f | |
cosmonaut | 9c83423c79 | |
cosmonaut | 7d3a7901b2 | |
cosmonaut | 278db7d55b | |
cosmonaut | 774028a013 | |
cosmonaut | f6369b6bce | |
cosmonaut | c34f74a99d | |
cosmonaut | d6606d90f6 | |
cosmonaut | ef10be4e9d | |
cosmonaut | 1fa73f0275 | |
cosmonaut | 81c882bd48 | |
cosmonaut | 7328cbc13d | |
cosmonaut | 32b269526f | |
cosmonaut | 9028a8b1a0 | |
cosmonaut | edd21ec573 | |
Caleb Cornett | 2469cf530a | |
cosmonaut | e5213da686 | |
cosmonaut | a88d8dabbb | |
cosmonaut | 1064bae828 | |
cosmonaut | 8a3d93d2dc | |
cosmonaut | 8973b3e658 | |
cosmonaut | a0c57c7a59 | |
cosmonaut | 5679dba978 | |
cosmonaut | cb25e6feff | |
cosmonaut | 4acc2588e1 | |
cosmonaut | 8022cd1011 | |
cosmonaut | d2fca3654b | |
cosmonaut | a5c8ebfc3a | |
cosmonaut | 7d7437721c | |
cosmonaut | 0c588b96f4 | |
cosmonaut | 1fe256a479 | |
cosmonaut | 9df9aaeb3a | |
cosmonaut | e1a26e7c69 | |
cosmonaut | 29891c51e5 | |
cosmonaut | 9f588d389b | |
cosmonaut | ae45aa9ff2 | |
cosmonaut | a337b94dfa |
|
@ -0,0 +1,14 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.cs]
|
||||
csharp_space_after_cast = true
|
||||
charset = utf-8-bom
|
||||
max_line_length = 100
|
|
@ -7,3 +7,9 @@
|
|||
[submodule "lib/RefreshCS"]
|
||||
path = lib/RefreshCS
|
||||
url = https://gitea.moonside.games/MoonsideGames/RefreshCS.git
|
||||
[submodule "lib/WellspringCS"]
|
||||
path = lib/WellspringCS
|
||||
url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git
|
||||
[submodule "lib/dav1dfile"]
|
||||
path = lib/dav1dfile
|
||||
url = https://github.com/MoonsideGames/dav1dfile.git
|
||||
|
|
|
@ -1,24 +1,42 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Platforms>x64</Platforms>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>11</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);lib\**\*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);lib\**\*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
|
||||
<ProjectReference Include=".\lib\RefreshCS\RefreshCS.csproj" />
|
||||
<ProjectReference Include=".\lib\FAudio\csharp\FAudio-CS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="lib\FAudio\csharp\FAudio.cs" />
|
||||
<Compile Include="lib\RefreshCS\src\Refresh.cs" />
|
||||
<Compile Include="lib\SDL2-CS\src\SDL2.cs" />
|
||||
<Compile Include="lib\WellspringCS\WellspringCS.cs" />
|
||||
<Compile Include="lib\dav1dfile\csharp\dav1dfile.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="MoonWorks.dll.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_fullscreen.vert.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\video_yuv2rgba.frag.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_transform.vert.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="src\Graphics\StockShaders\Binary\text_msdf.frag.refresh">
|
||||
<LogicalName>MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -5,10 +5,18 @@
|
|||
<dllmap dll="SDL2" os="linux,freebsd,netbsd" target="libSDL2-2.0.so.0"/>
|
||||
|
||||
<dllmap dll="Refresh" os="windows" target="Refresh.dll"/>
|
||||
<dllmap dll="Refresh" os="osx" target="libRefresh.0.dylib"/>
|
||||
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.0"/>
|
||||
<dllmap dll="Refresh" os="osx" target="libRefresh.1.dylib"/>
|
||||
<dllmap dll="Refresh" os="linux,freebsd,netbsd" target="libRefresh.so.1"/>
|
||||
|
||||
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
|
||||
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
|
||||
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
|
||||
|
||||
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
|
||||
<dllmap dll="Wellspring" os="osx" target="libWellspring.1.dylib"/>
|
||||
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.1"/>
|
||||
|
||||
<dllmap dll="dav1dfile" os="windows" target="dav1dfile.dll"/>
|
||||
<dllmap dll="dav1dfile" os="osx" target="libdav1dfile.1.dylib"/>
|
||||
<dllmap dll="dav1dfile" os="linux,freebsd,netbsd,openbsd" target="libdav1dfile.so.1"/>
|
||||
</configuration>
|
||||
|
|
|
@ -4,15 +4,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
VisualStudioVersion = 16.0.30717.126
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoonWorks", "MoonWorks.csproj", "{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{608AA31D-F163-4096-B4EF-B9C7D21D52BB} = {608AA31D-F163-4096-B4EF-B9C7D21D52BB}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS.Core", "lib\SDL2-CS\SDL2-CS.Core.csproj", "{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudio\csharp\FAudio-CS.Core.csproj", "{608AA31D-F163-4096-B4EF-B9C7D21D52BB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\RefreshCS\RefreshCS.csproj", "{AD7C94E4-0AFA-44CA-889C-110142369893}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -24,18 +15,6 @@ Global
|
|||
{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x64.Build.0 = Debug|x64
|
||||
{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.ActiveCfg = Release|x64
|
||||
{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.Build.0 = Release|x64
|
||||
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Debug|x64.Build.0 = Debug|x64
|
||||
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Release|x64.ActiveCfg = Release|x64
|
||||
{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Release|x64.Build.0 = Release|x64
|
||||
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Debug|x64.Build.0 = Debug|x64
|
||||
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Release|x64.ActiveCfg = Release|x64
|
||||
{608AA31D-F163-4096-B4EF-B9C7D21D52BB}.Release|x64.Build.0 = Release|x64
|
||||
{AD7C94E4-0AFA-44CA-889C-110142369893}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{AD7C94E4-0AFA-44CA-889C-110142369893}.Debug|x64.Build.0 = Debug|x64
|
||||
{AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.ActiveCfg = Release|x64
|
||||
{AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
14
README.md
14
README.md
|
@ -10,11 +10,25 @@ MoonWorks *does not* include things like a built-in physics engine, a GUI editor
|
|||
|
||||
MoonWorks uses strictly Free Open Source Software. It will never have any kind of dependency on proprietary products.
|
||||
|
||||
## Documentation
|
||||
|
||||
API Reference: https://moonside.games/docs/moonworksapi/
|
||||
|
||||
High-level documentation is provided here: https://moonside.games/docs/moonworks/
|
||||
|
||||
The source is documented in doc comments that your preferred IDE can read.
|
||||
|
||||
Join our Discord! https://discord.gg/ujhwdkHmhN
|
||||
|
||||
## Dependencies
|
||||
|
||||
* [SDL2](https://github.com/flibitijibibo/SDL2-CS) - Window management, Input
|
||||
* [Refresh](https://gitea.moonside.games/MoonsideGames/Refresh) - Graphics
|
||||
* [FAudio](https://github.com/FNA-XNA/FAudio) - Audio
|
||||
* [Wellspring](https://gitea.moonside.games/MoonsideGames/Wellspring) - Font Rendering
|
||||
* [dav1dfile](https://github.com/MoonsideGames/dav1dfile) - Compressed Video
|
||||
|
||||
Prebuilt dependencies can be obtained here: https://moonside.games/files/moonlibs.tar.bz2
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5e7991da958b1b22658aa358b5f049d42317125d
|
||||
Subproject commit 60480416bda930bf7544e6abe31b937f0daa0256
|
|
@ -1 +1 @@
|
|||
Subproject commit 64a6e2ac6508879cfc66a50d553be93ab116a2bb
|
||||
Subproject commit b5325e6d0329eeb35b074091a569a5f679852d28
|
|
@ -1 +1 @@
|
|||
Subproject commit 2b8d237fd4585d14ea837764ac247d4cd200158f
|
||||
Subproject commit e4afbb848586fca530b6538320f799f81a18b941
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains raw audio data in a specified Format. <br/>
|
||||
/// Submit this to a SourceVoice to play audio.
|
||||
/// </summary>
|
||||
public class AudioBuffer : AudioResource
|
||||
{
|
||||
IntPtr BufferDataPtr;
|
||||
uint BufferDataLength;
|
||||
private bool OwnsBufferData;
|
||||
|
||||
public Format Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new AudioBuffer.
|
||||
/// </summary>
|
||||
/// <param name="ownsBufferData">If true, the buffer data will be destroyed when this AudioBuffer is destroyed.</param>
|
||||
public AudioBuffer(
|
||||
AudioDevice device,
|
||||
Format format,
|
||||
IntPtr bufferPtr,
|
||||
uint bufferLengthInBytes,
|
||||
bool ownsBufferData) : base(device)
|
||||
{
|
||||
Format = format;
|
||||
BufferDataPtr = bufferPtr;
|
||||
BufferDataLength = bufferLengthInBytes;
|
||||
OwnsBufferData = ownsBufferData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create another AudioBuffer from this audio buffer.
|
||||
/// It will not own the buffer data.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset in bytes from the top of the original buffer.</param>
|
||||
/// <param name="length">Length in bytes of the new buffer.</param>
|
||||
/// <returns></returns>
|
||||
public AudioBuffer Slice(int offset, uint length)
|
||||
{
|
||||
return new AudioBuffer(Device, Format, BufferDataPtr + offset, length, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an FAudioBuffer struct from this AudioBuffer.
|
||||
/// </summary>
|
||||
/// <param name="loop">Whether we should set the FAudioBuffer to loop.</param>
|
||||
public FAudio.FAudioBuffer ToFAudioBuffer(bool loop = false)
|
||||
{
|
||||
return new FAudio.FAudioBuffer
|
||||
{
|
||||
Flags = FAudio.FAUDIO_END_OF_STREAM,
|
||||
pContext = IntPtr.Zero,
|
||||
pAudioData = BufferDataPtr,
|
||||
AudioBytes = BufferDataLength,
|
||||
PlayBegin = 0,
|
||||
PlayLength = 0,
|
||||
LoopBegin = 0,
|
||||
LoopLength = 0,
|
||||
LoopCount = loop ? FAudio.FAUDIO_LOOP_INFINITE : 0
|
||||
};
|
||||
}
|
||||
|
||||
protected override unsafe void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (OwnsBufferData)
|
||||
{
|
||||
NativeMemory.Free((void*) BufferDataPtr);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Streamable audio in Ogg format.
|
||||
/// </summary>
|
||||
public class AudioDataOgg : AudioDataStreamable
|
||||
{
|
||||
private IntPtr FileDataPtr = IntPtr.Zero;
|
||||
private IntPtr VorbisHandle = IntPtr.Zero;
|
||||
|
||||
private string FilePath;
|
||||
|
||||
public override bool Loaded => VorbisHandle != IntPtr.Zero;
|
||||
public override uint DecodeBufferSize => 32768;
|
||||
|
||||
public AudioDataOgg(AudioDevice device, string filePath) : base(device)
|
||||
{
|
||||
FilePath = filePath;
|
||||
|
||||
var handle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
|
||||
if (error != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Error loading file!");
|
||||
}
|
||||
|
||||
var info = FAudio.stb_vorbis_get_info(handle);
|
||||
|
||||
Format = new Format
|
||||
{
|
||||
Tag = FormatTag.IEEE_FLOAT,
|
||||
BitsPerSample = 32,
|
||||
Channels = (ushort) info.channels,
|
||||
SampleRate = info.sample_rate
|
||||
};
|
||||
|
||||
FAudio.stb_vorbis_close(handle);
|
||||
}
|
||||
|
||||
public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
|
||||
{
|
||||
var lengthInFloats = bufferLengthInBytes / sizeof(float);
|
||||
|
||||
/* NOTE: this function returns samples per channel, not total samples */
|
||||
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
|
||||
VorbisHandle,
|
||||
Format.Channels,
|
||||
(IntPtr) buffer,
|
||||
lengthInFloats
|
||||
);
|
||||
|
||||
var sampleCount = samples * Format.Channels;
|
||||
reachedEnd = sampleCount < lengthInFloats;
|
||||
filledLengthInBytes = sampleCount * sizeof(float);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the Ogg data for streaming.
|
||||
/// </summary>
|
||||
public override unsafe void Load()
|
||||
{
|
||||
if (!Loaded)
|
||||
{
|
||||
var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
|
||||
FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
|
||||
var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
|
||||
fileStream.ReadExactly(fileDataSpan);
|
||||
fileStream.Close();
|
||||
|
||||
VorbisHandle = FAudio.stb_vorbis_open_memory(FileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero);
|
||||
if (error != 0)
|
||||
{
|
||||
NativeMemory.Free((void*) FileDataPtr);
|
||||
Logger.LogError("Error opening OGG file!");
|
||||
Logger.LogError("Error: " + error);
|
||||
throw new InvalidOperationException("Error opening OGG file!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Seek(uint sampleFrame)
|
||||
{
|
||||
FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the Ogg data, freeing resources.
|
||||
/// </summary>
|
||||
public override unsafe void Unload()
|
||||
{
|
||||
if (Loaded)
|
||||
{
|
||||
FAudio.stb_vorbis_close(VorbisHandle);
|
||||
NativeMemory.Free((void*) FileDataPtr);
|
||||
|
||||
VorbisHandle = IntPtr.Zero;
|
||||
FileDataPtr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
|
||||
/// </summary>
|
||||
public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
|
||||
{
|
||||
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
|
||||
if (error != 0)
|
||||
{
|
||||
throw new InvalidOperationException("Error loading file!");
|
||||
}
|
||||
var info = FAudio.stb_vorbis_get_info(filePointer);
|
||||
var lengthInFloats =
|
||||
FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels;
|
||||
var lengthInBytes = lengthInFloats * Marshal.SizeOf<float>();
|
||||
var buffer = NativeMemory.Alloc((nuint) lengthInBytes);
|
||||
|
||||
FAudio.stb_vorbis_get_samples_float_interleaved(
|
||||
filePointer,
|
||||
info.channels,
|
||||
(nint) buffer,
|
||||
(int) lengthInFloats
|
||||
);
|
||||
|
||||
FAudio.stb_vorbis_close(filePointer);
|
||||
|
||||
var format = new Format
|
||||
{
|
||||
Tag = FormatTag.IEEE_FLOAT,
|
||||
BitsPerSample = 32,
|
||||
Channels = (ushort) info.channels,
|
||||
SampleRate = info.sample_rate
|
||||
};
|
||||
|
||||
return new AudioBuffer(
|
||||
device,
|
||||
format,
|
||||
(nint) buffer,
|
||||
(uint) lengthInBytes,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Streamable audio in QOA format.
|
||||
/// </summary>
|
||||
public class AudioDataQoa : AudioDataStreamable
|
||||
{
|
||||
private IntPtr QoaHandle = IntPtr.Zero;
|
||||
private IntPtr FileDataPtr = IntPtr.Zero;
|
||||
|
||||
private string FilePath;
|
||||
|
||||
private const uint QOA_MAGIC = 0x716f6166; /* 'qoaf' */
|
||||
|
||||
public override bool Loaded => QoaHandle != IntPtr.Zero;
|
||||
|
||||
private uint decodeBufferSize;
|
||||
public override uint DecodeBufferSize => decodeBufferSize;
|
||||
|
||||
public AudioDataQoa(AudioDevice device, string filePath) : base(device)
|
||||
{
|
||||
FilePath = filePath;
|
||||
|
||||
using var stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
UInt64 fileHeader = ReverseEndianness(reader.ReadUInt64());
|
||||
if ((fileHeader >> 32) != QOA_MAGIC)
|
||||
{
|
||||
throw new InvalidOperationException("Specified file is not a QOA file.");
|
||||
}
|
||||
|
||||
uint totalSamplesPerChannel = (uint) (fileHeader & (0xFFFFFFFF));
|
||||
if (totalSamplesPerChannel == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Specified file is not a valid QOA file.");
|
||||
}
|
||||
|
||||
UInt64 frameHeader = ReverseEndianness(reader.ReadUInt64());
|
||||
uint channels = (uint) ((frameHeader >> 56) & 0x0000FF);
|
||||
uint samplerate = (uint) ((frameHeader >> 32) & 0xFFFFFF);
|
||||
uint samplesPerChannelPerFrame = (uint) ((frameHeader >> 16) & 0x00FFFF);
|
||||
|
||||
Format = new Format
|
||||
{
|
||||
Tag = FormatTag.PCM,
|
||||
BitsPerSample = 16,
|
||||
Channels = (ushort) channels,
|
||||
SampleRate = samplerate
|
||||
};
|
||||
|
||||
decodeBufferSize = channels * samplesPerChannelPerFrame * sizeof(short);
|
||||
}
|
||||
|
||||
public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
|
||||
{
|
||||
var lengthInShorts = bufferLengthInBytes / sizeof(short);
|
||||
|
||||
// NOTE: this function returns samples per channel!
|
||||
var samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer);
|
||||
|
||||
var sampleCount = samples * Format.Channels;
|
||||
reachedEnd = sampleCount < lengthInShorts;
|
||||
filledLengthInBytes = (int) (sampleCount * sizeof(short));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares qoa data for streaming.
|
||||
/// </summary>
|
||||
public override unsafe void Load()
|
||||
{
|
||||
if (!Loaded)
|
||||
{
|
||||
var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
|
||||
FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
|
||||
var fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
|
||||
fileStream.ReadExactly(fileDataSpan);
|
||||
fileStream.Close();
|
||||
|
||||
QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0);
|
||||
if (QoaHandle == IntPtr.Zero)
|
||||
{
|
||||
NativeMemory.Free((void*) FileDataPtr);
|
||||
Logger.LogError("Error opening QOA file!");
|
||||
throw new InvalidOperationException("Error opening QOA file!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Seek(uint sampleFrame)
|
||||
{
|
||||
FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the qoa data, freeing resources.
|
||||
/// </summary>
|
||||
public override unsafe void Unload()
|
||||
{
|
||||
if (Loaded)
|
||||
{
|
||||
FAudio.qoa_close(QoaHandle);
|
||||
NativeMemory.Free((void*) FileDataPtr);
|
||||
|
||||
QoaHandle = IntPtr.Zero;
|
||||
FileDataPtr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the entire qoa file into an AudioBuffer. Useful for static audio.
|
||||
/// </summary>
|
||||
public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
|
||||
{
|
||||
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
var fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
|
||||
var fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
|
||||
fileStream.ReadExactly(fileDataSpan);
|
||||
fileStream.Close();
|
||||
|
||||
var qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0);
|
||||
if (qoaHandle == 0)
|
||||
{
|
||||
NativeMemory.Free(fileDataPtr);
|
||||
Logger.LogError("Error opening QOA file!");
|
||||
throw new InvalidOperationException("Error opening QOA file!");
|
||||
}
|
||||
|
||||
FAudio.qoa_attributes(qoaHandle, out var channels, out var samplerate, out var samples_per_channel_per_frame, out var total_samples_per_channel);
|
||||
|
||||
var bufferLengthInBytes = total_samples_per_channel * channels * sizeof(short);
|
||||
var buffer = NativeMemory.Alloc(bufferLengthInBytes);
|
||||
FAudio.qoa_decode_entire(qoaHandle, (short*) buffer);
|
||||
|
||||
FAudio.qoa_close(qoaHandle);
|
||||
NativeMemory.Free(fileDataPtr);
|
||||
|
||||
var format = new Format
|
||||
{
|
||||
Tag = FormatTag.PCM,
|
||||
BitsPerSample = 16,
|
||||
Channels = (ushort) channels,
|
||||
SampleRate = samplerate
|
||||
};
|
||||
|
||||
return new AudioBuffer(device, format, (nint) buffer, bufferLengthInBytes, true);
|
||||
}
|
||||
|
||||
private static unsafe UInt64 ReverseEndianness(UInt64 value)
|
||||
{
|
||||
byte* bytes = (byte*) &value;
|
||||
|
||||
return
|
||||
((UInt64)(bytes[0]) << 56) | ((UInt64)(bytes[1]) << 48) |
|
||||
((UInt64)(bytes[2]) << 40) | ((UInt64)(bytes[3]) << 32) |
|
||||
((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
|
||||
((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this in conjunction with a StreamingVoice to play back streaming audio data.
|
||||
/// </summary>
|
||||
public abstract class AudioDataStreamable : AudioResource
|
||||
{
|
||||
public Format Format { get; protected set; }
|
||||
public abstract bool Loaded { get; }
|
||||
public abstract uint DecodeBufferSize { get; }
|
||||
|
||||
protected AudioDataStreamable(AudioDevice device) : base(device)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the raw audio data into memory to prepare it for stream decoding.
|
||||
/// </summary>
|
||||
public abstract void Load();
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the raw audio data from memory.
|
||||
/// </summary>
|
||||
public abstract void Unload();
|
||||
|
||||
/// <summary>
|
||||
/// Seeks to the given sample frame.
|
||||
/// </summary>
|
||||
public abstract void Seek(uint sampleFrame);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decodes data of length bufferLengthInBytes into the provided buffer.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer that decoded bytes will be placed into.</param>
|
||||
/// <param name="bufferLengthInBytes">Requested length of decoded audio data.</param>
|
||||
/// <param name="filledLengthInBytes">How much data was actually filled in by the decode.</param>
|
||||
/// <param name="reachedEnd">Whether the end of the data was reached on this decode.</param>
|
||||
public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Unload();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public static class AudioDataWav
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an AudioBuffer containing all the WAV audio data in a file.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
|
||||
{
|
||||
// mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385
|
||||
|
||||
// WaveFormatEx data
|
||||
ushort wFormatTag;
|
||||
ushort nChannels;
|
||||
uint nSamplesPerSec;
|
||||
uint nAvgBytesPerSec;
|
||||
ushort nBlockAlign;
|
||||
ushort wBitsPerSample;
|
||||
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
// RIFF Signature
|
||||
string signature = new string(reader.ReadChars(4));
|
||||
if (signature != "RIFF")
|
||||
{
|
||||
throw new NotSupportedException("Specified stream is not a wave file.");
|
||||
}
|
||||
|
||||
reader.ReadUInt32(); // Riff Chunk Size
|
||||
|
||||
string wformat = new string(reader.ReadChars(4));
|
||||
if (wformat != "WAVE")
|
||||
{
|
||||
throw new NotSupportedException("Specified stream is not a wave file.");
|
||||
}
|
||||
|
||||
// WAVE Header
|
||||
string format_signature = new string(reader.ReadChars(4));
|
||||
while (format_signature != "fmt ")
|
||||
{
|
||||
reader.ReadBytes(reader.ReadInt32());
|
||||
format_signature = new string(reader.ReadChars(4));
|
||||
}
|
||||
|
||||
int format_chunk_size = reader.ReadInt32();
|
||||
|
||||
wFormatTag = reader.ReadUInt16();
|
||||
nChannels = reader.ReadUInt16();
|
||||
nSamplesPerSec = reader.ReadUInt32();
|
||||
nAvgBytesPerSec = reader.ReadUInt32();
|
||||
nBlockAlign = reader.ReadUInt16();
|
||||
wBitsPerSample = reader.ReadUInt16();
|
||||
|
||||
// Reads residual bytes
|
||||
if (format_chunk_size > 16)
|
||||
{
|
||||
reader.ReadBytes(format_chunk_size - 16);
|
||||
}
|
||||
|
||||
// data Signature
|
||||
string data_signature = new string(reader.ReadChars(4));
|
||||
while (data_signature.ToLowerInvariant() != "data")
|
||||
{
|
||||
reader.ReadBytes(reader.ReadInt32());
|
||||
data_signature = new string(reader.ReadChars(4));
|
||||
}
|
||||
if (data_signature != "data")
|
||||
{
|
||||
throw new NotSupportedException("Specified wave file is not supported.");
|
||||
}
|
||||
|
||||
int waveDataLength = reader.ReadInt32();
|
||||
var waveDataBuffer = NativeMemory.Alloc((nuint) waveDataLength);
|
||||
var waveDataSpan = new Span<byte>(waveDataBuffer, waveDataLength);
|
||||
stream.ReadExactly(waveDataSpan);
|
||||
|
||||
var format = new Format
|
||||
{
|
||||
Tag = (FormatTag) wFormatTag,
|
||||
BitsPerSample = wBitsPerSample,
|
||||
Channels = nChannels,
|
||||
SampleRate = nSamplesPerSec
|
||||
};
|
||||
|
||||
return new AudioBuffer(
|
||||
device,
|
||||
format,
|
||||
(nint) waveDataBuffer,
|
||||
(uint) waveDataLength,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,274 +1,354 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class AudioDevice : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
public byte[] Handle3D { get; }
|
||||
public IntPtr MasteringVoice { get; }
|
||||
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
|
||||
public IntPtr ReverbVoice { get; }
|
||||
/// <summary>
|
||||
/// AudioDevice manages all audio-related concerns.
|
||||
/// </summary>
|
||||
public class AudioDevice : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
public byte[] Handle3D { get; }
|
||||
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
|
||||
|
||||
public float CurveDistanceScalar = 1f;
|
||||
public float DopplerScale = 1f;
|
||||
public float SpeedOfSound = 343.5f;
|
||||
private IntPtr trueMasteringVoice;
|
||||
|
||||
internal FAudio.FAudioVoiceSends ReverbSends;
|
||||
// this is a fun little trick where we use a submix voice as a "faux" mastering voice
|
||||
// this lets us maintain API consistency for effects like panning and reverb
|
||||
private SubmixVoice fauxMasteringVoice;
|
||||
public SubmixVoice MasteringVoice => fauxMasteringVoice;
|
||||
|
||||
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
|
||||
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>();
|
||||
public float CurveDistanceScalar = 1f;
|
||||
public float DopplerScale = 1f;
|
||||
public float SpeedOfSound = 343.5f;
|
||||
|
||||
private bool IsDisposed;
|
||||
private readonly HashSet<GCHandle> resourceHandles = new HashSet<GCHandle>();
|
||||
private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
|
||||
|
||||
public unsafe AudioDevice()
|
||||
{
|
||||
FAudio.FAudioCreate(out var handle, 0, 0);
|
||||
Handle = handle;
|
||||
private AudioTweenManager AudioTweenManager;
|
||||
|
||||
/* Find a suitable device */
|
||||
private SourceVoicePool VoicePool;
|
||||
private List<SourceVoice> VoicesToReturn = new List<SourceVoice>();
|
||||
|
||||
FAudio.FAudio_GetDeviceCount(Handle, out var devices);
|
||||
private const int Step = 200;
|
||||
private TimeSpan UpdateInterval;
|
||||
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
|
||||
private long previousTickTime;
|
||||
private Thread Thread;
|
||||
private AutoResetEvent WakeSignal;
|
||||
internal readonly object StateLock = new object();
|
||||
|
||||
if (devices == 0)
|
||||
{
|
||||
Logger.LogError("No audio devices found!");
|
||||
Handle = IntPtr.Zero;
|
||||
FAudio.FAudio_Release(Handle);
|
||||
return;
|
||||
}
|
||||
private bool Running;
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
FAudio.FAudioDeviceDetails deviceDetails;
|
||||
internal unsafe AudioDevice()
|
||||
{
|
||||
UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
|
||||
|
||||
uint i = 0;
|
||||
for (i = 0; i < devices; i++)
|
||||
{
|
||||
FAudio.FAudio_GetDeviceDetails(
|
||||
Handle,
|
||||
i,
|
||||
out deviceDetails
|
||||
);
|
||||
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
|
||||
{
|
||||
DeviceDetails = deviceDetails;
|
||||
break;
|
||||
}
|
||||
}
|
||||
FAudio.FAudioCreate(out var handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
|
||||
Handle = handle;
|
||||
|
||||
if (i == devices)
|
||||
{
|
||||
i = 0; /* whatever we'll just use the first one I guess */
|
||||
FAudio.FAudio_GetDeviceDetails(
|
||||
Handle,
|
||||
i,
|
||||
out deviceDetails
|
||||
);
|
||||
DeviceDetails = deviceDetails;
|
||||
}
|
||||
/* Find a suitable device */
|
||||
|
||||
/* Init Mastering Voice */
|
||||
IntPtr masteringVoice;
|
||||
FAudio.FAudio_GetDeviceCount(Handle, out var devices);
|
||||
|
||||
if (FAudio.FAudio_CreateMasteringVoice(
|
||||
Handle,
|
||||
out masteringVoice,
|
||||
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
||||
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
||||
0,
|
||||
i,
|
||||
IntPtr.Zero
|
||||
) != 0)
|
||||
{
|
||||
Logger.LogError("No mastering voice found!");
|
||||
Handle = IntPtr.Zero;
|
||||
FAudio.FAudio_Release(Handle);
|
||||
return;
|
||||
}
|
||||
if (devices == 0)
|
||||
{
|
||||
Logger.LogError("No audio devices found!");
|
||||
FAudio.FAudio_Release(Handle);
|
||||
Handle = IntPtr.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
MasteringVoice = masteringVoice;
|
||||
FAudio.FAudioDeviceDetails deviceDetails;
|
||||
|
||||
/* Init 3D Audio */
|
||||
uint i = 0;
|
||||
for (i = 0; i < devices; i++)
|
||||
{
|
||||
FAudio.FAudio_GetDeviceDetails(
|
||||
Handle,
|
||||
i,
|
||||
out deviceDetails
|
||||
);
|
||||
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
|
||||
{
|
||||
DeviceDetails = deviceDetails;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
|
||||
FAudio.F3DAudioInitialize(
|
||||
DeviceDetails.OutputFormat.dwChannelMask,
|
||||
SpeedOfSound,
|
||||
Handle3D
|
||||
);
|
||||
if (i == devices)
|
||||
{
|
||||
i = 0; /* whatever we'll just use the first one I guess */
|
||||
FAudio.FAudio_GetDeviceDetails(
|
||||
Handle,
|
||||
i,
|
||||
out deviceDetails
|
||||
);
|
||||
DeviceDetails = deviceDetails;
|
||||
}
|
||||
|
||||
/* Init reverb */
|
||||
/* Init Mastering Voice */
|
||||
var result = FAudio.FAudio_CreateMasteringVoice(
|
||||
Handle,
|
||||
out trueMasteringVoice,
|
||||
FAudio.FAUDIO_DEFAULT_CHANNELS,
|
||||
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
|
||||
0,
|
||||
i,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
IntPtr reverbVoice;
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.LogError("Failed to create a mastering voice!");
|
||||
Logger.LogError("Audio device creation failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr reverb;
|
||||
FAudio.FAudioCreateReverb(out reverb, 0);
|
||||
fauxMasteringVoice = SubmixVoice.CreateFauxMasteringVoice(this);
|
||||
|
||||
IntPtr chainPtr;
|
||||
chainPtr = Marshal.AllocHGlobal(
|
||||
Marshal.SizeOf<FAudio.FAudioEffectChain>()
|
||||
);
|
||||
/* Init 3D Audio */
|
||||
|
||||
FAudio.FAudioEffectChain* reverbChain = (FAudio.FAudioEffectChain*) chainPtr;
|
||||
reverbChain->EffectCount = 1;
|
||||
reverbChain->pEffectDescriptors = Marshal.AllocHGlobal(
|
||||
Marshal.SizeOf<FAudio.FAudioEffectDescriptor>()
|
||||
);
|
||||
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
|
||||
FAudio.F3DAudioInitialize(
|
||||
DeviceDetails.OutputFormat.dwChannelMask,
|
||||
SpeedOfSound,
|
||||
Handle3D
|
||||
);
|
||||
|
||||
FAudio.FAudioEffectDescriptor* reverbDescriptor =
|
||||
(FAudio.FAudioEffectDescriptor*) reverbChain->pEffectDescriptors;
|
||||
AudioTweenManager = new AudioTweenManager();
|
||||
VoicePool = new SourceVoicePool(this);
|
||||
|
||||
reverbDescriptor->InitialState = 1;
|
||||
reverbDescriptor->OutputChannels = (uint) (
|
||||
(DeviceDetails.OutputFormat.Format.nChannels == 6) ? 6 : 1
|
||||
);
|
||||
reverbDescriptor->pEffect = reverb;
|
||||
WakeSignal = new AutoResetEvent(true);
|
||||
|
||||
FAudio.FAudio_CreateSubmixVoice(
|
||||
Handle,
|
||||
out reverbVoice,
|
||||
1, /* omnidirectional reverb */
|
||||
DeviceDetails.OutputFormat.Format.nSamplesPerSec,
|
||||
0,
|
||||
0,
|
||||
IntPtr.Zero,
|
||||
chainPtr
|
||||
);
|
||||
FAudio.FAPOBase_Release(reverb);
|
||||
Thread = new Thread(ThreadMain);
|
||||
Thread.IsBackground = true;
|
||||
Thread.Start();
|
||||
|
||||
Marshal.FreeHGlobal(reverbChain->pEffectDescriptors);
|
||||
Marshal.FreeHGlobal(chainPtr);
|
||||
Running = true;
|
||||
|
||||
ReverbVoice = reverbVoice;
|
||||
TickStopwatch.Start();
|
||||
previousTickTime = 0;
|
||||
}
|
||||
|
||||
/* Init reverb params */
|
||||
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
|
||||
private void ThreadMain()
|
||||
{
|
||||
while (Running)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
ThreadMainTick();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
IntPtr reverbParamsPtr = Marshal.AllocHGlobal(
|
||||
Marshal.SizeOf<FAudio.FAudioFXReverbParameters>()
|
||||
);
|
||||
WakeSignal.WaitOne(UpdateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
FAudio.FAudioFXReverbParameters* reverbParams = (FAudio.FAudioFXReverbParameters*) reverbParamsPtr;
|
||||
reverbParams->WetDryMix = 100.0f;
|
||||
reverbParams->ReflectionsDelay = 7;
|
||||
reverbParams->ReverbDelay = 11;
|
||||
reverbParams->RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY;
|
||||
reverbParams->PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
|
||||
reverbParams->PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION;
|
||||
reverbParams->PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
|
||||
reverbParams->PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX;
|
||||
reverbParams->EarlyDiffusion = 15;
|
||||
reverbParams->LateDiffusion = 15;
|
||||
reverbParams->LowEQGain = 8;
|
||||
reverbParams->LowEQCutoff = 4;
|
||||
reverbParams->HighEQGain = 8;
|
||||
reverbParams->HighEQCutoff = 6;
|
||||
reverbParams->RoomFilterFreq = 5000f;
|
||||
reverbParams->RoomFilterMain = -10f;
|
||||
reverbParams->RoomFilterHF = -1f;
|
||||
reverbParams->ReflectionsGain = -26.0200005f;
|
||||
reverbParams->ReverbGain = 10.0f;
|
||||
reverbParams->DecayTime = 1.49000001f;
|
||||
reverbParams->Density = 100.0f;
|
||||
reverbParams->RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE;
|
||||
FAudio.FAudioVoice_SetEffectParameters(
|
||||
ReverbVoice,
|
||||
0,
|
||||
reverbParamsPtr,
|
||||
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
|
||||
0
|
||||
);
|
||||
Marshal.FreeHGlobal(reverbParamsPtr);
|
||||
private void ThreadMainTick()
|
||||
{
|
||||
long tickDelta = TickStopwatch.Elapsed.Ticks - previousTickTime;
|
||||
previousTickTime = TickStopwatch.Elapsed.Ticks;
|
||||
float elapsedSeconds = (float) tickDelta / System.TimeSpan.TicksPerSecond;
|
||||
|
||||
/* Init reverb sends */
|
||||
AudioTweenManager.Update(elapsedSeconds);
|
||||
|
||||
ReverbSends = new FAudio.FAudioVoiceSends
|
||||
{
|
||||
SendCount = 2,
|
||||
pSends = Marshal.AllocHGlobal(
|
||||
2 * Marshal.SizeOf<FAudio.FAudioSendDescriptor>()
|
||||
)
|
||||
};
|
||||
FAudio.FAudioSendDescriptor* sendDesc = (FAudio.FAudioSendDescriptor*) ReverbSends.pSends;
|
||||
sendDesc[0].Flags = 0;
|
||||
sendDesc[0].pOutputVoice = MasteringVoice;
|
||||
sendDesc[1].Flags = 0;
|
||||
sendDesc[1].pOutputVoice = ReverbVoice;
|
||||
}
|
||||
foreach (var voice in updatingSourceVoices)
|
||||
{
|
||||
voice.Update();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
for (var i = streamingSounds.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var weakReference = streamingSounds[i];
|
||||
if (weakReference.TryGetTarget(out var streamingSound))
|
||||
{
|
||||
streamingSound.Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
streamingSounds.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var voice in VoicesToReturn)
|
||||
{
|
||||
if (voice is UpdatingSourceVoice updatingSourceVoice)
|
||||
{
|
||||
updatingSourceVoices.Remove(updatingSourceVoice);
|
||||
}
|
||||
|
||||
internal void AddDynamicSoundInstance(StreamingSound instance)
|
||||
{
|
||||
streamingSounds.Add(new WeakReference<StreamingSound>(instance));
|
||||
}
|
||||
voice.Reset();
|
||||
VoicePool.Return(voice);
|
||||
}
|
||||
|
||||
internal void AddResourceReference(WeakReference<AudioResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Add(resourceReference);
|
||||
}
|
||||
}
|
||||
VoicesToReturn.Clear();
|
||||
}
|
||||
|
||||
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Remove(resourceReference);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Triggers all pending operations with the given syncGroup value.
|
||||
/// </summary>
|
||||
public void TriggerSyncGroup(uint syncGroup)
|
||||
{
|
||||
FAudio.FAudio_CommitChanges(Handle, syncGroup);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
foreach (var weakReference in streamingSounds)
|
||||
{
|
||||
if (weakReference.TryGetTarget(out var streamingSound))
|
||||
{
|
||||
streamingSound.Dispose();
|
||||
}
|
||||
}
|
||||
streamingSounds.Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// Obtains an appropriate source voice from the voice pool.
|
||||
/// </summary>
|
||||
/// <param name="format">The format that the voice must match.</param>
|
||||
/// <returns>A source voice with the given format.</returns>
|
||||
public T Obtain<T>(Format format) where T : SourceVoice, IPoolable<T>
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
var voice = VoicePool.Obtain<T>(format);
|
||||
|
||||
FAudio.FAudio_Release(Handle);
|
||||
if (voice is UpdatingSourceVoice updatingSourceVoice)
|
||||
{
|
||||
updatingSourceVoices.Add(updatingSourceVoice);
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
return voice;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
~AudioDevice()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the source voice to the voice pool.
|
||||
/// </summary>
|
||||
/// <param name="voice"></param>
|
||||
internal void Return(SourceVoice voice)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
VoicesToReturn.Add(voice);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
internal void CreateTween(
|
||||
Voice voice,
|
||||
AudioTweenProperty property,
|
||||
System.Func<float, float> easingFunction,
|
||||
float start,
|
||||
float end,
|
||||
float duration,
|
||||
float delayTime
|
||||
) {
|
||||
lock (StateLock)
|
||||
{
|
||||
AudioTweenManager.CreateTween(
|
||||
voice,
|
||||
property,
|
||||
easingFunction,
|
||||
start,
|
||||
end,
|
||||
duration,
|
||||
delayTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearTweens(
|
||||
Voice voice,
|
||||
AudioTweenProperty property
|
||||
) {
|
||||
lock (StateLock)
|
||||
{
|
||||
AudioTweenManager.ClearTweens(voice, property);
|
||||
}
|
||||
}
|
||||
|
||||
internal void WakeThread()
|
||||
{
|
||||
WakeSignal.Set();
|
||||
}
|
||||
|
||||
internal void AddResourceReference(GCHandle resourceReference)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
resourceHandles.Add(resourceReference);
|
||||
|
||||
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
||||
{
|
||||
updatingSourceVoices.Add(updatableVoice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveResourceReference(GCHandle resourceReference)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
resourceHandles.Remove(resourceReference);
|
||||
|
||||
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
|
||||
{
|
||||
updatingSourceVoices.Remove(updatableVoice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Running = false;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Thread.Join();
|
||||
|
||||
// dispose all source voices first
|
||||
foreach (var handle in resourceHandles)
|
||||
{
|
||||
if (handle.Target is SourceVoice voice)
|
||||
{
|
||||
voice.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// dispose all submix voices except the faux mastering voice
|
||||
foreach (var handle in resourceHandles)
|
||||
{
|
||||
if (handle.Target is SubmixVoice voice && voice != fauxMasteringVoice)
|
||||
{
|
||||
voice.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// dispose the faux mastering voice
|
||||
fauxMasteringVoice.Dispose();
|
||||
|
||||
// dispose the true mastering voice
|
||||
FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
|
||||
|
||||
// destroy all other audio resources
|
||||
foreach (var handle in resourceHandles)
|
||||
{
|
||||
if (handle.Target is AudioResource resource)
|
||||
{
|
||||
resource.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
resourceHandles.Clear();
|
||||
}
|
||||
|
||||
FAudio.FAudio_Release(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~AudioDevice()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class AudioEmitter : AudioResource
|
||||
{
|
||||
internal FAudio.F3DAUDIO_EMITTER emitterData;
|
||||
/// <summary>
|
||||
/// An emitter for 3D spatial audio.
|
||||
/// </summary>
|
||||
public class AudioEmitter : AudioResource
|
||||
{
|
||||
internal FAudio.F3DAUDIO_EMITTER emitterData;
|
||||
|
||||
public float DopplerScale
|
||||
{
|
||||
|
@ -107,17 +110,17 @@ namespace MoonWorks.Audio
|
|||
GCHandleType.Pinned
|
||||
);
|
||||
|
||||
public AudioEmitter(AudioDevice device) : base(device)
|
||||
{
|
||||
emitterData = new FAudio.F3DAUDIO_EMITTER();
|
||||
public AudioEmitter(AudioDevice device) : base(device)
|
||||
{
|
||||
emitterData = new FAudio.F3DAUDIO_EMITTER();
|
||||
|
||||
DopplerScale = 1f;
|
||||
Forward = Vector3.Forward;
|
||||
Position = Vector3.Zero;
|
||||
Up = Vector3.Up;
|
||||
Velocity = Vector3.Zero;
|
||||
DopplerScale = 1f;
|
||||
Forward = Vector3.Forward;
|
||||
Position = Vector3.Zero;
|
||||
Up = Vector3.Up;
|
||||
Velocity = Vector3.Zero;
|
||||
|
||||
/* Unexposed variables, defaults based on XNA behavior */
|
||||
/* Unexposed variables, defaults based on XNA behavior */
|
||||
emitterData.pCone = IntPtr.Zero;
|
||||
emitterData.ChannelCount = 1;
|
||||
emitterData.ChannelRadius = 1.0f;
|
||||
|
@ -128,8 +131,6 @@ namespace MoonWorks.Audio
|
|||
emitterData.pLPFReverbCurve = IntPtr.Zero;
|
||||
emitterData.pReverbCurve = IntPtr.Zero;
|
||||
emitterData.CurveDistanceScaler = 1.0f;
|
||||
}
|
||||
|
||||
protected override void Destroy() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
using System;
|
||||
using MoonWorks.Math;
|
||||
using System;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class AudioListener : AudioResource
|
||||
{
|
||||
internal FAudio.F3DAUDIO_LISTENER listenerData;
|
||||
/// <summary>
|
||||
/// A listener for 3D spatial audio. Usually attached to a camera.
|
||||
/// </summary>
|
||||
public class AudioListener : AudioResource
|
||||
{
|
||||
internal FAudio.F3DAUDIO_LISTENER listenerData;
|
||||
|
||||
public Vector3 Forward
|
||||
{
|
||||
|
@ -80,18 +83,16 @@ namespace MoonWorks.Audio
|
|||
}
|
||||
}
|
||||
|
||||
public AudioListener(AudioDevice device) : base(device)
|
||||
{
|
||||
listenerData = new FAudio.F3DAUDIO_LISTENER();
|
||||
Forward = Vector3.Forward;
|
||||
Position = Vector3.Zero;
|
||||
Up = Vector3.Up;
|
||||
Velocity = Vector3.Zero;
|
||||
public AudioListener(AudioDevice device) : base(device)
|
||||
{
|
||||
listenerData = new FAudio.F3DAUDIO_LISTENER();
|
||||
Forward = Vector3.Forward;
|
||||
Position = Vector3.Zero;
|
||||
Up = Vector3.Up;
|
||||
Velocity = Vector3.Zero;
|
||||
|
||||
/* Unexposed variables, defaults based on XNA behavior */
|
||||
/* Unexposed variables, defaults based on XNA behavior */
|
||||
listenerData.pCone = IntPtr.Zero;
|
||||
}
|
||||
|
||||
protected override void Destroy() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,53 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class AudioResource : IDisposable
|
||||
{
|
||||
public AudioDevice Device { get; }
|
||||
public abstract class AudioResource : IDisposable
|
||||
{
|
||||
public AudioDevice Device { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private WeakReference<AudioResource> selfReference;
|
||||
private GCHandle SelfReference;
|
||||
|
||||
public AudioResource(AudioDevice device)
|
||||
{
|
||||
Device = device;
|
||||
protected AudioResource(AudioDevice device)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
selfReference = new WeakReference<AudioResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
}
|
||||
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
|
||||
Device.AddResourceReference(SelfReference);
|
||||
}
|
||||
|
||||
protected abstract void Destroy();
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Device.RemoveResourceReference(SelfReference);
|
||||
SelfReference.Free();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Destroy();
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (selfReference != null)
|
||||
{
|
||||
Device.RemoveResourceReference(selfReference);
|
||||
selfReference = null;
|
||||
}
|
||||
~AudioResource()
|
||||
{
|
||||
#if DEBUG
|
||||
// If you see this log message, you leaked an audio resource without disposing it!
|
||||
// We can't clean it up for you because this can cause catastrophic issues.
|
||||
// You should really fix this when it happens.
|
||||
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
|
||||
#endif
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~AudioResource()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
using System.Collections.Generic;
|
||||
using EasingFunction = System.Func<float, float>;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
internal enum AudioTweenProperty
|
||||
{
|
||||
Pan,
|
||||
Pitch,
|
||||
Volume,
|
||||
FilterFrequency,
|
||||
Reverb
|
||||
}
|
||||
|
||||
internal class AudioTween
|
||||
{
|
||||
public Voice Voice;
|
||||
public AudioTweenProperty Property;
|
||||
public EasingFunction EasingFunction;
|
||||
public float Time;
|
||||
public float StartValue;
|
||||
public float EndValue;
|
||||
public float DelayTime;
|
||||
public float Duration;
|
||||
}
|
||||
|
||||
internal class AudioTweenPool
|
||||
{
|
||||
private Queue<AudioTween> Tweens = new Queue<AudioTween>(16);
|
||||
|
||||
public AudioTweenPool()
|
||||
{
|
||||
for (int i = 0; i < 16; i += 1)
|
||||
{
|
||||
Tweens.Enqueue(new AudioTween());
|
||||
}
|
||||
}
|
||||
|
||||
public AudioTween Obtain()
|
||||
{
|
||||
if (Tweens.Count > 0)
|
||||
{
|
||||
var tween = Tweens.Dequeue();
|
||||
return tween;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AudioTween();
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(AudioTween tween)
|
||||
{
|
||||
tween.Voice = null;
|
||||
Tweens.Enqueue(tween);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
internal class AudioTweenManager
|
||||
{
|
||||
private AudioTweenPool AudioTweenPool = new AudioTweenPool();
|
||||
private readonly Dictionary<(Voice, AudioTweenProperty), AudioTween> AudioTweens = new Dictionary<(Voice, AudioTweenProperty), AudioTween>();
|
||||
private readonly List<AudioTween> DelayedAudioTweens = new List<AudioTween>();
|
||||
|
||||
public void Update(float elapsedSeconds)
|
||||
{
|
||||
for (var i = DelayedAudioTweens.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var audioTween = DelayedAudioTweens[i];
|
||||
var voice = audioTween.Voice;
|
||||
|
||||
audioTween.Time += elapsedSeconds;
|
||||
|
||||
if (audioTween.Time >= audioTween.DelayTime)
|
||||
{
|
||||
// set the tween start value to the current value of the property
|
||||
switch (audioTween.Property)
|
||||
{
|
||||
case AudioTweenProperty.Pan:
|
||||
audioTween.StartValue = voice.Pan;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Pitch:
|
||||
audioTween.StartValue = voice.Pitch;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Volume:
|
||||
audioTween.StartValue = voice.Volume;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.FilterFrequency:
|
||||
audioTween.StartValue = voice.FilterFrequency;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Reverb:
|
||||
audioTween.StartValue = voice.Reverb;
|
||||
break;
|
||||
}
|
||||
|
||||
audioTween.Time = 0;
|
||||
DelayedAudioTweens.RemoveAt(i);
|
||||
|
||||
AddTween(audioTween);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, audioTween) in AudioTweens)
|
||||
{
|
||||
bool finished = UpdateAudioTween(audioTween, elapsedSeconds);
|
||||
|
||||
if (finished)
|
||||
{
|
||||
AudioTweenPool.Free(audioTween);
|
||||
AudioTweens.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateTween(
|
||||
Voice voice,
|
||||
AudioTweenProperty property,
|
||||
System.Func<float, float> easingFunction,
|
||||
float start,
|
||||
float end,
|
||||
float duration,
|
||||
float delayTime
|
||||
) {
|
||||
var tween = AudioTweenPool.Obtain();
|
||||
tween.Voice = voice;
|
||||
tween.Property = property;
|
||||
tween.EasingFunction = easingFunction;
|
||||
tween.StartValue = start;
|
||||
tween.EndValue = end;
|
||||
tween.Duration = duration;
|
||||
tween.Time = 0;
|
||||
tween.DelayTime = delayTime;
|
||||
|
||||
if (delayTime == 0)
|
||||
{
|
||||
AddTween(tween);
|
||||
}
|
||||
else
|
||||
{
|
||||
DelayedAudioTweens.Add(tween);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearTweens(Voice voice, AudioTweenProperty property)
|
||||
{
|
||||
AudioTweens.Remove((voice, property));
|
||||
}
|
||||
|
||||
private void AddTween(
|
||||
AudioTween audioTween
|
||||
) {
|
||||
// if a tween with the same sound and property already exists, get rid of it
|
||||
if (AudioTweens.TryGetValue((audioTween.Voice, audioTween.Property), out var currentTween))
|
||||
{
|
||||
AudioTweenPool.Free(currentTween);
|
||||
}
|
||||
|
||||
AudioTweens[(audioTween.Voice, audioTween.Property)] = audioTween;
|
||||
}
|
||||
|
||||
private static bool UpdateAudioTween(AudioTween audioTween, float delta)
|
||||
{
|
||||
float value;
|
||||
audioTween.Time += delta;
|
||||
|
||||
var finished = audioTween.Time >= audioTween.Duration;
|
||||
if (finished)
|
||||
{
|
||||
value = audioTween.EndValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = MoonWorks.Math.Easing.Interp(
|
||||
audioTween.StartValue,
|
||||
audioTween.EndValue,
|
||||
audioTween.Time,
|
||||
audioTween.Duration,
|
||||
audioTween.EasingFunction
|
||||
);
|
||||
}
|
||||
|
||||
switch (audioTween.Property)
|
||||
{
|
||||
case AudioTweenProperty.Pan:
|
||||
audioTween.Voice.Pan = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Pitch:
|
||||
audioTween.Voice.Pitch = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Volume:
|
||||
audioTween.Voice.Volume = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.FilterFrequency:
|
||||
audioTween.Voice.FilterFrequency = value;
|
||||
break;
|
||||
|
||||
case AudioTweenProperty.Reverb:
|
||||
audioTween.Voice.Reverb = value;
|
||||
break;
|
||||
}
|
||||
|
||||
return finished;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
public enum FilterType
|
||||
{
|
||||
None,
|
||||
LowPass,
|
||||
BandPass,
|
||||
HighPass
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
public enum FormatTag : ushort
|
||||
{
|
||||
Unknown = 0,
|
||||
PCM = 1,
|
||||
MSADPCM = 2,
|
||||
IEEE_FLOAT = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the format of audio data. Usually specified in an audio file's header information.
|
||||
/// </summary>
|
||||
public record struct Format
|
||||
{
|
||||
public FormatTag Tag;
|
||||
public ushort Channels;
|
||||
public uint SampleRate;
|
||||
public ushort BitsPerSample;
|
||||
|
||||
internal FAudio.FAudioWaveFormatEx ToFAudioFormat()
|
||||
{
|
||||
var blockAlign = (ushort) ((BitsPerSample / 8) * Channels);
|
||||
|
||||
return new FAudio.FAudioWaveFormatEx
|
||||
{
|
||||
wFormatTag = (ushort) Tag,
|
||||
nChannels = Channels,
|
||||
nSamplesPerSec = SampleRate,
|
||||
wBitsPerSample = BitsPerSample,
|
||||
nBlockAlign = blockAlign,
|
||||
nAvgBytesPerSec = blockAlign * SampleRate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
public interface IPoolable<T>
|
||||
{
|
||||
static abstract T Create(AudioDevice device, Format format);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// PersistentVoice should be used when you need to maintain a long-term reference to a source voice.
|
||||
/// </summary>
|
||||
public class PersistentVoice : SourceVoice, IPoolable<PersistentVoice>
|
||||
{
|
||||
public PersistentVoice(AudioDevice device, Format format) : base(device, format)
|
||||
{
|
||||
}
|
||||
|
||||
public static PersistentVoice Create(AudioDevice device, Format format)
|
||||
{
|
||||
return new PersistentVoice(device, format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an AudioBuffer to the voice queue.
|
||||
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to submit to the voice.</param>
|
||||
/// <param name="loop">Whether the voice should loop this buffer.</param>
|
||||
public void Submit(AudioBuffer buffer, bool loop = false)
|
||||
{
|
||||
Submit(buffer.ToFAudioBuffer(loop));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this in conjunction with SourceVoice.SetReverbEffectChain to add reverb to a voice.
|
||||
/// </summary>
|
||||
public unsafe class ReverbEffect : SubmixVoice
|
||||
{
|
||||
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
|
||||
public static FAudio.FAudioFXReverbParameters DefaultParams = new FAudio.FAudioFXReverbParameters
|
||||
{
|
||||
WetDryMix = 100.0f,
|
||||
ReflectionsDelay = 7,
|
||||
ReverbDelay = 11,
|
||||
RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY,
|
||||
PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION,
|
||||
PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION,
|
||||
PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX,
|
||||
PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX,
|
||||
EarlyDiffusion = 15,
|
||||
LateDiffusion = 15,
|
||||
LowEQGain = 8,
|
||||
LowEQCutoff = 4,
|
||||
HighEQGain = 8,
|
||||
HighEQCutoff = 6,
|
||||
RoomFilterFreq = 5000f,
|
||||
RoomFilterMain = -10f,
|
||||
RoomFilterHF = -1f,
|
||||
ReflectionsGain = -26.0200005f,
|
||||
ReverbGain = 10.0f,
|
||||
DecayTime = 1.49000001f,
|
||||
Density = 100.0f,
|
||||
RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE
|
||||
};
|
||||
|
||||
public FAudio.FAudioFXReverbParameters Params { get; private set; }
|
||||
|
||||
public ReverbEffect(AudioDevice audioDevice, uint processingStage) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec, processingStage)
|
||||
{
|
||||
/* Init reverb */
|
||||
IntPtr reverb;
|
||||
FAudio.FAudioCreateReverb(out reverb, 0);
|
||||
|
||||
var chain = new FAudio.FAudioEffectChain();
|
||||
var descriptor = new FAudio.FAudioEffectDescriptor
|
||||
{
|
||||
InitialState = 1,
|
||||
OutputChannels = 1,
|
||||
pEffect = reverb
|
||||
};
|
||||
|
||||
chain.EffectCount = 1;
|
||||
chain.pEffectDescriptors = (nint) (&descriptor);
|
||||
|
||||
FAudio.FAudioVoice_SetEffectChain(
|
||||
Handle,
|
||||
ref chain
|
||||
);
|
||||
|
||||
FAudio.FAPOBase_Release(reverb);
|
||||
|
||||
SetParams(DefaultParams);
|
||||
}
|
||||
|
||||
public void SetParams(in FAudio.FAudioFXReverbParameters reverbParams)
|
||||
{
|
||||
Params = reverbParams;
|
||||
|
||||
fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
|
||||
{
|
||||
FAudio.FAudioVoice_SetEffectParameters(
|
||||
Handle,
|
||||
0,
|
||||
(nint) reverbParamsPtr,
|
||||
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class SoundInstance : AudioResource
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
internal FAudio.FAudioWaveFormatEx Format { get; }
|
||||
public bool Loop { get; }
|
||||
|
||||
protected FAudio.F3DAUDIO_DSP_SETTINGS dspSettings;
|
||||
|
||||
protected bool is3D;
|
||||
|
||||
public abstract SoundState State { get; protected set; }
|
||||
|
||||
private float _pan = 0;
|
||||
public float Pan
|
||||
{
|
||||
get => _pan;
|
||||
set
|
||||
{
|
||||
_pan = value;
|
||||
|
||||
if (_pan < -1f)
|
||||
{
|
||||
_pan = -1f;
|
||||
}
|
||||
if (_pan > 1f)
|
||||
{
|
||||
_pan = 1f;
|
||||
}
|
||||
|
||||
if (is3D) { return; }
|
||||
|
||||
SetPanMatrixCoefficients();
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
Device.MasteringVoice,
|
||||
dspSettings.SrcChannelCount,
|
||||
dspSettings.DstChannelCount,
|
||||
dspSettings.pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private float _pitch = 1;
|
||||
public float Pitch
|
||||
{
|
||||
get => _pitch;
|
||||
set
|
||||
{
|
||||
_pitch = MathHelper.Clamp(value, -1f, 1f);
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
|
||||
private float _volume = 1;
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
_volume = value;
|
||||
FAudio.FAudioVoice_SetVolume(Handle, _volume, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private float _reverb;
|
||||
public unsafe float Reverb
|
||||
{
|
||||
get => _reverb;
|
||||
set
|
||||
{
|
||||
_reverb = value;
|
||||
|
||||
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
|
||||
outputMatrix[0] = _reverb;
|
||||
if (dspSettings.SrcChannelCount == 2)
|
||||
{
|
||||
outputMatrix[1] = _reverb;
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
Device.ReverbVoice,
|
||||
dspSettings.SrcChannelCount,
|
||||
1,
|
||||
dspSettings.pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private float _lowPassFilter;
|
||||
public float LowPassFilter
|
||||
{
|
||||
get => _lowPassFilter;
|
||||
set
|
||||
{
|
||||
_lowPassFilter = value;
|
||||
|
||||
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
|
||||
{
|
||||
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
|
||||
Frequency = _lowPassFilter,
|
||||
OneOverQ = 1f
|
||||
};
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
Handle,
|
||||
ref p,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private float _highPassFilter;
|
||||
public float HighPassFilter
|
||||
{
|
||||
get => _highPassFilter;
|
||||
set
|
||||
{
|
||||
_highPassFilter = value;
|
||||
|
||||
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
|
||||
{
|
||||
Type = FAudio.FAudioFilterType.FAudioHighPassFilter,
|
||||
Frequency = _highPassFilter,
|
||||
OneOverQ = 1f
|
||||
};
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
Handle,
|
||||
ref p,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private float _bandPassFilter;
|
||||
public float BandPassFilter
|
||||
{
|
||||
get => _bandPassFilter;
|
||||
set
|
||||
{
|
||||
_bandPassFilter = value;
|
||||
|
||||
FAudio.FAudioFilterParameters p = new FAudio.FAudioFilterParameters
|
||||
{
|
||||
Type = FAudio.FAudioFilterType.FAudioBandPassFilter,
|
||||
Frequency = _bandPassFilter,
|
||||
OneOverQ = 1f
|
||||
};
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
Handle,
|
||||
ref p,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public SoundInstance(
|
||||
AudioDevice device,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) : base(device)
|
||||
{
|
||||
var blockAlign = (ushort)(4 * channels);
|
||||
var format = new FAudio.FAudioWaveFormatEx
|
||||
{
|
||||
wFormatTag = 3,
|
||||
wBitsPerSample = 32,
|
||||
nChannels = channels,
|
||||
nBlockAlign = blockAlign,
|
||||
nSamplesPerSec = samplesPerSecond,
|
||||
nAvgBytesPerSec = blockAlign * samplesPerSecond
|
||||
};
|
||||
|
||||
Format = format;
|
||||
|
||||
FAudio.FAudio_CreateSourceVoice(
|
||||
Device.Handle,
|
||||
out var handle,
|
||||
ref format,
|
||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
Logger.LogError("SoundInstance failed to initialize!");
|
||||
return;
|
||||
}
|
||||
|
||||
Handle = handle;
|
||||
this.is3D = is3D;
|
||||
InitDSPSettings(Format.nChannels);
|
||||
|
||||
FAudio.FAudioVoice_SetOutputVoices(
|
||||
handle,
|
||||
ref Device.ReverbSends
|
||||
);
|
||||
|
||||
Loop = loop;
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
public void Apply3D(AudioListener listener, AudioEmitter emitter)
|
||||
{
|
||||
is3D = true;
|
||||
|
||||
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
|
||||
emitter.emitterData.ChannelCount = dspSettings.SrcChannelCount;
|
||||
|
||||
FAudio.F3DAudioCalculate(
|
||||
Device.Handle3D,
|
||||
ref listener.listenerData,
|
||||
ref emitter.emitterData,
|
||||
FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
|
||||
ref dspSettings
|
||||
);
|
||||
|
||||
UpdatePitch();
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
Device.MasteringVoice,
|
||||
dspSettings.SrcChannelCount,
|
||||
dspSettings.DstChannelCount,
|
||||
dspSettings.pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public abstract void Play();
|
||||
public abstract void Pause();
|
||||
public abstract void Stop(bool immediate);
|
||||
|
||||
private void InitDSPSettings(uint srcChannels)
|
||||
{
|
||||
dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS();
|
||||
dspSettings.DopplerFactor = 1f;
|
||||
dspSettings.SrcChannelCount = srcChannels;
|
||||
dspSettings.DstChannelCount = Device.DeviceDetails.OutputFormat.Format.nChannels;
|
||||
|
||||
int memsize = (
|
||||
4 *
|
||||
(int) dspSettings.SrcChannelCount *
|
||||
(int) dspSettings.DstChannelCount
|
||||
);
|
||||
|
||||
dspSettings.pMatrixCoefficients = Marshal.AllocHGlobal(memsize);
|
||||
unsafe
|
||||
{
|
||||
byte* memPtr = (byte*) dspSettings.pMatrixCoefficients;
|
||||
for (int i = 0; i < memsize; i += 1)
|
||||
{
|
||||
memPtr[i] = 0;
|
||||
}
|
||||
}
|
||||
SetPanMatrixCoefficients();
|
||||
}
|
||||
|
||||
private void UpdatePitch()
|
||||
{
|
||||
float doppler;
|
||||
float dopplerScale = Device.DopplerScale;
|
||||
if (!is3D || dopplerScale == 0.0f)
|
||||
{
|
||||
doppler = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
doppler = dspSettings.DopplerFactor * dopplerScale;
|
||||
}
|
||||
|
||||
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
||||
Handle,
|
||||
(float) System.Math.Pow(2.0, _pitch) * doppler,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
||||
private unsafe void SetPanMatrixCoefficients()
|
||||
{
|
||||
/* Two major things to notice:
|
||||
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
|
||||
* 2. Stereo panning is WAY more complicated than you think.
|
||||
* The main thing is that hard panning does NOT eliminate an
|
||||
* entire channel; the two channels are blended on each side.
|
||||
* -flibit
|
||||
*/
|
||||
float* outputMatrix = (float*) dspSettings.pMatrixCoefficients;
|
||||
if (dspSettings.SrcChannelCount == 1)
|
||||
{
|
||||
if (dspSettings.DstChannelCount == 1)
|
||||
{
|
||||
outputMatrix[0] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputMatrix[0] = (_pan > 0.0f) ? (1.0f - _pan) : 1.0f;
|
||||
outputMatrix[1] = (_pan < 0.0f) ? (1.0f + _pan) : 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dspSettings.DstChannelCount == 1)
|
||||
{
|
||||
outputMatrix[0] = 1.0f;
|
||||
outputMatrix[1] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_pan <= 0.0f)
|
||||
{
|
||||
// Left speaker blends left/right channels
|
||||
outputMatrix[0] = 0.5f * _pan + 1.0f;
|
||||
outputMatrix[1] = 0.5f * -_pan;
|
||||
// Right speaker gets less of the right channel
|
||||
outputMatrix[2] = 0.0f;
|
||||
outputMatrix[3] = _pan + 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left speaker gets less of the left channel
|
||||
outputMatrix[0] = -_pan + 1.0f;
|
||||
outputMatrix[1] = 0.0f;
|
||||
// Right speaker blends right/left channels
|
||||
outputMatrix[2] = 0.5f * _pan;
|
||||
outputMatrix[3] = 0.5f * -_pan + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
Stop(true);
|
||||
|
||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||
Marshal.FreeHGlobal(dspSettings.pMatrixCoefficients);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically.
|
||||
/// </summary>
|
||||
public class SoundSequence : UpdatingSourceVoice, IPoolable<SoundSequence>
|
||||
{
|
||||
public int NeedSoundThreshold = 0;
|
||||
public delegate void OnSoundNeededFunc();
|
||||
public OnSoundNeededFunc OnSoundNeeded;
|
||||
|
||||
public SoundSequence(AudioDevice device, Format format) : base(device, format)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SoundSequence(AudioDevice device, AudioBuffer templateSound) : base(device, templateSound.Format)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static SoundSequence Create(AudioDevice device, Format format)
|
||||
{
|
||||
return new SoundSequence(device, format);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (State != SoundState.Playing) { return; }
|
||||
|
||||
if (NeedSoundThreshold > 0)
|
||||
{
|
||||
var buffersNeeded = NeedSoundThreshold - (int) BuffersQueued;
|
||||
|
||||
for (int i = 0; i < buffersNeeded; i += 1)
|
||||
{
|
||||
if (OnSoundNeeded != null)
|
||||
{
|
||||
OnSoundNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnqueueSound(AudioBuffer buffer)
|
||||
{
|
||||
#if DEBUG
|
||||
if (!(buffer.Format == Format))
|
||||
{
|
||||
Logger.LogWarn("Sound sequence audio format mismatch!");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
lock (StateLock)
|
||||
{
|
||||
Submit(buffer.ToFAudioBuffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
namespace MoonWorks.Audio
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public enum SoundState
|
||||
{
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped
|
||||
}
|
||||
public enum SoundState
|
||||
{
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
using System;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Emits audio from submitted audio buffers.
|
||||
/// </summary>
|
||||
public abstract class SourceVoice : Voice
|
||||
{
|
||||
private Format format;
|
||||
public Format Format => format;
|
||||
|
||||
protected bool PlaybackInitiated;
|
||||
|
||||
/// <summary>
|
||||
/// The number of buffers queued in the voice.
|
||||
/// This includes the currently playing voice!
|
||||
/// </summary>
|
||||
public uint BuffersQueued
|
||||
{
|
||||
get
|
||||
{
|
||||
FAudio.FAudioSourceVoice_GetState(
|
||||
Handle,
|
||||
out var state,
|
||||
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
||||
);
|
||||
|
||||
return state.BuffersQueued;
|
||||
}
|
||||
}
|
||||
|
||||
private SoundState state = SoundState.Stopped;
|
||||
public SoundState State
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BuffersQueued == 0)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected object StateLock = new object();
|
||||
|
||||
public SourceVoice(
|
||||
AudioDevice device,
|
||||
Format format
|
||||
) : base(device, format.Channels, device.DeviceDetails.OutputFormat.Format.nChannels)
|
||||
{
|
||||
this.format = format;
|
||||
var fAudioFormat = format.ToFAudioFormat();
|
||||
|
||||
FAudio.FAudio_CreateSourceVoice(
|
||||
device.Handle,
|
||||
out handle,
|
||||
ref fAudioFormat,
|
||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero, // default sends to mastering voice!
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
SetOutputVoice(device.MasteringVoice);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts consumption and processing of audio by the voice.
|
||||
/// Delivers the result to any connected submix or mastering voice.
|
||||
/// </summary>
|
||||
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
||||
public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
|
||||
|
||||
State = SoundState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses playback.
|
||||
/// All source buffers that are queued on the voice and the current cursor position are preserved.
|
||||
/// </summary>
|
||||
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
||||
public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
|
||||
|
||||
State = SoundState.Paused;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops looping the voice when it reaches the end of the current loop region.
|
||||
/// If the cursor for the voice is not in a loop region, ExitLoop does nothing.
|
||||
/// </summary>
|
||||
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
||||
public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops playback and removes all pending audio buffers from the voice queue.
|
||||
/// </summary>
|
||||
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
|
||||
public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an AudioBuffer to the voice queue.
|
||||
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to submit to the voice.</param>
|
||||
public void Submit(AudioBuffer buffer)
|
||||
{
|
||||
Submit(buffer.ToFAudioBuffer());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates positional sound. This must be called continuously to update positional sound.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <param name="emitter"></param>
|
||||
public unsafe void Apply3D(AudioListener listener, AudioEmitter emitter)
|
||||
{
|
||||
Is3D = true;
|
||||
|
||||
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
|
||||
emitter.emitterData.ChannelCount = SourceChannelCount;
|
||||
|
||||
var dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS
|
||||
{
|
||||
DopplerFactor = DopplerFactor,
|
||||
SrcChannelCount = SourceChannelCount,
|
||||
DstChannelCount = DestinationChannelCount,
|
||||
pMatrixCoefficients = (nint) pMatrixCoefficients
|
||||
};
|
||||
|
||||
FAudio.F3DAudioCalculate(
|
||||
Device.Handle3D,
|
||||
ref listener.listenerData,
|
||||
ref emitter.emitterData,
|
||||
FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
|
||||
ref dspSettings
|
||||
);
|
||||
|
||||
UpdatePitch();
|
||||
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
OutputVoice.Handle,
|
||||
SourceChannelCount,
|
||||
DestinationChannelCount,
|
||||
(nint) pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that this source voice can be returned to the voice pool.
|
||||
/// Holding on to the reference after calling this will cause problems!
|
||||
/// </summary>
|
||||
public void Return()
|
||||
{
|
||||
Stop();
|
||||
Device.Return(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an FAudio buffer to the voice queue.
|
||||
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to submit to the voice.</param>
|
||||
protected void Submit(FAudio.FAudioBuffer buffer)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||
Handle,
|
||||
ref buffer,
|
||||
IntPtr.Zero
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Stop();
|
||||
PlaybackInitiated = false;
|
||||
base.Reset();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
internal class SourceVoicePool
|
||||
{
|
||||
private AudioDevice Device;
|
||||
|
||||
Dictionary<(System.Type, Format), Queue<SourceVoice>> VoiceLists = new Dictionary<(System.Type, Format), Queue<SourceVoice>>();
|
||||
|
||||
public SourceVoicePool(AudioDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
|
||||
public T Obtain<T>(Format format) where T : SourceVoice, IPoolable<T>
|
||||
{
|
||||
if (!VoiceLists.ContainsKey((typeof(T), format)))
|
||||
{
|
||||
VoiceLists.Add((typeof(T), format), new Queue<SourceVoice>());
|
||||
}
|
||||
|
||||
var list = VoiceLists[(typeof(T), format)];
|
||||
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Enqueue(T.Create(Device, format));
|
||||
}
|
||||
|
||||
return (T) list.Dequeue();
|
||||
}
|
||||
|
||||
public void Return(SourceVoice voice)
|
||||
{
|
||||
var list = VoiceLists[(voice.GetType(), voice.Format)];
|
||||
list.Enqueue(voice);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class StaticSound : AudioResource
|
||||
{
|
||||
internal FAudio.FAudioBuffer Handle;
|
||||
public ushort Channels { get; }
|
||||
public uint SamplesPerSecond { get; }
|
||||
|
||||
public uint LoopStart { get; set; } = 0;
|
||||
public uint LoopLength { get; set; } = 0;
|
||||
|
||||
public static StaticSound LoadOgg(AudioDevice device, string filePath)
|
||||
{
|
||||
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
|
||||
if (error != 0)
|
||||
{
|
||||
throw new AudioLoadException("Error loading file!");
|
||||
}
|
||||
var info = FAudio.stb_vorbis_get_info(filePointer);
|
||||
var bufferSize = FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels;
|
||||
var buffer = new float[bufferSize];
|
||||
|
||||
FAudio.stb_vorbis_get_samples_float_interleaved(
|
||||
filePointer,
|
||||
info.channels,
|
||||
buffer,
|
||||
(int) bufferSize
|
||||
);
|
||||
|
||||
FAudio.stb_vorbis_close(filePointer);
|
||||
|
||||
return new StaticSound(
|
||||
device,
|
||||
(ushort) info.channels,
|
||||
info.sample_rate,
|
||||
buffer,
|
||||
0,
|
||||
(uint) buffer.Length
|
||||
);
|
||||
}
|
||||
|
||||
public StaticSound(
|
||||
AudioDevice device,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
float[] buffer,
|
||||
uint bufferOffset, /* in floats */
|
||||
uint bufferLength /* in floats */
|
||||
) : base(device)
|
||||
{
|
||||
Channels = channels;
|
||||
SamplesPerSecond = samplesPerSecond;
|
||||
|
||||
var bufferLengthInBytes = (int) (bufferLength * sizeof(float));
|
||||
Handle = new FAudio.FAudioBuffer();
|
||||
Handle.Flags = FAudio.FAUDIO_END_OF_STREAM;
|
||||
Handle.pContext = IntPtr.Zero;
|
||||
Handle.AudioBytes = (uint) bufferLengthInBytes;
|
||||
Handle.pAudioData = Marshal.AllocHGlobal(bufferLengthInBytes);
|
||||
Marshal.Copy(buffer, (int) bufferOffset, Handle.pAudioData, (int) bufferLength);
|
||||
Handle.PlayBegin = 0;
|
||||
Handle.PlayLength = 0;
|
||||
|
||||
LoopStart = 0;
|
||||
LoopLength = 0;
|
||||
}
|
||||
|
||||
public StaticSoundInstance CreateInstance(bool loop = false)
|
||||
{
|
||||
return new StaticSoundInstance(Device, this, false, loop);
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
Marshal.FreeHGlobal(Handle.pAudioData);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class StaticSoundInstance : SoundInstance
|
||||
{
|
||||
public StaticSound Parent { get; }
|
||||
|
||||
private SoundState _state = SoundState.Stopped;
|
||||
public override SoundState State
|
||||
{
|
||||
get
|
||||
{
|
||||
FAudio.FAudioSourceVoice_GetState(
|
||||
Handle,
|
||||
out var state,
|
||||
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
||||
);
|
||||
if (state.BuffersQueued == 0)
|
||||
{
|
||||
Stop(true);
|
||||
}
|
||||
|
||||
return _state;
|
||||
}
|
||||
|
||||
protected set
|
||||
{
|
||||
_state = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal StaticSoundInstance(
|
||||
AudioDevice device,
|
||||
StaticSound parent,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) : base(device, parent.Channels, parent.SamplesPerSecond, is3D, loop)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Loop)
|
||||
{
|
||||
Parent.Handle.LoopCount = 255;
|
||||
Parent.Handle.LoopBegin = Parent.LoopStart;
|
||||
Parent.Handle.LoopLength = Parent.LoopLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent.Handle.LoopCount = 0;
|
||||
Parent.Handle.LoopBegin = 0;
|
||||
Parent.Handle.LoopLength = 0;
|
||||
}
|
||||
|
||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||
Handle,
|
||||
ref Parent.Handle,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
|
||||
State = SoundState.Playing;
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
if (State == SoundState.Paused)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
State = SoundState.Paused;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop(bool immediate = true)
|
||||
{
|
||||
if (immediate)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
else
|
||||
{
|
||||
FAudio.FAudioSourceVoice_ExitLoop(Handle, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// For streaming long playback.
|
||||
/// Can be extended to support custom decoders.
|
||||
/// </summary>
|
||||
public abstract class StreamingSound : SoundInstance
|
||||
{
|
||||
private readonly List<IntPtr> queuedBuffers = new List<IntPtr>();
|
||||
private readonly List<uint> queuedSizes = new List<uint>();
|
||||
private const int MINIMUM_BUFFER_CHECK = 3;
|
||||
|
||||
public int PendingBufferCount => queuedBuffers.Count;
|
||||
|
||||
public StreamingSound(
|
||||
AudioDevice device,
|
||||
ushort channels,
|
||||
uint samplesPerSecond,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) : base(device, channels, samplesPerSecond, is3D, loop) { }
|
||||
|
||||
public override void Play()
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
State = SoundState.Playing;
|
||||
Update();
|
||||
FAudio.FAudioSourceVoice_Start(Handle, 0, 0);
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
if (State == SoundState.Playing)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
State = SoundState.Paused;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop(bool immediate = true)
|
||||
{
|
||||
if (immediate)
|
||||
{
|
||||
FAudio.FAudioSourceVoice_Stop(Handle, 0, 0);
|
||||
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
|
||||
ClearBuffers();
|
||||
}
|
||||
|
||||
State = SoundState.Stopped;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
if (State != SoundState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FAudio.FAudioSourceVoice_GetState(
|
||||
Handle,
|
||||
out var state,
|
||||
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
|
||||
);
|
||||
|
||||
while (PendingBufferCount > state.BuffersQueued)
|
||||
lock (queuedBuffers)
|
||||
{
|
||||
Marshal.FreeHGlobal(queuedBuffers[0]);
|
||||
queuedBuffers.RemoveAt(0);
|
||||
}
|
||||
|
||||
QueueBuffers();
|
||||
}
|
||||
|
||||
protected void QueueBuffers()
|
||||
{
|
||||
for (
|
||||
int i = MINIMUM_BUFFER_CHECK - PendingBufferCount;
|
||||
i > 0;
|
||||
i -= 1
|
||||
)
|
||||
{
|
||||
AddBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
protected void ClearBuffers()
|
||||
{
|
||||
lock (queuedBuffers)
|
||||
{
|
||||
foreach (IntPtr buf in queuedBuffers)
|
||||
{
|
||||
Marshal.FreeHGlobal(buf);
|
||||
}
|
||||
queuedBuffers.Clear();
|
||||
queuedSizes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddBuffer()
|
||||
{
|
||||
AddBuffer(
|
||||
out var buffer,
|
||||
out var bufferOffset,
|
||||
out var bufferLength,
|
||||
out var reachedEnd
|
||||
);
|
||||
|
||||
var lengthInBytes = bufferLength * sizeof(float);
|
||||
|
||||
IntPtr next = Marshal.AllocHGlobal((int) lengthInBytes);
|
||||
Marshal.Copy(buffer, (int) bufferOffset, next, (int) bufferLength);
|
||||
|
||||
lock (queuedBuffers)
|
||||
{
|
||||
queuedBuffers.Add(next);
|
||||
if (State != SoundState.Stopped)
|
||||
{
|
||||
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
|
||||
{
|
||||
AudioBytes = lengthInBytes,
|
||||
pAudioData = next,
|
||||
PlayLength = (
|
||||
lengthInBytes /
|
||||
Format.nChannels /
|
||||
(uint)(Format.wBitsPerSample / 8)
|
||||
)
|
||||
};
|
||||
|
||||
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
|
||||
Handle,
|
||||
ref buf,
|
||||
IntPtr.Zero
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
queuedSizes.Add(lengthInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/* We have reached the end of the file, what do we do? */
|
||||
if (reachedEnd)
|
||||
{
|
||||
if (Loop)
|
||||
{
|
||||
SeekStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void AddBuffer(
|
||||
out float[] buffer,
|
||||
out uint bufferOffset, /* in floats */
|
||||
out uint bufferLength, /* in floats */
|
||||
out bool reachedEnd
|
||||
);
|
||||
|
||||
protected abstract void SeekStart();
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
Stop(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
public class StreamingSoundOgg : StreamingSound
|
||||
{
|
||||
// FIXME: what should this value be?
|
||||
public const int BUFFER_SIZE = 1024 * 128;
|
||||
|
||||
internal IntPtr FileHandle { get; }
|
||||
internal FAudio.stb_vorbis_info Info { get; }
|
||||
|
||||
private readonly float[] buffer;
|
||||
|
||||
public override SoundState State { get; protected set; }
|
||||
|
||||
public static StreamingSoundOgg Load(
|
||||
AudioDevice device,
|
||||
string filePath,
|
||||
bool is3D = false,
|
||||
bool loop = false
|
||||
) {
|
||||
var fileHandle = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
|
||||
if (error != 0)
|
||||
{
|
||||
Logger.LogError("Error opening OGG file!");
|
||||
throw new AudioLoadException("Error opening OGG file!");
|
||||
}
|
||||
|
||||
var info = FAudio.stb_vorbis_get_info(fileHandle);
|
||||
|
||||
return new StreamingSoundOgg(
|
||||
device,
|
||||
fileHandle,
|
||||
info,
|
||||
is3D,
|
||||
loop
|
||||
);
|
||||
}
|
||||
|
||||
internal StreamingSoundOgg(
|
||||
AudioDevice device,
|
||||
IntPtr fileHandle,
|
||||
FAudio.stb_vorbis_info info,
|
||||
bool is3D,
|
||||
bool loop
|
||||
) : base(device, (ushort) info.channels, info.sample_rate, is3D, loop)
|
||||
{
|
||||
FileHandle = fileHandle;
|
||||
Info = info;
|
||||
buffer = new float[BUFFER_SIZE];
|
||||
|
||||
device.AddDynamicSoundInstance(this);
|
||||
}
|
||||
|
||||
protected override void AddBuffer(
|
||||
out float[] buffer,
|
||||
out uint bufferOffset,
|
||||
out uint bufferLength,
|
||||
out bool reachedEnd
|
||||
) {
|
||||
buffer = this.buffer;
|
||||
|
||||
/* NOTE: this function returns samples per channel, not total samples */
|
||||
var samples = FAudio.stb_vorbis_get_samples_float_interleaved(
|
||||
FileHandle,
|
||||
Info.channels,
|
||||
buffer,
|
||||
buffer.Length
|
||||
);
|
||||
|
||||
var sampleCount = samples * Info.channels;
|
||||
bufferOffset = 0;
|
||||
bufferLength = (uint) sampleCount;
|
||||
reachedEnd = sampleCount < buffer.Length;
|
||||
}
|
||||
|
||||
protected override void SeekStart()
|
||||
{
|
||||
FAudio.stb_vorbis_seek_start(FileHandle);
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
FAudio.stb_vorbis_close(FileHandle);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Use in conjunction with an AudioDataStreamable object to play back streaming audio data.
|
||||
/// </summary>
|
||||
public class StreamingVoice : UpdatingSourceVoice, IPoolable<StreamingVoice>
|
||||
{
|
||||
private const int BUFFER_COUNT = 3;
|
||||
private readonly IntPtr[] buffers;
|
||||
private int nextBufferIndex = 0;
|
||||
private uint BufferSize;
|
||||
|
||||
public bool Loop { get; set; }
|
||||
|
||||
public AudioDataStreamable AudioData { get; protected set; }
|
||||
|
||||
public unsafe StreamingVoice(AudioDevice device, Format format) : base(device, format)
|
||||
{
|
||||
buffers = new IntPtr[BUFFER_COUNT];
|
||||
}
|
||||
|
||||
public static StreamingVoice Create(AudioDevice device, Format format)
|
||||
{
|
||||
return new StreamingVoice(device, format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads and prepares an AudioDataStreamable for streaming playback.
|
||||
/// This automatically calls Load on the given AudioDataStreamable.
|
||||
/// </summary>
|
||||
public void Load(AudioDataStreamable data)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (AudioData != null)
|
||||
{
|
||||
AudioData.Unload();
|
||||
}
|
||||
|
||||
data.Load();
|
||||
AudioData = data;
|
||||
|
||||
InitializeBuffers();
|
||||
QueueBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads AudioDataStreamable from this voice.
|
||||
/// This automatically calls Unload on the given AudioDataStreamable.
|
||||
/// </summary>
|
||||
public void Unload()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (AudioData != null)
|
||||
{
|
||||
Stop();
|
||||
AudioData.Unload();
|
||||
AudioData = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Unload();
|
||||
base.Reset();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (AudioData == null || State != SoundState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QueueBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueBuffers()
|
||||
{
|
||||
int buffersNeeded = BUFFER_COUNT - (int) BuffersQueued; // don't get got by uint underflow!
|
||||
for (int i = 0; i < buffersNeeded; i += 1)
|
||||
{
|
||||
AddBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void AddBuffer()
|
||||
{
|
||||
var buffer = buffers[nextBufferIndex];
|
||||
nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
|
||||
|
||||
AudioData.Decode(
|
||||
(void*) buffer,
|
||||
(int) BufferSize,
|
||||
out int filledLengthInBytes,
|
||||
out bool reachedEnd
|
||||
);
|
||||
|
||||
if (filledLengthInBytes > 0)
|
||||
{
|
||||
var buf = new FAudio.FAudioBuffer
|
||||
{
|
||||
AudioBytes = (uint) filledLengthInBytes,
|
||||
pAudioData = buffer,
|
||||
PlayLength = (
|
||||
(uint) (filledLengthInBytes /
|
||||
Format.Channels /
|
||||
(uint) (Format.BitsPerSample / 8))
|
||||
)
|
||||
};
|
||||
|
||||
Submit(buf);
|
||||
}
|
||||
|
||||
if (reachedEnd)
|
||||
{
|
||||
/* We have reached the end of the data, what do we do? */
|
||||
if (Loop)
|
||||
{
|
||||
AudioData.Seek(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void InitializeBuffers()
|
||||
{
|
||||
BufferSize = AudioData.DecodeBufferSize;
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
{
|
||||
if (buffers[i] != IntPtr.Zero)
|
||||
{
|
||||
NativeMemory.Free((void*) buffers[i]);
|
||||
}
|
||||
|
||||
buffers[i] = (IntPtr) NativeMemory.Alloc(BufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected override unsafe void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
Stop();
|
||||
|
||||
for (int i = 0; i < BUFFER_COUNT; i += 1)
|
||||
{
|
||||
if (buffers[i] != IntPtr.Zero)
|
||||
{
|
||||
NativeMemory.Free((void*) buffers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// SourceVoices can send audio to a SubmixVoice for convenient effects processing.
|
||||
/// Submixes process in order of processingStage, from lowest to highest.
|
||||
/// Therefore submixes early in a chain should have a low processingStage, and later in the chain they should have a higher one.
|
||||
/// </summary>
|
||||
public class SubmixVoice : Voice
|
||||
{
|
||||
public SubmixVoice(
|
||||
AudioDevice device,
|
||||
uint sourceChannelCount,
|
||||
uint sampleRate,
|
||||
uint processingStage
|
||||
) : base(device, sourceChannelCount, device.DeviceDetails.OutputFormat.Format.nChannels)
|
||||
{
|
||||
FAudio.FAudio_CreateSubmixVoice(
|
||||
device.Handle,
|
||||
out handle,
|
||||
sourceChannelCount,
|
||||
sampleRate,
|
||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||
processingStage,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
SetOutputVoice(device.MasteringVoice);
|
||||
}
|
||||
|
||||
private SubmixVoice(
|
||||
AudioDevice device
|
||||
) : base(device, device.DeviceDetails.OutputFormat.Format.nChannels, device.DeviceDetails.OutputFormat.Format.nChannels)
|
||||
{
|
||||
FAudio.FAudio_CreateSubmixVoice(
|
||||
device.Handle,
|
||||
out handle,
|
||||
device.DeviceDetails.OutputFormat.Format.nChannels,
|
||||
device.DeviceDetails.OutputFormat.Format.nSamplesPerSec,
|
||||
FAudio.FAUDIO_VOICE_USEFILTER,
|
||||
int.MaxValue,
|
||||
IntPtr.Zero, // default sends to mastering voice
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
OutputVoice = null;
|
||||
}
|
||||
|
||||
internal static SubmixVoice CreateFauxMasteringVoice(AudioDevice device)
|
||||
{
|
||||
return new SubmixVoice(device);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// TransientVoice is intended for playing one-off sound effects that don't have a long term reference. <br/>
|
||||
/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back.
|
||||
/// </summary>
|
||||
public class TransientVoice : UpdatingSourceVoice, IPoolable<TransientVoice>
|
||||
{
|
||||
static TransientVoice IPoolable<TransientVoice>.Create(AudioDevice device, Format format)
|
||||
{
|
||||
return new TransientVoice(device, format);
|
||||
}
|
||||
|
||||
public TransientVoice(AudioDevice device, Format format) : base(device, format)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
lock (StateLock)
|
||||
{
|
||||
if (PlaybackInitiated && BuffersQueued == 0)
|
||||
{
|
||||
Return();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace MoonWorks.Audio
|
||||
{
|
||||
public abstract class UpdatingSourceVoice : SourceVoice
|
||||
{
|
||||
protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void Update();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,578 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using EasingFunction = System.Func<float, float>;
|
||||
|
||||
namespace MoonWorks.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles audio playback from audio buffer data. Can be configured with a variety of parameters.
|
||||
/// </summary>
|
||||
public abstract unsafe class Voice : AudioResource
|
||||
{
|
||||
protected IntPtr handle;
|
||||
public IntPtr Handle => handle;
|
||||
|
||||
public uint SourceChannelCount { get; }
|
||||
public uint DestinationChannelCount { get; }
|
||||
|
||||
protected SubmixVoice OutputVoice;
|
||||
private ReverbEffect ReverbEffect;
|
||||
|
||||
protected byte* pMatrixCoefficients;
|
||||
|
||||
public bool Is3D { get; protected set; }
|
||||
|
||||
private float dopplerFactor;
|
||||
/// <summary>
|
||||
/// The strength of the doppler effect on this voice.
|
||||
/// </summary>
|
||||
public float DopplerFactor
|
||||
{
|
||||
get => dopplerFactor;
|
||||
set
|
||||
{
|
||||
if (dopplerFactor != value)
|
||||
{
|
||||
dopplerFactor = value;
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float volume = 1;
|
||||
/// <summary>
|
||||
/// The overall volume level for the voice.
|
||||
/// </summary>
|
||||
public float Volume
|
||||
{
|
||||
get => volume;
|
||||
internal set
|
||||
{
|
||||
value = Math.MathHelper.Max(0, value);
|
||||
if (volume != value)
|
||||
{
|
||||
volume = value;
|
||||
FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float pitch = 0;
|
||||
/// <summary>
|
||||
/// The pitch of the voice.
|
||||
/// </summary>
|
||||
public float Pitch
|
||||
{
|
||||
get => pitch;
|
||||
internal set
|
||||
{
|
||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
if (pitch != value)
|
||||
{
|
||||
pitch = value;
|
||||
UpdatePitch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const float MAX_FILTER_FREQUENCY = 1f;
|
||||
private const float MAX_FILTER_ONEOVERQ = 1.5f;
|
||||
|
||||
private FAudio.FAudioFilterParameters filterParameters = new FAudio.FAudioFilterParameters
|
||||
{
|
||||
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
|
||||
Frequency = 1f,
|
||||
OneOverQ = 1f
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The frequency cutoff on the voice filter.
|
||||
/// </summary>
|
||||
public float FilterFrequency
|
||||
{
|
||||
get => filterParameters.Frequency;
|
||||
internal set
|
||||
{
|
||||
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
|
||||
if (filterParameters.Frequency != value)
|
||||
{
|
||||
filterParameters.Frequency = value;
|
||||
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
Handle,
|
||||
ref filterParameters,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reciprocal of Q factor.
|
||||
/// Controls how quickly frequencies beyond the filter frequency are dampened.
|
||||
/// </summary>
|
||||
public float FilterOneOverQ
|
||||
{
|
||||
get => filterParameters.OneOverQ;
|
||||
internal set
|
||||
{
|
||||
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
|
||||
if (filterParameters.OneOverQ != value)
|
||||
{
|
||||
filterParameters.OneOverQ = value;
|
||||
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
Handle,
|
||||
ref filterParameters,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FilterType filterType;
|
||||
/// <summary>
|
||||
/// The frequency filter that is applied to the voice.
|
||||
/// </summary>
|
||||
public FilterType FilterType
|
||||
{
|
||||
get => filterType;
|
||||
set
|
||||
{
|
||||
if (filterType != value)
|
||||
{
|
||||
filterType = value;
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case FilterType.None:
|
||||
filterParameters = new FAudio.FAudioFilterParameters
|
||||
{
|
||||
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
|
||||
Frequency = 1f,
|
||||
OneOverQ = 1f
|
||||
};
|
||||
break;
|
||||
|
||||
case FilterType.LowPass:
|
||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
|
||||
filterParameters.Frequency = 1f;
|
||||
break;
|
||||
|
||||
case FilterType.BandPass:
|
||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
|
||||
break;
|
||||
|
||||
case FilterType.HighPass:
|
||||
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
|
||||
filterParameters.Frequency = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_SetFilterParameters(
|
||||
Handle,
|
||||
ref filterParameters,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected float pan = 0;
|
||||
/// <summary>
|
||||
/// Left-right panning. -1 is hard left pan, 1 is hard right pan.
|
||||
/// </summary>
|
||||
public float Pan
|
||||
{
|
||||
get => pan;
|
||||
internal set
|
||||
{
|
||||
value = Math.MathHelper.Clamp(value, -1f, 1f);
|
||||
if (pan != value)
|
||||
{
|
||||
pan = value;
|
||||
|
||||
if (pan < -1f)
|
||||
{
|
||||
pan = -1f;
|
||||
}
|
||||
if (pan > 1f)
|
||||
{
|
||||
pan = 1f;
|
||||
}
|
||||
|
||||
if (Is3D) { return; }
|
||||
|
||||
SetPanMatrixCoefficients();
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
OutputVoice.Handle,
|
||||
SourceChannelCount,
|
||||
DestinationChannelCount,
|
||||
(nint) pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float reverb;
|
||||
/// <summary>
|
||||
/// The wet-dry mix of the reverb effect.
|
||||
/// Has no effect if SetReverbEffectChain has not been called.
|
||||
/// </summary>
|
||||
public unsafe float Reverb
|
||||
{
|
||||
get => reverb;
|
||||
internal set
|
||||
{
|
||||
if (ReverbEffect != null)
|
||||
{
|
||||
value = MathF.Max(0, value);
|
||||
if (reverb != value)
|
||||
{
|
||||
reverb = value;
|
||||
|
||||
float* outputMatrix = (float*) pMatrixCoefficients;
|
||||
outputMatrix[0] = reverb;
|
||||
if (SourceChannelCount == 2)
|
||||
{
|
||||
outputMatrix[1] = reverb;
|
||||
}
|
||||
|
||||
FAudio.FAudioVoice_SetOutputMatrix(
|
||||
Handle,
|
||||
ReverbEffect.Handle,
|
||||
SourceChannelCount,
|
||||
1,
|
||||
(nint) pMatrixCoefficients,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (ReverbEffect == null)
|
||||
{
|
||||
Logger.LogWarn("Tried to set reverb value before applying a reverb effect");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
|
||||
{
|
||||
SourceChannelCount = sourceChannelCount;
|
||||
DestinationChannelCount = destinationChannelCount;
|
||||
nuint memsize = 4 * sourceChannelCount * destinationChannelCount;
|
||||
pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize);
|
||||
SetPanMatrixCoefficients();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the pitch of the voice. Valid input range is -1f to 1f.
|
||||
/// </summary>
|
||||
public void SetPitch(float targetValue)
|
||||
{
|
||||
Pitch = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.Pitch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the pitch of the voice over a time duration in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public void SetPitch(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the pitch of the voice over a time duration in seconds after a delay in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public void SetPitch(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pitch, easingFunction, Pitch, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the volume of the voice. Minimum value is 0f.
|
||||
/// </summary>
|
||||
public void SetVolume(float targetValue)
|
||||
{
|
||||
Volume = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.Volume);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the volume of the voice over a time duration in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public void SetVolume(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the volume of the voice over a time duration in seconds after a delay in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public void SetVolume(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Volume, easingFunction, Volume, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the frequency cutoff on the voice filter. Valid range is 0.01f to 1f.
|
||||
/// </summary>
|
||||
public void SetFilterFrequency(float targetValue)
|
||||
{
|
||||
FilterFrequency = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.FilterFrequency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the frequency cutoff on the voice filter over a time duration in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public void SetFilterFrequency(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the frequency cutoff on the voice filter over a time duration in seconds after a delay in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public void SetFilterFrequency(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.FilterFrequency, easingFunction, FilterFrequency, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets reciprocal of Q factor on the frequency filter.
|
||||
/// Controls how quickly frequencies beyond the filter frequency are dampened.
|
||||
/// </summary>
|
||||
public void SetFilterOneOverQ(float targetValue)
|
||||
{
|
||||
FilterOneOverQ = targetValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a left-right panning value. -1f is hard left pan, 1f is hard right pan.
|
||||
/// </summary>
|
||||
public virtual void SetPan(float targetValue)
|
||||
{
|
||||
Pan = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.Pan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a left-right panning value over a time duration in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public virtual void SetPan(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a left-right panning value over a time duration in seconds after a delay in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public virtual void SetPan(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Pan, easingFunction, Pan, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the wet-dry mix value of the reverb effect. Minimum value is 0f.
|
||||
/// </summary>
|
||||
public virtual void SetReverb(float targetValue)
|
||||
{
|
||||
Reverb = targetValue;
|
||||
Device.ClearTweens(this, AudioTweenProperty.Reverb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the wet-dry mix value of the reverb effect over a time duration in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public virtual void SetReverb(float targetValue, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the wet-dry mix value of the reverb effect over a time duration in seconds after a delay in seconds.
|
||||
/// </summary>
|
||||
/// <param name="easingFunction">An easing function. See MoonWorks.Math.Easing.Function.Float</param>
|
||||
public virtual void SetReverb(float targetValue, float delayTime, float duration, EasingFunction easingFunction)
|
||||
{
|
||||
Device.CreateTween(this, AudioTweenProperty.Reverb, easingFunction, Volume, targetValue, duration, delayTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the output voice for this voice.
|
||||
/// </summary>
|
||||
/// <param name="send">Where the output should be sent.</param>
|
||||
public unsafe void SetOutputVoice(SubmixVoice send)
|
||||
{
|
||||
OutputVoice = send;
|
||||
|
||||
if (ReverbEffect != null)
|
||||
{
|
||||
SetReverbEffectChain(ReverbEffect);
|
||||
}
|
||||
else
|
||||
{
|
||||
FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
|
||||
sendDesc[0].Flags = 0;
|
||||
sendDesc[0].pOutputVoice = send.Handle;
|
||||
|
||||
var sends = new FAudio.FAudioVoiceSends();
|
||||
sends.SendCount = 1;
|
||||
sends.pSends = (nint) sendDesc;
|
||||
|
||||
FAudio.FAudioVoice_SetOutputVoices(
|
||||
Handle,
|
||||
ref sends
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a reverb effect chain to this voice.
|
||||
/// </summary>
|
||||
public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
|
||||
{
|
||||
var sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
|
||||
sendDesc[0].Flags = 0;
|
||||
sendDesc[0].pOutputVoice = OutputVoice.Handle;
|
||||
sendDesc[1].Flags = 0;
|
||||
sendDesc[1].pOutputVoice = reverbEffect.Handle;
|
||||
|
||||
var sends = new FAudio.FAudioVoiceSends();
|
||||
sends.SendCount = 2;
|
||||
sends.pSends = (nint) sendDesc;
|
||||
|
||||
FAudio.FAudioVoice_SetOutputVoices(
|
||||
Handle,
|
||||
ref sends
|
||||
);
|
||||
|
||||
ReverbEffect = reverbEffect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the reverb effect chain from this voice.
|
||||
/// </summary>
|
||||
public void RemoveReverbEffectChain()
|
||||
{
|
||||
if (ReverbEffect != null)
|
||||
{
|
||||
ReverbEffect = null;
|
||||
reverb = 0;
|
||||
SetOutputVoice(OutputVoice);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all voice parameters to defaults.
|
||||
/// </summary>
|
||||
public virtual void Reset()
|
||||
{
|
||||
RemoveReverbEffectChain();
|
||||
Volume = 1;
|
||||
Pan = 0;
|
||||
Pitch = 0;
|
||||
FilterType = FilterType.None;
|
||||
SetOutputVoice(Device.MasteringVoice);
|
||||
}
|
||||
|
||||
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
|
||||
private unsafe void SetPanMatrixCoefficients()
|
||||
{
|
||||
/* Two major things to notice:
|
||||
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
|
||||
* 2. Stereo panning is WAY more complicated than you think.
|
||||
* The main thing is that hard panning does NOT eliminate an
|
||||
* entire channel; the two channels are blended on each side.
|
||||
* -flibit
|
||||
*/
|
||||
float* outputMatrix = (float*) pMatrixCoefficients;
|
||||
if (SourceChannelCount == 1)
|
||||
{
|
||||
if (DestinationChannelCount == 1)
|
||||
{
|
||||
outputMatrix[0] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
|
||||
outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DestinationChannelCount == 1)
|
||||
{
|
||||
outputMatrix[0] = 1.0f;
|
||||
outputMatrix[1] = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pan <= 0.0f)
|
||||
{
|
||||
// Left speaker blends left/right channels
|
||||
outputMatrix[0] = 0.5f * pan + 1.0f;
|
||||
outputMatrix[1] = 0.5f * -pan;
|
||||
// Right speaker gets less of the right channel
|
||||
outputMatrix[2] = 0.0f;
|
||||
outputMatrix[3] = pan + 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Left speaker gets less of the left channel
|
||||
outputMatrix[0] = -pan + 1.0f;
|
||||
outputMatrix[1] = 0.0f;
|
||||
// Right speaker blends right/left channels
|
||||
outputMatrix[2] = 0.5f * pan;
|
||||
outputMatrix[3] = 0.5f * -pan + 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdatePitch()
|
||||
{
|
||||
float doppler;
|
||||
float dopplerScale = Device.DopplerScale;
|
||||
if (!Is3D || dopplerScale == 0.0f)
|
||||
{
|
||||
doppler = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
doppler = DopplerFactor * dopplerScale;
|
||||
}
|
||||
|
||||
FAudio.FAudioSourceVoice_SetFrequencyRatio(
|
||||
Handle,
|
||||
(float) System.Math.Pow(2.0, pitch) * doppler,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
protected override unsafe void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
NativeMemory.Free(pMatrixCoefficients);
|
||||
FAudio.FAudioVoice_DestroyVoice(Handle);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Graphics;
|
||||
using MoonWorks.Graphics.PackedVector;
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
/// <summary>
|
||||
/// Conversion utilities for interop.
|
||||
/// </summary>
|
||||
public static class Conversions
|
||||
{
|
||||
private readonly static Dictionary<VertexElementFormat, uint> Sizes = new Dictionary<VertexElementFormat, uint>
|
||||
{
|
||||
{ VertexElementFormat.Byte4, (uint) Marshal.SizeOf<Byte4>() },
|
||||
{ VertexElementFormat.Color, (uint) Marshal.SizeOf<Color>() },
|
||||
{ VertexElementFormat.Float, (uint) Marshal.SizeOf<float>() },
|
||||
{ VertexElementFormat.HalfVector2, (uint) Marshal.SizeOf<HalfVector2>() },
|
||||
{ VertexElementFormat.HalfVector4, (uint) Marshal.SizeOf<HalfVector4>() },
|
||||
{ VertexElementFormat.NormalizedShort2, (uint) Marshal.SizeOf<NormalizedShort2>() },
|
||||
{ VertexElementFormat.NormalizedShort4, (uint) Marshal.SizeOf<NormalizedShort4>() },
|
||||
{ VertexElementFormat.Short2, (uint) Marshal.SizeOf<Short2>() },
|
||||
{ VertexElementFormat.Short4, (uint) Marshal.SizeOf<Short4>() },
|
||||
{ VertexElementFormat.UInt, (uint) Marshal.SizeOf<uint>() },
|
||||
{ VertexElementFormat.Vector2, (uint) Marshal.SizeOf<Math.Float.Vector2>() },
|
||||
{ VertexElementFormat.Vector3, (uint) Marshal.SizeOf<Math.Float.Vector3>() },
|
||||
{ VertexElementFormat.Vector4, (uint) Marshal.SizeOf<Math.Float.Vector4>() }
|
||||
};
|
||||
|
||||
public static byte BoolToByte(bool b)
|
||||
{
|
||||
return (byte) (b ? 1 : 0);
|
||||
}
|
||||
|
||||
public static bool ByteToBool(byte b)
|
||||
{
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
public static uint VertexElementFormatSize(VertexElementFormat format)
|
||||
{
|
||||
return Sizes[format];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
public class AudioLoadException : Exception
|
||||
{
|
||||
public AudioLoadException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
namespace MoonWorks
|
||||
{
|
||||
public enum FrameLimiterMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The game will render at the maximum possible framerate that the computing resources allow. <br/>
|
||||
/// Note that this may lead to overheating, resource starvation, etc.
|
||||
/// </summary>
|
||||
Uncapped,
|
||||
/// <summary>
|
||||
/// The game will render no more than the specified frames per second.
|
||||
/// </summary>
|
||||
Capped
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Game's frame limiter setting. Specifies uncapped framerate or a maximum rendering frames per second value. <br/>
|
||||
/// Note that this is separate from the Game's Update timestep and can be a different value.
|
||||
/// </summary>
|
||||
public struct FrameLimiterSettings
|
||||
{
|
||||
public FrameLimiterMode Mode;
|
||||
/// <summary>
|
||||
/// If Mode is set to Capped, this is the maximum frames per second that will be rendered.
|
||||
/// </summary>
|
||||
public int Cap;
|
||||
|
||||
public FrameLimiterSettings(
|
||||
FrameLimiterMode mode,
|
||||
int cap
|
||||
) {
|
||||
Mode = mode;
|
||||
Cap = cap;
|
||||
}
|
||||
}
|
||||
}
|
498
src/Game.cs
498
src/Game.cs
|
@ -1,164 +1,426 @@
|
|||
using System.Collections.Generic;
|
||||
using SDL2;
|
||||
using SDL2;
|
||||
using MoonWorks.Audio;
|
||||
using MoonWorks.Graphics;
|
||||
using MoonWorks.Input;
|
||||
using MoonWorks.Window;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
public abstract class Game
|
||||
{
|
||||
public const double MAX_DELTA_TIME = 0.1;
|
||||
/// <summary>
|
||||
/// This class is your entry point into controlling your game. <br/>
|
||||
/// It manages the main game loop and subsystems. <br/>
|
||||
/// You should inherit this class and implement Update and Draw methods. <br/>
|
||||
/// Then instantiate your Game subclass from your Program.Main method and call the Run method.
|
||||
/// </summary>
|
||||
public abstract class Game
|
||||
{
|
||||
public TimeSpan MAX_DELTA_TIME = TimeSpan.FromMilliseconds(100);
|
||||
public TimeSpan Timestep { get; private set; }
|
||||
|
||||
private bool quit = false;
|
||||
private double timestep;
|
||||
ulong currentTime = SDL.SDL_GetPerformanceCounter();
|
||||
double accumulator = 0;
|
||||
bool debugMode;
|
||||
private bool quit = false;
|
||||
private Stopwatch gameTimer;
|
||||
private long previousTicks = 0;
|
||||
TimeSpan accumulatedUpdateTime = TimeSpan.Zero;
|
||||
TimeSpan accumulatedDrawTime = TimeSpan.Zero;
|
||||
// must be a power of 2 so we can do a bitmask optimization when checking worst case
|
||||
private const int PREVIOUS_SLEEP_TIME_COUNT = 128;
|
||||
private const int SLEEP_TIME_MASK = PREVIOUS_SLEEP_TIME_COUNT - 1;
|
||||
private TimeSpan[] previousSleepTimes = new TimeSpan[PREVIOUS_SLEEP_TIME_COUNT];
|
||||
private int sleepTimeIndex = 0;
|
||||
private TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1);
|
||||
|
||||
public OSWindow Window { get; }
|
||||
public GraphicsDevice GraphicsDevice { get; }
|
||||
public AudioDevice AudioDevice { get; }
|
||||
public Inputs Inputs { get; }
|
||||
private bool FramerateCapped = false;
|
||||
private TimeSpan FramerateCapTimeSpan = TimeSpan.Zero;
|
||||
|
||||
private Dictionary<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
|
||||
{
|
||||
{ PresentMode.Immediate, RefreshCS.Refresh.PresentMode.Immediate },
|
||||
{ PresentMode.Mailbox, RefreshCS.Refresh.PresentMode.Mailbox },
|
||||
{ PresentMode.FIFO, RefreshCS.Refresh.PresentMode.FIFO },
|
||||
{ PresentMode.FIFORelaxed, RefreshCS.Refresh.PresentMode.FIFORelaxed }
|
||||
};
|
||||
public GraphicsDevice GraphicsDevice { get; }
|
||||
public AudioDevice AudioDevice { get; }
|
||||
public Inputs Inputs { get; }
|
||||
|
||||
public Game(
|
||||
WindowCreateInfo windowCreateInfo,
|
||||
PresentMode presentMode,
|
||||
int targetTimestep = 60,
|
||||
bool debugMode = false
|
||||
) {
|
||||
timestep = 1.0 / targetTimestep;
|
||||
/// <summary>
|
||||
/// This Window is automatically created when your Game is instantiated.
|
||||
/// </summary>
|
||||
public Window MainWindow { get; }
|
||||
|
||||
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
|
||||
{
|
||||
System.Console.WriteLine("Failed to initialize SDL!");
|
||||
return;
|
||||
}
|
||||
/// <summary>
|
||||
/// Instantiates your Game.
|
||||
/// </summary>
|
||||
/// <param name="windowCreateInfo">The parameters that will be used to create the MainWindow.</param>
|
||||
/// <param name="frameLimiterSettings">The frame limiter settings.</param>
|
||||
/// <param name="targetTimestep">How often Game.Update will run in terms of ticks per second.</param>
|
||||
/// <param name="debugMode">If true, enables extra debug checks. Should be turned off for release builds.</param>
|
||||
public Game(
|
||||
WindowCreateInfo windowCreateInfo,
|
||||
FrameLimiterSettings frameLimiterSettings,
|
||||
int targetTimestep = 60,
|
||||
bool debugMode = false
|
||||
)
|
||||
{
|
||||
Logger.LogInfo("Initializing frame limiter...");
|
||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep);
|
||||
gameTimer = Stopwatch.StartNew();
|
||||
|
||||
Logger.Initialize();
|
||||
SetFrameLimiter(frameLimiterSettings);
|
||||
|
||||
Inputs = new Inputs();
|
||||
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
||||
{
|
||||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
||||
}
|
||||
|
||||
Window = new OSWindow(windowCreateInfo);
|
||||
Logger.LogInfo("Initializing SDL...");
|
||||
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
|
||||
{
|
||||
Logger.LogError("Failed to initialize SDL!");
|
||||
return;
|
||||
}
|
||||
|
||||
GraphicsDevice = new GraphicsDevice(
|
||||
Window.Handle,
|
||||
moonWorksToRefreshPresentMode[presentMode],
|
||||
debugMode
|
||||
);
|
||||
Logger.Initialize();
|
||||
|
||||
AudioDevice = new AudioDevice();
|
||||
Logger.LogInfo("Initializing input...");
|
||||
Inputs = new Inputs();
|
||||
|
||||
this.debugMode = debugMode;
|
||||
}
|
||||
Logger.LogInfo("Initializing graphics device...");
|
||||
GraphicsDevice = new GraphicsDevice(
|
||||
Backend.Vulkan,
|
||||
debugMode
|
||||
);
|
||||
|
||||
public void Run()
|
||||
{
|
||||
while (!quit)
|
||||
{
|
||||
var newTime = SDL.SDL_GetPerformanceCounter();
|
||||
double frameTime = (newTime - currentTime) / (double)SDL.SDL_GetPerformanceFrequency();
|
||||
Logger.LogInfo("Initializing main window...");
|
||||
MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN);
|
||||
|
||||
if (frameTime > MAX_DELTA_TIME)
|
||||
{
|
||||
frameTime = MAX_DELTA_TIME;
|
||||
}
|
||||
if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode))
|
||||
{
|
||||
throw new System.SystemException("Could not claim window!");
|
||||
}
|
||||
|
||||
currentTime = newTime;
|
||||
Logger.LogInfo("Initializing audio thread...");
|
||||
AudioDevice = new AudioDevice();
|
||||
}
|
||||
|
||||
accumulator += frameTime;
|
||||
/// <summary>
|
||||
/// Initiates the main game loop. Call this once from your Program.Main method.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
MainWindow.Show();
|
||||
|
||||
if (!quit)
|
||||
{
|
||||
while (accumulator >= timestep)
|
||||
{
|
||||
Inputs.Mouse.Wheel = 0;
|
||||
while (!quit)
|
||||
{
|
||||
Tick();
|
||||
}
|
||||
|
||||
HandleSDLEvents();
|
||||
Logger.LogInfo("Starting shutdown sequence...");
|
||||
|
||||
Inputs.Update();
|
||||
AudioDevice.Update();
|
||||
Logger.LogInfo("Cleaning up game...");
|
||||
Destroy();
|
||||
|
||||
Update(timestep);
|
||||
Logger.LogInfo("Unclaiming window...");
|
||||
GraphicsDevice.UnclaimWindow(MainWindow);
|
||||
|
||||
accumulator -= timestep;
|
||||
}
|
||||
Logger.LogInfo("Disposing window...");
|
||||
MainWindow.Dispose();
|
||||
|
||||
double alpha = accumulator / timestep;
|
||||
Logger.LogInfo("Disposing graphics device...");
|
||||
GraphicsDevice.Dispose();
|
||||
|
||||
Draw(timestep, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.LogInfo("Closing audio thread...");
|
||||
AudioDevice.Dispose();
|
||||
|
||||
private void HandleSDLEvents()
|
||||
{
|
||||
while (SDL.SDL_PollEvent(out var _event) == 1)
|
||||
{
|
||||
switch (_event.type)
|
||||
{
|
||||
case SDL.SDL_EventType.SDL_QUIT:
|
||||
quit = true;
|
||||
break;
|
||||
SDL.SDL_Quit();
|
||||
}
|
||||
|
||||
case SDL.SDL_EventType.SDL_TEXTINPUT:
|
||||
HandleTextInput(_event);
|
||||
break;
|
||||
/// <summary>
|
||||
/// Updates the frame limiter settings.
|
||||
/// </summary>
|
||||
public void SetFrameLimiter(FrameLimiterSettings settings)
|
||||
{
|
||||
FramerateCapped = settings.Mode == FrameLimiterMode.Capped;
|
||||
|
||||
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
|
||||
Inputs.Mouse.Wheel += _event.wheel.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (FramerateCapped)
|
||||
{
|
||||
FramerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap);
|
||||
}
|
||||
else
|
||||
{
|
||||
FramerateCapTimeSpan = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void Update(double dt);
|
||||
/// <summary>
|
||||
/// Starts the game shutdown process.
|
||||
/// </summary>
|
||||
public void Quit()
|
||||
{
|
||||
quit = true;
|
||||
}
|
||||
|
||||
protected abstract void Draw(double dt, double alpha);
|
||||
/// <summary>
|
||||
/// Will execute at the specified targetTimestep you provided when instantiating your Game class.
|
||||
/// </summary>
|
||||
/// <param name="delta"></param>
|
||||
protected abstract void Update(TimeSpan delta);
|
||||
|
||||
private void HandleTextInput(SDL2.SDL.SDL_Event evt)
|
||||
{
|
||||
// Based on the SDL2# LPUtf8StrMarshaler
|
||||
unsafe
|
||||
{
|
||||
int bytes = MeasureStringLength(evt.text.text);
|
||||
if (bytes > 0)
|
||||
{
|
||||
/* UTF8 will never encode more characters
|
||||
/// <summary>
|
||||
/// If the frame limiter mode is Capped, this will run at most Cap times per second. <br />
|
||||
/// Otherwise it will run as many times as possible.
|
||||
/// </summary>
|
||||
/// <param name="alpha">A value from 0-1 describing how "in-between" update ticks it is called. Useful for interpolation.</param>
|
||||
protected abstract void Draw(double alpha);
|
||||
|
||||
/// <summary>
|
||||
/// You can optionally override this to perform cleanup tasks before the game quits.
|
||||
/// </summary>
|
||||
protected virtual void Destroy() {}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a file is dropped on the game window.
|
||||
/// </summary>
|
||||
protected virtual void DropFile(string filePath) {}
|
||||
|
||||
/// <summary>
|
||||
/// Required to distinguish between multiple files dropped at once
|
||||
/// vs multiple files dropped one at a time.
|
||||
/// Called once for every multi-file drop.
|
||||
/// </summary>
|
||||
protected virtual void DropBegin() {}
|
||||
protected virtual void DropComplete() {}
|
||||
|
||||
private void Tick()
|
||||
{
|
||||
AdvanceElapsedTime();
|
||||
|
||||
if (FramerateCapped)
|
||||
{
|
||||
/* We want to wait until the framerate cap,
|
||||
* but we don't want to oversleep. Requesting repeated 1ms sleeps and
|
||||
* seeing how long we actually slept for lets us estimate the worst case
|
||||
* sleep precision so we don't oversleep the next frame.
|
||||
*/
|
||||
while (accumulatedDrawTime + worstCaseSleepPrecision < FramerateCapTimeSpan)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1);
|
||||
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
|
||||
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
|
||||
}
|
||||
|
||||
/* Now that we have slept into the sleep precision threshold, we need to wait
|
||||
* for just a little bit longer until the target elapsed time has been reached.
|
||||
* SpinWait(1) works by pausing the thread for very short intervals, so it is
|
||||
* an efficient and time-accurate way to wait out the rest of the time.
|
||||
*/
|
||||
while (accumulatedDrawTime < FramerateCapTimeSpan)
|
||||
{
|
||||
System.Threading.Thread.SpinWait(1);
|
||||
AdvanceElapsedTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we are going to perform an update, let's handle SDL events.
|
||||
HandleSDLEvents();
|
||||
|
||||
// Do not let any step take longer than our maximum.
|
||||
if (accumulatedUpdateTime > MAX_DELTA_TIME)
|
||||
{
|
||||
accumulatedUpdateTime = MAX_DELTA_TIME;
|
||||
}
|
||||
|
||||
if (!quit)
|
||||
{
|
||||
while (accumulatedUpdateTime >= Timestep)
|
||||
{
|
||||
Inputs.Update();
|
||||
Update(Timestep);
|
||||
AudioDevice.WakeThread();
|
||||
|
||||
accumulatedUpdateTime -= Timestep;
|
||||
}
|
||||
|
||||
var alpha = accumulatedUpdateTime / Timestep;
|
||||
|
||||
Draw(alpha);
|
||||
accumulatedDrawTime -= FramerateCapTimeSpan;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSDLEvents()
|
||||
{
|
||||
while (SDL.SDL_PollEvent(out var _event) == 1)
|
||||
{
|
||||
switch (_event.type)
|
||||
{
|
||||
case SDL.SDL_EventType.SDL_QUIT:
|
||||
quit = true;
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_TEXTINPUT:
|
||||
HandleTextInput(_event);
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
|
||||
Inputs.Mouse.WheelRaw += _event.wheel.y;
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_DROPBEGIN:
|
||||
DropBegin();
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_DROPCOMPLETE:
|
||||
DropComplete();
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_DROPFILE:
|
||||
HandleFileDrop(_event);
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED:
|
||||
HandleControllerAdded(_event);
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED:
|
||||
HandleControllerRemoved(_event);
|
||||
break;
|
||||
|
||||
case SDL.SDL_EventType.SDL_WINDOWEVENT:
|
||||
HandleWindowEvent(_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleWindowEvent(SDL.SDL_Event evt)
|
||||
{
|
||||
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
|
||||
{
|
||||
var window = Window.Lookup(evt.window.windowID);
|
||||
window.HandleSizeChange((uint) evt.window.data1, (uint) evt.window.data2);
|
||||
}
|
||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE)
|
||||
{
|
||||
var window = Window.Lookup(evt.window.windowID);
|
||||
GraphicsDevice.UnclaimWindow(window);
|
||||
window.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTextInput(SDL.SDL_Event evt)
|
||||
{
|
||||
// Based on the SDL2# LPUtf8StrMarshaler
|
||||
unsafe
|
||||
{
|
||||
int bytes = MeasureStringLength(evt.text.text);
|
||||
if (bytes > 0)
|
||||
{
|
||||
/* UTF8 will never encode more characters
|
||||
* than bytes in a string, so bytes is a
|
||||
* suitable upper estimate of size needed
|
||||
*/
|
||||
char* charsBuffer = stackalloc char[bytes];
|
||||
int chars = Encoding.UTF8.GetChars(
|
||||
evt.text.text,
|
||||
bytes,
|
||||
charsBuffer,
|
||||
bytes
|
||||
);
|
||||
char* charsBuffer = stackalloc char[bytes];
|
||||
int chars = Encoding.UTF8.GetChars(
|
||||
evt.text.text,
|
||||
bytes,
|
||||
charsBuffer,
|
||||
bytes
|
||||
);
|
||||
|
||||
for (int i = 0; i < chars; i += 1)
|
||||
{
|
||||
Inputs.OnTextInput(charsBuffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < chars; i += 1)
|
||||
{
|
||||
Inputs.OnTextInput(charsBuffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFileDrop(SDL.SDL_Event evt)
|
||||
{
|
||||
// Need to do it this way because SDL2 expects you to free the filename string.
|
||||
string filePath = SDL.UTF8_ToManaged(evt.drop.file, true);
|
||||
DropFile(filePath);
|
||||
}
|
||||
|
||||
private void HandleControllerAdded(SDL.SDL_Event evt)
|
||||
{
|
||||
var index = evt.cdevice.which;
|
||||
if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
|
||||
{
|
||||
Logger.LogInfo("New controller detected!");
|
||||
Inputs.AddGamepad(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleControllerRemoved(SDL.SDL_Event evt)
|
||||
{
|
||||
Logger.LogInfo("Controller removal detected!");
|
||||
Inputs.RemoveGamepad(evt.cdevice.which);
|
||||
}
|
||||
|
||||
public static void ShowRuntimeError(string title, string message)
|
||||
{
|
||||
SDL.SDL_ShowSimpleMessageBox(
|
||||
SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
||||
title ?? "",
|
||||
message ?? "",
|
||||
IntPtr.Zero
|
||||
);
|
||||
}
|
||||
|
||||
private TimeSpan AdvanceElapsedTime()
|
||||
{
|
||||
long currentTicks = gameTimer.Elapsed.Ticks;
|
||||
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
|
||||
accumulatedUpdateTime += timeAdvanced;
|
||||
accumulatedDrawTime += timeAdvanced;
|
||||
previousTicks = currentTicks;
|
||||
return timeAdvanced;
|
||||
}
|
||||
|
||||
/* To calculate the sleep precision of the OS, we take the worst case
|
||||
* time spent sleeping over the results of previous requests to sleep 1ms.
|
||||
*/
|
||||
private void UpdateEstimatedSleepPrecision(TimeSpan timeSpentSleeping)
|
||||
{
|
||||
/* It is unlikely that the scheduler will actually be more imprecise than
|
||||
* 4ms and we don't want to get wrecked by a single long sleep so we cap this
|
||||
* value at 4ms for sanity.
|
||||
*/
|
||||
var upperTimeBound = TimeSpan.FromMilliseconds(4);
|
||||
|
||||
if (timeSpentSleeping > upperTimeBound)
|
||||
{
|
||||
timeSpentSleeping = upperTimeBound;
|
||||
}
|
||||
|
||||
/* We know the previous worst case - it's saved in worstCaseSleepPrecision.
|
||||
* We also know the current index. So the only way the worst case changes
|
||||
* is if we either 1) just got a new worst case, or 2) the worst case was
|
||||
* the oldest entry on the list.
|
||||
*/
|
||||
if (timeSpentSleeping >= worstCaseSleepPrecision)
|
||||
{
|
||||
worstCaseSleepPrecision = timeSpentSleeping;
|
||||
}
|
||||
else if (previousSleepTimes[sleepTimeIndex] == worstCaseSleepPrecision)
|
||||
{
|
||||
var maxSleepTime = TimeSpan.MinValue;
|
||||
for (int i = 0; i < previousSleepTimes.Length; i++)
|
||||
{
|
||||
if (previousSleepTimes[i] > maxSleepTime)
|
||||
{
|
||||
maxSleepTime = previousSleepTimes[i];
|
||||
}
|
||||
}
|
||||
worstCaseSleepPrecision = maxSleepTime;
|
||||
}
|
||||
|
||||
previousSleepTimes[sleepTimeIndex] = timeSpentSleeping;
|
||||
sleepTimeIndex = (sleepTimeIndex + 1) & SLEEP_TIME_MASK;
|
||||
}
|
||||
|
||||
private unsafe static int MeasureStringLength(byte* ptr)
|
||||
{
|
||||
int bytes;
|
||||
for (bytes = 0; *ptr != 0; ptr += 1, bytes += 1);
|
||||
for (bytes = 0; *ptr != 0; ptr += 1, bytes += 1) ;
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct BufferBinding
|
||||
{
|
||||
public Buffer Buffer;
|
||||
public ulong Offset;
|
||||
/// <summary>
|
||||
/// A buffer-offset pair to be used when binding vertex buffers.
|
||||
/// </summary>
|
||||
public struct BufferBinding
|
||||
{
|
||||
public Buffer Buffer;
|
||||
public ulong Offset;
|
||||
|
||||
public BufferBinding(Buffer buffer, ulong offset)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
}
|
||||
}
|
||||
public BufferBinding(Buffer buffer, ulong offset)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct TextureSamplerBinding
|
||||
{
|
||||
public Texture Texture;
|
||||
public Sampler Sampler;
|
||||
/// <summary>
|
||||
/// A texture-sampler pair to be used when binding samplers.
|
||||
/// </summary>
|
||||
public struct TextureSamplerBinding
|
||||
{
|
||||
public Texture Texture;
|
||||
public Sampler Sampler;
|
||||
|
||||
public TextureSamplerBinding(Texture texture, Sampler sampler)
|
||||
{
|
||||
Texture = texture;
|
||||
Sampler = sampler;
|
||||
}
|
||||
}
|
||||
public TextureSamplerBinding(Texture texture, Sampler sampler)
|
||||
{
|
||||
Texture = texture;
|
||||
Sampler = sampler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -19,6 +19,8 @@ using System;
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
using MoonWorks.Graphics.PackedVector;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
|
@ -41,7 +43,7 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
unchecked
|
||||
{
|
||||
return (byte)(this.packedValue);
|
||||
return (byte) (this.packedValue);
|
||||
}
|
||||
}
|
||||
set
|
||||
|
@ -59,12 +61,12 @@ namespace MoonWorks.Graphics
|
|||
{
|
||||
unchecked
|
||||
{
|
||||
return (byte)(this.packedValue >> 8);
|
||||
return (byte) (this.packedValue >> 8);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
this.packedValue = (this.packedValue & 0xffff00ff) | ((uint)value << 8);
|
||||
this.packedValue = (this.packedValue & 0xffff00ff) | ((uint) value << 8);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1538,27 +1540,27 @@ namespace MoonWorks.Graphics
|
|||
RosyBrown = new Color(0xff8f8fbc);
|
||||
RoyalBlue = new Color(0xffe16941);
|
||||
SaddleBrown = new Color(0xff13458b);
|
||||
Salmon= new Color(0xff7280fa);
|
||||
Salmon = new Color(0xff7280fa);
|
||||
SandyBrown = new Color(0xff60a4f4);
|
||||
SeaGreen = new Color(0xff578b2e);
|
||||
SeaShell = new Color(0xffeef5ff);
|
||||
Sienna = new Color(0xff2d52a0);
|
||||
Silver = new Color(0xffc0c0c0);
|
||||
SkyBlue = new Color(0xffebce87);
|
||||
SlateBlue= new Color(0xffcd5a6a);
|
||||
SlateGray= new Color(0xff908070);
|
||||
Snow= new Color(0xfffafaff);
|
||||
SpringGreen= new Color(0xff7fff00);
|
||||
SteelBlue= new Color(0xffb48246);
|
||||
Tan= new Color(0xff8cb4d2);
|
||||
Teal= new Color(0xff808000);
|
||||
Thistle= new Color(0xffd8bfd8);
|
||||
Tomato= new Color(0xff4763ff);
|
||||
Turquoise= new Color(0xffd0e040);
|
||||
Violet= new Color(0xffee82ee);
|
||||
Wheat= new Color(0xffb3def5);
|
||||
White= new Color(uint.MaxValue);
|
||||
WhiteSmoke= new Color(0xfff5f5f5);
|
||||
SlateBlue = new Color(0xffcd5a6a);
|
||||
SlateGray = new Color(0xff908070);
|
||||
Snow = new Color(0xfffafaff);
|
||||
SpringGreen = new Color(0xff7fff00);
|
||||
SteelBlue = new Color(0xffb48246);
|
||||
Tan = new Color(0xff8cb4d2);
|
||||
Teal = new Color(0xff808000);
|
||||
Thistle = new Color(0xffd8bfd8);
|
||||
Tomato = new Color(0xff4763ff);
|
||||
Turquoise = new Color(0xffd0e040);
|
||||
Violet = new Color(0xffee82ee);
|
||||
Wheat = new Color(0xffb3def5);
|
||||
White = new Color(uint.MaxValue);
|
||||
WhiteSmoke = new Color(0xfff5f5f5);
|
||||
Yellow = new Color(0xff00ffff);
|
||||
YellowGreen = new Color(0xff32cd9a);
|
||||
}
|
||||
|
@ -1623,7 +1625,7 @@ namespace MoonWorks.Graphics
|
|||
R = (byte) MathHelper.Clamp(r, Byte.MinValue, Byte.MaxValue);
|
||||
G = (byte) MathHelper.Clamp(g, Byte.MinValue, Byte.MaxValue);
|
||||
B = (byte) MathHelper.Clamp(b, Byte.MinValue, Byte.MaxValue);
|
||||
A = (byte)255;
|
||||
A = (byte) 255;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1757,6 +1759,67 @@ namespace MoonWorks.Graphics
|
|||
);
|
||||
}
|
||||
|
||||
// Modified from one of the responses here:
|
||||
// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both/6930407#6930407
|
||||
public static Color FromHSV(float r, float g, float b)
|
||||
{
|
||||
r = (100 + r) % 1f;
|
||||
|
||||
float hueSlice = 6 * r; // [0, 6)
|
||||
float hueSliceInteger = MathF.Floor(hueSlice);
|
||||
|
||||
// In [0,1) for each hue slice
|
||||
float hueSliceInterpolant = hueSlice - hueSliceInteger;
|
||||
|
||||
Vector3 tempRGB = new Vector3(
|
||||
b * (1f - g),
|
||||
b * (1f - g * hueSliceInterpolant),
|
||||
b * (1f - g * (1f - hueSliceInterpolant))
|
||||
);
|
||||
|
||||
// The idea here to avoid conditions is to notice that the conversion code can be rewritten:
|
||||
// if ( var_i == 0 ) { R = V ; G = TempRGB.z ; B = TempRGB.x }
|
||||
// else if ( var_i == 2 ) { R = TempRGB.x ; G = V ; B = TempRGB.z }
|
||||
// else if ( var_i == 4 ) { R = TempRGB.z ; G = TempRGB.x ; B = V }
|
||||
//
|
||||
// else if ( var_i == 1 ) { R = TempRGB.y ; G = V ; B = TempRGB.x }
|
||||
// else if ( var_i == 3 ) { R = TempRGB.x ; G = TempRGB.y ; B = V }
|
||||
// else if ( var_i == 5 ) { R = V ; G = TempRGB.x ; B = TempRGB.y }
|
||||
//
|
||||
// This shows several things:
|
||||
// . A separation between even and odd slices
|
||||
// . If slices (0,2,4) and (1,3,5) can be rewritten as basically being slices (0,1,2) then
|
||||
// the operation simply amounts to performing a "rotate right" on the RGB components
|
||||
// . The base value to rotate is either (V, B, R) for even slices or (G, V, R) for odd slices
|
||||
//
|
||||
float isOddSlice = hueSliceInteger % 2f; // 0 if even (slices 0, 2, 4), 1 if odd (slices 1, 3, 5)
|
||||
float threeSliceSelector = 0.5f * (hueSliceInteger - isOddSlice); // (0, 1, 2) corresponding to slices (0, 2, 4) and (1, 3, 5)
|
||||
|
||||
Vector3 scrollingRGBForEvenSlices = new Vector3(b, tempRGB.Z, tempRGB.X); // (V, Temp Blue, Temp Red) for even slices (0, 2, 4)
|
||||
Vector3 scrollingRGBForOddSlices = new Vector3(tempRGB.Y, b, tempRGB.X); // (Temp Green, V, Temp Red) for odd slices (1, 3, 5)
|
||||
Vector3 scrollingRGB = Vector3.Lerp(scrollingRGBForEvenSlices, scrollingRGBForOddSlices, isOddSlice);
|
||||
|
||||
float IsNotFirstSlice = MathHelper.Clamp(threeSliceSelector, 0f, 1f); // 1 if NOT the first slice (true for slices 1 and 2)
|
||||
float IsNotSecondSlice = MathHelper.Clamp(threeSliceSelector - 1f, 0f, 1f); // 1 if NOT the first or second slice (true only for slice 2)
|
||||
|
||||
Vector3 color = Vector3.Lerp(
|
||||
scrollingRGB,
|
||||
Vector3.Lerp(
|
||||
new Vector3(scrollingRGB.Z, scrollingRGB.X, scrollingRGB.Y),
|
||||
new Vector3(scrollingRGB.Y, scrollingRGB.Z, scrollingRGB.X),
|
||||
IsNotSecondSlice
|
||||
),
|
||||
IsNotFirstSlice
|
||||
);
|
||||
|
||||
return new Color(color);
|
||||
}
|
||||
|
||||
public static Color FromHSV(int r, int g, int b)
|
||||
{
|
||||
return Color.FromHSV(r / 255f, g / 255f, b / 255f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Static Operators and Override Methods
|
||||
|
@ -1769,10 +1832,10 @@ namespace MoonWorks.Graphics
|
|||
/// <returns><c>True</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator ==(Color a, Color b)
|
||||
{
|
||||
return ( a.A == b.A &&
|
||||
return (a.A == b.A &&
|
||||
a.R == b.R &&
|
||||
a.G == b.G &&
|
||||
a.B == b.B );
|
||||
a.B == b.B);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
internal class CommandBufferPool
|
||||
{
|
||||
private GraphicsDevice GraphicsDevice;
|
||||
private ConcurrentQueue<CommandBuffer> CommandBuffers = new ConcurrentQueue<CommandBuffer>();
|
||||
|
||||
public CommandBufferPool(GraphicsDevice graphicsDevice)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
}
|
||||
|
||||
public CommandBuffer Obtain()
|
||||
{
|
||||
if (CommandBuffers.TryDequeue(out var commandBuffer))
|
||||
{
|
||||
return commandBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new CommandBuffer(GraphicsDevice);
|
||||
}
|
||||
}
|
||||
|
||||
public void Return(CommandBuffer commandBuffer)
|
||||
{
|
||||
commandBuffer.Handle = IntPtr.Zero;
|
||||
CommandBuffers.Enqueue(commandBuffer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
internal class FencePool
|
||||
{
|
||||
private GraphicsDevice GraphicsDevice;
|
||||
private ConcurrentQueue<Fence> Fences = new ConcurrentQueue<Fence>();
|
||||
|
||||
public FencePool(GraphicsDevice graphicsDevice)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
}
|
||||
|
||||
public Fence Obtain()
|
||||
{
|
||||
if (Fences.TryDequeue(out var fence))
|
||||
{
|
||||
return fence;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Fence(GraphicsDevice);
|
||||
}
|
||||
}
|
||||
|
||||
public void Return(Fence fence)
|
||||
{
|
||||
Fences.Enqueue(fence);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public enum HorizontalAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum VerticalAlignment
|
||||
{
|
||||
Baseline,
|
||||
Top,
|
||||
Middle,
|
||||
Bottom
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public unsafe class Font : GraphicsResource
|
||||
{
|
||||
public Texture Texture { get; }
|
||||
public float PixelsPerEm { get; }
|
||||
public float DistanceRange { get; }
|
||||
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
private byte* StringBytes;
|
||||
private int StringBytesLength;
|
||||
|
||||
/// <summary>
|
||||
/// Loads a TTF or OTF font from a path for use in MSDF rendering.
|
||||
/// Note that there must be an msdf-atlas-gen JSON and image file alongside.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public unsafe static Font Load(
|
||||
GraphicsDevice graphicsDevice,
|
||||
CommandBuffer commandBuffer,
|
||||
string fontPath
|
||||
) {
|
||||
var fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read);
|
||||
var fontFileByteBuffer = NativeMemory.Alloc((nuint) fontFileStream.Length);
|
||||
var fontFileByteSpan = new Span<byte>(fontFileByteBuffer, (int) fontFileStream.Length);
|
||||
fontFileStream.ReadExactly(fontFileByteSpan);
|
||||
fontFileStream.Close();
|
||||
|
||||
var atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read);
|
||||
var atlasFileByteBuffer = NativeMemory.Alloc((nuint) atlasFileStream.Length);
|
||||
var atlasFileByteSpan = new Span<byte>(atlasFileByteBuffer, (int) atlasFileStream.Length);
|
||||
atlasFileStream.ReadExactly(atlasFileByteSpan);
|
||||
atlasFileStream.Close();
|
||||
|
||||
var handle = Wellspring.Wellspring_CreateFont(
|
||||
(IntPtr) fontFileByteBuffer,
|
||||
(uint) fontFileByteSpan.Length,
|
||||
(IntPtr) atlasFileByteBuffer,
|
||||
(uint) atlasFileByteSpan.Length,
|
||||
out float pixelsPerEm,
|
||||
out float distanceRange
|
||||
);
|
||||
|
||||
var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png"));
|
||||
|
||||
NativeMemory.Free(fontFileByteBuffer);
|
||||
NativeMemory.Free(atlasFileByteBuffer);
|
||||
|
||||
return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
|
||||
}
|
||||
|
||||
private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
|
||||
{
|
||||
Handle = handle;
|
||||
Texture = texture;
|
||||
PixelsPerEm = pixelsPerEm;
|
||||
DistanceRange = distanceRange;
|
||||
|
||||
StringBytesLength = 32;
|
||||
StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
|
||||
}
|
||||
|
||||
public unsafe bool TextBounds(
|
||||
string text,
|
||||
int pixelSize,
|
||||
HorizontalAlignment horizontalAlignment,
|
||||
VerticalAlignment verticalAlignment,
|
||||
out Wellspring.Rectangle rectangle
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytesLength < byteCount)
|
||||
{
|
||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
|
||||
|
||||
var result = Wellspring.Wellspring_TextBounds(
|
||||
Handle,
|
||||
pixelSize,
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) StringBytes,
|
||||
(uint) byteCount,
|
||||
out rectangle
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
Logger.LogWarn("Could not decode string: " + text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
|
||||
Wellspring.Wellspring_DestroyFont(Handle);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vertex : IVertexType
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector2 TexCoord;
|
||||
public Color Color;
|
||||
|
||||
public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[]
|
||||
{
|
||||
VertexElementFormat.Vector3,
|
||||
VertexElementFormat.Vector2,
|
||||
VertexElementFormat.Color
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
public unsafe class TextBatch : GraphicsResource
|
||||
{
|
||||
public const int INITIAL_CHAR_COUNT = 64;
|
||||
public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4;
|
||||
public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6;
|
||||
|
||||
private GraphicsDevice GraphicsDevice { get; }
|
||||
public IntPtr Handle { get; }
|
||||
|
||||
public Buffer VertexBuffer { get; protected set; } = null;
|
||||
public Buffer IndexBuffer { get; protected set; } = null;
|
||||
public uint PrimitiveCount { get; protected set; }
|
||||
|
||||
public Font CurrentFont { get; private set; }
|
||||
|
||||
private byte* StringBytes;
|
||||
private int StringBytesLength;
|
||||
|
||||
public TextBatch(GraphicsDevice device) : base(device)
|
||||
{
|
||||
GraphicsDevice = device;
|
||||
Handle = Wellspring.Wellspring_CreateTextBatch();
|
||||
|
||||
StringBytesLength = 128;
|
||||
StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength);
|
||||
|
||||
VertexBuffer = Buffer.Create<Vertex>(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
|
||||
IndexBuffer = Buffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
|
||||
}
|
||||
|
||||
// Call this to initialize or reset the batch.
|
||||
public void Start(Font font)
|
||||
{
|
||||
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
|
||||
CurrentFont = font;
|
||||
PrimitiveCount = 0;
|
||||
}
|
||||
|
||||
// Add text with size and color to the batch
|
||||
public unsafe bool Add(
|
||||
string text,
|
||||
int pixelSize,
|
||||
Color color,
|
||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||
) {
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytesLength < byteCount)
|
||||
{
|
||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
{
|
||||
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
|
||||
|
||||
var result = Wellspring.Wellspring_AddToTextBatch(
|
||||
Handle,
|
||||
pixelSize,
|
||||
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
||||
(Wellspring.HorizontalAlignment) horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment) verticalAlignment,
|
||||
(IntPtr) StringBytes,
|
||||
(uint) byteCount
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
Logger.LogWarn("Could not decode string: " + text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call this after you have made all the Add calls you want, but before beginning a render pass.
|
||||
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
|
||||
{
|
||||
Wellspring.Wellspring_GetBufferData(
|
||||
Handle,
|
||||
out uint vertexCount,
|
||||
out IntPtr vertexDataPointer,
|
||||
out uint vertexDataLengthInBytes,
|
||||
out IntPtr indexDataPointer,
|
||||
out uint indexDataLengthInBytes
|
||||
);
|
||||
|
||||
if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
}
|
||||
|
||||
if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||
{
|
||||
IndexBuffer.Dispose();
|
||||
IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
|
||||
}
|
||||
|
||||
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
||||
{
|
||||
commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes);
|
||||
commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes);
|
||||
}
|
||||
|
||||
PrimitiveCount = vertexCount / 2;
|
||||
}
|
||||
|
||||
// Call this AFTER binding your text pipeline!
|
||||
public void Render(CommandBuffer commandBuffer, Math.Float.Matrix4x4 transformMatrix)
|
||||
{
|
||||
commandBuffer.BindFragmentSamplers(new TextureSamplerBinding(
|
||||
CurrentFont.Texture,
|
||||
GraphicsDevice.LinearSampler
|
||||
));
|
||||
commandBuffer.BindVertexBuffers(VertexBuffer);
|
||||
commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
|
||||
commandBuffer.DrawIndexedPrimitives(
|
||||
0,
|
||||
0,
|
||||
PrimitiveCount,
|
||||
commandBuffer.PushVertexShaderUniforms(transformMatrix),
|
||||
commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange)
|
||||
);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
IndexBuffer.Dispose();
|
||||
}
|
||||
|
||||
NativeMemory.Free(StringBytes);
|
||||
Wellspring.Wellspring_DestroyTextBatch(Handle);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,138 +1,484 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using MoonWorks.Video;
|
||||
using RefreshCS;
|
||||
using WellspringCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public class GraphicsDevice : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
/// <summary>
|
||||
/// GraphicsDevice manages all graphics-related concerns.
|
||||
/// </summary>
|
||||
public class GraphicsDevice : IDisposable
|
||||
{
|
||||
public IntPtr Handle { get; }
|
||||
public Backend Backend { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
private uint windowFlags;
|
||||
public SDL2.SDL.SDL_WindowFlags WindowFlags => (SDL2.SDL.SDL_WindowFlags) windowFlags;
|
||||
|
||||
private readonly Queue<CommandBuffer> commandBufferPool;
|
||||
// Built-in video pipeline
|
||||
internal GraphicsPipeline VideoPipeline { get; }
|
||||
|
||||
private readonly List<WeakReference<GraphicsResource>> resources = new List<WeakReference<GraphicsResource>>();
|
||||
// Built-in text shader info
|
||||
public GraphicsShaderInfo TextVertexShaderInfo { get; }
|
||||
public GraphicsShaderInfo TextFragmentShaderInfo { get; }
|
||||
public VertexInputState TextVertexInputState { get; }
|
||||
|
||||
public GraphicsDevice(
|
||||
IntPtr deviceWindowHandle,
|
||||
Refresh.PresentMode presentMode,
|
||||
bool debugMode,
|
||||
int initialCommandBufferPoolSize = 4
|
||||
) {
|
||||
var presentationParameters = new Refresh.PresentationParameters
|
||||
{
|
||||
deviceWindowHandle = deviceWindowHandle,
|
||||
presentMode = presentMode
|
||||
};
|
||||
// Built-in samplers
|
||||
public Sampler PointSampler { get; }
|
||||
public Sampler LinearSampler { get; }
|
||||
|
||||
Handle = Refresh.Refresh_CreateDevice(
|
||||
presentationParameters,
|
||||
Conversions.BoolToByte(debugMode)
|
||||
);
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
commandBufferPool = new Queue<CommandBuffer>(initialCommandBufferPoolSize);
|
||||
for (var i = 0; i < initialCommandBufferPoolSize; i += 1)
|
||||
{
|
||||
commandBufferPool.Enqueue(new CommandBuffer(this));
|
||||
}
|
||||
}
|
||||
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
|
||||
private FencePool FencePool;
|
||||
private CommandBufferPool CommandBufferPool;
|
||||
|
||||
public CommandBuffer AcquireCommandBuffer()
|
||||
{
|
||||
var commandBufferHandle = Refresh.Refresh_AcquireCommandBuffer(Handle, 0);
|
||||
if (commandBufferPool.Count == 0)
|
||||
{
|
||||
commandBufferPool.Enqueue(new CommandBuffer(this));
|
||||
}
|
||||
internal GraphicsDevice(
|
||||
Backend preferredBackend,
|
||||
bool debugMode
|
||||
) {
|
||||
Backend = (Backend) Refresh.Refresh_SelectBackend((Refresh.Backend) preferredBackend, out windowFlags);
|
||||
|
||||
var commandBuffer = commandBufferPool.Dequeue();
|
||||
commandBuffer.Handle = commandBufferHandle;
|
||||
if (Backend == Backend.Invalid)
|
||||
{
|
||||
throw new System.Exception("Could not set graphics backend!");
|
||||
}
|
||||
|
||||
return commandBuffer;
|
||||
}
|
||||
Handle = Refresh.Refresh_CreateDevice(
|
||||
Conversions.BoolToByte(debugMode)
|
||||
);
|
||||
|
||||
public unsafe void Submit(params CommandBuffer[] commandBuffers)
|
||||
{
|
||||
var commandBufferPtrs = stackalloc IntPtr[commandBuffers.Length];
|
||||
// TODO: check for CreateDevice fail
|
||||
|
||||
for (var i = 0; i < commandBuffers.Length; i += 1)
|
||||
{
|
||||
commandBufferPtrs[i] = commandBuffers[i].Handle;
|
||||
}
|
||||
// Check for replacement stock shaders
|
||||
string basePath = System.AppContext.BaseDirectory;
|
||||
|
||||
Refresh.Refresh_Submit(
|
||||
Handle,
|
||||
(uint) commandBuffers.Length,
|
||||
(IntPtr) commandBufferPtrs
|
||||
);
|
||||
string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh");
|
||||
string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh");
|
||||
|
||||
// return to pool
|
||||
for (var i = 0; i < commandBuffers.Length; i += 1)
|
||||
{
|
||||
commandBuffers[i].Handle = IntPtr.Zero;
|
||||
commandBufferPool.Enqueue(commandBuffers[i]);
|
||||
}
|
||||
}
|
||||
string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh");
|
||||
string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh");
|
||||
|
||||
public void Wait()
|
||||
{
|
||||
Refresh.Refresh_Wait(Handle);
|
||||
}
|
||||
ShaderModule videoVertShader;
|
||||
ShaderModule videoFragShader;
|
||||
|
||||
internal void AddResourceReference(WeakReference<GraphicsResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Add(resourceReference);
|
||||
}
|
||||
}
|
||||
ShaderModule textVertShader;
|
||||
ShaderModule textFragShader;
|
||||
|
||||
internal void RemoveResourceReference(WeakReference<GraphicsResource> resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Remove(resourceReference);
|
||||
}
|
||||
}
|
||||
if (File.Exists(videoVertPath) && File.Exists(videoFragPath))
|
||||
{
|
||||
videoVertShader = new ShaderModule(this, videoVertPath);
|
||||
videoFragShader = new ShaderModule(this, videoFragPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use defaults
|
||||
var assembly = typeof(GraphicsDevice).Assembly;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.TryGetTarget(out var target))
|
||||
{
|
||||
target.Dispose();
|
||||
}
|
||||
}
|
||||
resources.Clear();
|
||||
}
|
||||
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh");
|
||||
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh");
|
||||
|
||||
Refresh.Refresh_DestroyDevice(Handle);
|
||||
}
|
||||
videoVertShader = new ShaderModule(this, vertStream);
|
||||
videoFragShader = new ShaderModule(this, fragStream);
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
if (File.Exists(textVertPath) && File.Exists(textFragPath))
|
||||
{
|
||||
textVertShader = new ShaderModule(this, textVertPath);
|
||||
textFragShader = new ShaderModule(this, textFragPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use defaults
|
||||
var assembly = typeof(GraphicsDevice).Assembly;
|
||||
|
||||
// TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
~GraphicsDevice()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh");
|
||||
using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
textVertShader = new ShaderModule(this, vertStream);
|
||||
textFragShader = new ShaderModule(this, fragStream);
|
||||
}
|
||||
|
||||
VideoPipeline = new GraphicsPipeline(
|
||||
this,
|
||||
new GraphicsPipelineCreateInfo
|
||||
{
|
||||
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
||||
new ColorAttachmentDescription(
|
||||
TextureFormat.R8G8B8A8,
|
||||
ColorAttachmentBlendState.None
|
||||
)
|
||||
),
|
||||
DepthStencilState = DepthStencilState.Disable,
|
||||
VertexShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoVertShader,
|
||||
"main",
|
||||
0
|
||||
),
|
||||
FragmentShaderInfo = GraphicsShaderInfo.Create(
|
||||
videoFragShader,
|
||||
"main",
|
||||
3
|
||||
),
|
||||
VertexInputState = VertexInputState.Empty,
|
||||
RasterizerState = RasterizerState.CCW_CullNone,
|
||||
PrimitiveType = PrimitiveType.TriangleList,
|
||||
MultisampleState = MultisampleState.None
|
||||
}
|
||||
);
|
||||
|
||||
TextVertexShaderInfo = GraphicsShaderInfo.Create<Math.Float.Matrix4x4>(textVertShader, "main", 0);
|
||||
TextFragmentShaderInfo = GraphicsShaderInfo.Create<float>(textFragShader, "main", 1);
|
||||
TextVertexInputState = VertexInputState.CreateSingleBinding<Font.Vertex>();
|
||||
|
||||
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
|
||||
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
|
||||
|
||||
FencePool = new FencePool(this);
|
||||
CommandBufferPool = new CommandBufferPool(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a window so that frames can be presented to it.
|
||||
/// </summary>
|
||||
/// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param>
|
||||
/// <returns>True if successfully claimed.</returns>
|
||||
public bool ClaimWindow(Window window, PresentMode presentMode)
|
||||
{
|
||||
if (window.Claimed)
|
||||
{
|
||||
Logger.LogError("Window already claimed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var success = Conversions.ByteToBool(
|
||||
Refresh.Refresh_ClaimWindow(
|
||||
Handle,
|
||||
window.Handle,
|
||||
(Refresh.PresentMode) presentMode
|
||||
)
|
||||
);
|
||||
|
||||
if (success)
|
||||
{
|
||||
window.Claimed = true;
|
||||
window.SwapchainFormat = GetSwapchainFormat(window);
|
||||
if (window.SwapchainTexture == null)
|
||||
{
|
||||
window.SwapchainTexture = new Texture(this, window.SwapchainFormat);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unclaims a window, making it unavailable for presenting and freeing associated resources.
|
||||
/// </summary>
|
||||
public void UnclaimWindow(Window window)
|
||||
{
|
||||
if (window.Claimed)
|
||||
{
|
||||
Refresh.Refresh_UnclaimWindow(
|
||||
Handle,
|
||||
window.Handle
|
||||
);
|
||||
window.Claimed = false;
|
||||
|
||||
// The swapchain texture doesn't actually have a permanent texture reference, so we zero the handle before disposing.
|
||||
window.SwapchainTexture.Handle = IntPtr.Zero;
|
||||
window.SwapchainTexture.Dispose();
|
||||
window.SwapchainTexture = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the present mode of a claimed window. Does nothing if the window is not claimed.
|
||||
/// </summary>
|
||||
/// <param name="window"></param>
|
||||
/// <param name="presentMode"></param>
|
||||
public void SetPresentMode(Window window, PresentMode presentMode)
|
||||
{
|
||||
if (!window.Claimed)
|
||||
{
|
||||
Logger.LogError("Cannot set present mode on unclaimed window!");
|
||||
return;
|
||||
}
|
||||
|
||||
Refresh.Refresh_SetSwapchainPresentMode(
|
||||
Handle,
|
||||
window.Handle,
|
||||
(Refresh.PresentMode) presentMode
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a command buffer.
|
||||
/// This is the start of your rendering process.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CommandBuffer AcquireCommandBuffer()
|
||||
{
|
||||
var commandBuffer = CommandBufferPool.Obtain();
|
||||
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
|
||||
#if DEBUG
|
||||
commandBuffer.ResetStateTracking();
|
||||
#endif
|
||||
return commandBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits a command buffer to the GPU for processing.
|
||||
/// </summary>
|
||||
public void Submit(CommandBuffer commandBuffer)
|
||||
{
|
||||
#if DEBUG
|
||||
if (commandBuffer.Submitted)
|
||||
{
|
||||
throw new System.InvalidOperationException("Command buffer already submitted!");
|
||||
}
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_Submit(
|
||||
Handle,
|
||||
commandBuffer.Handle
|
||||
);
|
||||
|
||||
CommandBufferPool.Return(commandBuffer);
|
||||
|
||||
#if DEBUG
|
||||
commandBuffer.Submitted = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
|
||||
{
|
||||
var fenceHandle = Refresh.Refresh_SubmitAndAcquireFence(
|
||||
Handle,
|
||||
commandBuffer.Handle
|
||||
);
|
||||
|
||||
var fence = FencePool.Obtain();
|
||||
fence.SetHandle(fenceHandle);
|
||||
|
||||
return fence;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for the graphics device to become idle.
|
||||
/// </summary>
|
||||
public void Wait()
|
||||
{
|
||||
Refresh.Refresh_Wait(Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the given fence to become signaled.
|
||||
/// </summary>
|
||||
public unsafe void WaitForFences(Fence fence)
|
||||
{
|
||||
var handlePtr = stackalloc nint[1];
|
||||
handlePtr[0] = fence.Handle;
|
||||
|
||||
Refresh.Refresh_WaitForFences(
|
||||
Handle,
|
||||
1,
|
||||
1,
|
||||
(nint) handlePtr
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for one or more fences to become signaled.
|
||||
/// </summary>
|
||||
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
|
||||
public unsafe void WaitForFences(
|
||||
Fence fenceOne,
|
||||
Fence fenceTwo,
|
||||
bool waitAll
|
||||
) {
|
||||
var handlePtr = stackalloc nint[2];
|
||||
handlePtr[0] = fenceOne.Handle;
|
||||
handlePtr[1] = fenceTwo.Handle;
|
||||
|
||||
Refresh.Refresh_WaitForFences(
|
||||
Handle,
|
||||
Conversions.BoolToByte(waitAll),
|
||||
2,
|
||||
(nint) handlePtr
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for one or more fences to become signaled.
|
||||
/// </summary>
|
||||
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
|
||||
public unsafe void WaitForFences(
|
||||
Fence fenceOne,
|
||||
Fence fenceTwo,
|
||||
Fence fenceThree,
|
||||
bool waitAll
|
||||
) {
|
||||
var handlePtr = stackalloc nint[3];
|
||||
handlePtr[0] = fenceOne.Handle;
|
||||
handlePtr[1] = fenceTwo.Handle;
|
||||
handlePtr[2] = fenceThree.Handle;
|
||||
|
||||
Refresh.Refresh_WaitForFences(
|
||||
Handle,
|
||||
Conversions.BoolToByte(waitAll),
|
||||
3,
|
||||
(nint) handlePtr
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for one or more fences to become signaled.
|
||||
/// </summary>
|
||||
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
|
||||
public unsafe void WaitForFences(
|
||||
Fence fenceOne,
|
||||
Fence fenceTwo,
|
||||
Fence fenceThree,
|
||||
Fence fenceFour,
|
||||
bool waitAll
|
||||
) {
|
||||
var handlePtr = stackalloc nint[4];
|
||||
handlePtr[0] = fenceOne.Handle;
|
||||
handlePtr[1] = fenceTwo.Handle;
|
||||
handlePtr[2] = fenceThree.Handle;
|
||||
handlePtr[3] = fenceFour.Handle;
|
||||
|
||||
Refresh.Refresh_WaitForFences(
|
||||
Handle,
|
||||
Conversions.BoolToByte(waitAll),
|
||||
4,
|
||||
(nint) handlePtr
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for one or more fences to become signaled.
|
||||
/// </summary>
|
||||
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
|
||||
public unsafe void WaitForFences(Fence[] fences, bool waitAll)
|
||||
{
|
||||
var handlePtr = stackalloc nint[fences.Length];
|
||||
|
||||
for (var i = 0; i < fences.Length; i += 1)
|
||||
{
|
||||
handlePtr[i] = fences[i].Handle;
|
||||
}
|
||||
|
||||
Refresh.Refresh_WaitForFences(
|
||||
Handle,
|
||||
Conversions.BoolToByte(waitAll),
|
||||
4,
|
||||
(nint) handlePtr
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
|
||||
public bool QueryFence(Fence fence)
|
||||
{
|
||||
var result = Refresh.Refresh_QueryFence(Handle, fence.Handle);
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
throw new InvalidOperationException("The graphics device has been lost.");
|
||||
}
|
||||
|
||||
return result != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release reference to an acquired fence, enabling it to be reused.
|
||||
/// </summary>
|
||||
public void ReleaseFence(Fence fence)
|
||||
{
|
||||
Refresh.Refresh_ReleaseFence(Handle, fence.Handle);
|
||||
fence.Handle = IntPtr.Zero;
|
||||
FencePool.Return(fence);
|
||||
}
|
||||
|
||||
private TextureFormat GetSwapchainFormat(Window window)
|
||||
{
|
||||
return (TextureFormat) Refresh.Refresh_GetSwapchainFormat(Handle, window.Handle);
|
||||
}
|
||||
|
||||
internal void AddResourceReference(GCHandle resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Add(resourceReference);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveResourceReference(GCHandle resourceReference)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
resources.Remove(resourceReference);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
lock (resources)
|
||||
{
|
||||
// Dispose video players first to avoid race condition on threaded decoding
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Target is VideoPlayer player)
|
||||
{
|
||||
player.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Dispose everything else
|
||||
foreach (var resource in resources)
|
||||
{
|
||||
if (resource.Target is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
resources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
Refresh.Refresh_DestroyDevice(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~GraphicsDevice()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,55 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public abstract class GraphicsResource : IDisposable
|
||||
{
|
||||
public GraphicsDevice Device { get; }
|
||||
public IntPtr Handle { get; protected set; }
|
||||
// TODO: give this a Name property for debugging use
|
||||
public abstract class GraphicsResource : IDisposable
|
||||
{
|
||||
public GraphicsDevice Device { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
private GCHandle SelfReference;
|
||||
|
||||
private WeakReference<GraphicsResource> selfReference;
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public GraphicsResource(GraphicsDevice device)
|
||||
{
|
||||
Device = device;
|
||||
protected GraphicsResource(GraphicsDevice device)
|
||||
{
|
||||
Device = device;
|
||||
|
||||
selfReference = new WeakReference<GraphicsResource>(this);
|
||||
Device.AddResourceReference(selfReference);
|
||||
}
|
||||
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
|
||||
Device.AddResourceReference(SelfReference);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
QueueDestroyFunction(Device.Handle, Handle);
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Device.RemoveResourceReference(SelfReference);
|
||||
SelfReference.Free();
|
||||
}
|
||||
|
||||
if (selfReference != null)
|
||||
{
|
||||
Device.RemoveResourceReference(selfReference);
|
||||
selfReference = null;
|
||||
}
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
~GraphicsResource()
|
||||
{
|
||||
#if DEBUG
|
||||
// If you see this log message, you leaked a graphics resource without disposing it!
|
||||
// We'll try to clean it up for you but you really should fix this.
|
||||
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
|
||||
#endif
|
||||
|
||||
~GraphicsResource()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be defined on your struct type to enable simplified vertex input state definition.
|
||||
/// </summary>
|
||||
public interface IVertexType
|
||||
{
|
||||
/// <summary>
|
||||
/// An ordered list of the types in your vertex struct.
|
||||
/// </summary>
|
||||
static abstract VertexElementFormat[] Formats { get; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values, ranging from 0 to 1, using
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing four 8-bit unsigned integer values, ranging from 0 to 255.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -16,10 +16,10 @@
|
|||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct HalfSingle : IPackedVector<ushort>, IEquatable<HalfSingle>, IPackedVector
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -19,7 +19,7 @@ using System;
|
|||
using System.Runtime.InteropServices;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
internal static class HalfTypeHelper
|
||||
{
|
||||
|
@ -104,7 +104,7 @@ namespace MoonWorks.Graphics
|
|||
internal static float Convert(ushort value)
|
||||
{
|
||||
uint rst;
|
||||
uint mantissa = (uint)(value & 1023);
|
||||
uint mantissa = (uint) (value & 1023);
|
||||
uint exp = 0xfffffff2;
|
||||
|
||||
if ((value & -33792) == 0)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -16,10 +16,10 @@
|
|||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct HalfVector2 : IPackedVector<uint>, IPackedVector, IEquatable<HalfVector2>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -16,10 +16,10 @@
|
|||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing four 16-bit floating-point values.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -14,9 +14,9 @@
|
|||
|
||||
#endregion
|
||||
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.packedvector.ipackedvector.aspx
|
||||
public interface IPackedVector
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct NormalizedByte2 : IPackedVector<ushort>, IEquatable<NormalizedByte2>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct NormalizedByte4 : IPackedVector<uint>, IEquatable<NormalizedByte4>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct NormalizedShort2 : IPackedVector<uint>, IEquatable<NormalizedShort2>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct NormalizedShort4 : IPackedVector<ulong>, IEquatable<NormalizedShort4>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
public struct Short2 : IPackedVector<uint>, IEquatable<Short2>
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -17,9 +17,10 @@
|
|||
#region Using Statements
|
||||
using System;
|
||||
using MoonWorks.Math;
|
||||
using MoonWorks.Math.Float;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics.PackedVector
|
||||
{
|
||||
/// <summary>
|
||||
/// Packed vector type containing four 16-bit signed integer values.
|
||||
|
@ -192,7 +193,7 @@ namespace MoonWorks.Graphics
|
|||
static ulong Pack(float x, float y, float z, float w)
|
||||
{
|
||||
return (ulong) (
|
||||
((long) System.Math.Round(MathHelper.Clamp(x, -32768, 32767)) & 0xFFFF ) |
|
||||
((long) System.Math.Round(MathHelper.Clamp(x, -32768, 32767)) & 0xFFFF) |
|
||||
(((long) System.Math.Round(MathHelper.Clamp(y, -32768, 32767)) << 16) & 0xFFFF0000) |
|
||||
(((long) System.Math.Round(MathHelper.Clamp(z, -32768, 32767)) << 32) & 0xFFFF00000000) |
|
||||
((long) System.Math.Round(MathHelper.Clamp(w, -32768, 32767)) << 48)
|
||||
|
|
|
@ -1,302 +1,307 @@
|
|||
using System;
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
/// <summary>
|
||||
/// Presentation mode for a window.
|
||||
/// </summary>
|
||||
public enum PresentMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Does not wait for v-blank to update the window. Can cause visible tearing.
|
||||
/// </summary>
|
||||
Immediate,
|
||||
/// <summary>
|
||||
/// Waits for v-blank and uses a queue to hold present requests.
|
||||
/// Allows for low latency while preventing tearing.
|
||||
/// May not be supported on non-Vulkan non-Linux systems or older hardware.
|
||||
/// </summary>
|
||||
Mailbox,
|
||||
/// <summary>
|
||||
/// Waits for v-blank and adds present requests to a queue.
|
||||
/// Will probably cause latency.
|
||||
/// Required to be supported by all compliant hardware.
|
||||
/// </summary>
|
||||
FIFO,
|
||||
/// <summary>
|
||||
/// Usually waits for v-blank, but if v-blank has passed since last update will update immediately.
|
||||
/// May cause visible tearing.
|
||||
/// </summary>
|
||||
FIFORelaxed
|
||||
}
|
||||
}
|
||||
|
||||
/* Recreate all the enums in here so we don't need to explicitly
|
||||
* reference the RefreshCS namespace when using MoonWorks.Graphics
|
||||
*/
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public enum PresentMode
|
||||
{
|
||||
Immediate,
|
||||
Mailbox,
|
||||
FIFO,
|
||||
FIFORelaxed
|
||||
}
|
||||
public enum PrimitiveType
|
||||
{
|
||||
PointList,
|
||||
LineList,
|
||||
LineStrip,
|
||||
TriangleList,
|
||||
TriangleStrip
|
||||
}
|
||||
|
||||
public enum PrimitiveType
|
||||
{
|
||||
PointList,
|
||||
LineList,
|
||||
LineStrip,
|
||||
TriangleList,
|
||||
TriangleStrip
|
||||
}
|
||||
/// <summary>
|
||||
/// Describes the operation that a render pass will use when loading a render target.
|
||||
/// </summary>
|
||||
public enum LoadOp
|
||||
{
|
||||
Load,
|
||||
Clear,
|
||||
DontCare
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the operation that a render pass will use when loading a render target.
|
||||
/// </summary>
|
||||
public enum LoadOp
|
||||
{
|
||||
Load,
|
||||
Clear,
|
||||
DontCare
|
||||
}
|
||||
/// <summary>
|
||||
/// Describes the operation that a render pass will use when storing a render target.
|
||||
/// </summary>
|
||||
public enum StoreOp
|
||||
{
|
||||
Store,
|
||||
DontCare
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the operation that a render pass will use when storing a render target.
|
||||
/// </summary>
|
||||
public enum StoreOp
|
||||
{
|
||||
Store,
|
||||
DontCare
|
||||
}
|
||||
[Flags]
|
||||
public enum ClearOptionsFlags : uint
|
||||
{
|
||||
Color = 1,
|
||||
Depth = 2,
|
||||
Stencil = 4,
|
||||
DepthStencil = Depth | Stencil,
|
||||
All = Color | Depth | Stencil
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ClearOptionsFlags : uint
|
||||
{
|
||||
Color = 1,
|
||||
Depth = 2,
|
||||
Stencil = 4,
|
||||
DepthStencil = Depth | Stencil,
|
||||
All = Color | Depth | Stencil
|
||||
}
|
||||
public enum IndexElementSize
|
||||
{
|
||||
Sixteen,
|
||||
ThirtyTwo
|
||||
}
|
||||
|
||||
public enum IndexElementSize
|
||||
{
|
||||
Sixteen,
|
||||
ThirtyTwo
|
||||
}
|
||||
public enum TextureFormat
|
||||
{
|
||||
R8G8B8A8,
|
||||
B8G8R8A8,
|
||||
R5G6B5,
|
||||
A1R5G5B5,
|
||||
B4G4R4A4,
|
||||
A2R10G10B10,
|
||||
R16G16,
|
||||
R16G16B16A16,
|
||||
R8,
|
||||
BC1,
|
||||
BC2,
|
||||
BC3,
|
||||
BC7,
|
||||
R8G8_SNORM,
|
||||
R8G8B8A8_SNORM,
|
||||
R16_SFLOAT,
|
||||
R16G16_SFLOAT,
|
||||
R16G16B16A16_SFLOAT,
|
||||
R32_SFLOAT,
|
||||
R32G32_SFLOAT,
|
||||
R32G32B32A32_SFLOAT,
|
||||
|
||||
public enum TextureFormat
|
||||
{
|
||||
R8G8B8A8,
|
||||
R5G6B5,
|
||||
A1R5G5B5,
|
||||
B4G4R4A4,
|
||||
BC1,
|
||||
BC2,
|
||||
BC3,
|
||||
R8G8_SNORM,
|
||||
R8G8B8A8_SNORM,
|
||||
A2R10G10B10,
|
||||
R16G16,
|
||||
R16G16B16A16,
|
||||
R8,
|
||||
R32_SFLOAT,
|
||||
R32G32_SFLOAT,
|
||||
R32G32B32A32_SFLOAT,
|
||||
R16_SFLOAT,
|
||||
R16G16_SFLOAT,
|
||||
R16G16B16A16_SFLOAT,
|
||||
D16,
|
||||
D32,
|
||||
D16S8,
|
||||
D32S8
|
||||
}
|
||||
R8_UINT,
|
||||
R8G8_UINT,
|
||||
R8G8B8A8_UINT,
|
||||
R16_UINT,
|
||||
R16G16_UINT,
|
||||
R16G16B16A16_UINT,
|
||||
D16,
|
||||
D32,
|
||||
D16S8,
|
||||
D32S8
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum TextureUsageFlags : uint
|
||||
{
|
||||
Sampler = 1,
|
||||
ColorTarget = 2,
|
||||
DepthStencilTarget = 4
|
||||
}
|
||||
[Flags]
|
||||
public enum TextureUsageFlags : uint
|
||||
{
|
||||
Sampler = 1,
|
||||
ColorTarget = 2,
|
||||
DepthStencilTarget = 4,
|
||||
Compute = 8
|
||||
}
|
||||
|
||||
public enum SampleCount
|
||||
{
|
||||
One,
|
||||
Two,
|
||||
Four,
|
||||
Eight,
|
||||
Sixteen,
|
||||
ThirtyTwo,
|
||||
SixtyFour
|
||||
}
|
||||
public enum SampleCount
|
||||
{
|
||||
One,
|
||||
Two,
|
||||
Four,
|
||||
Eight
|
||||
}
|
||||
|
||||
public enum CubeMapFace : uint
|
||||
{
|
||||
PositiveX,
|
||||
NegativeX,
|
||||
PositiveY,
|
||||
NegativeY,
|
||||
PositiveZ,
|
||||
NegativeZ
|
||||
}
|
||||
public enum CubeMapFace : uint
|
||||
{
|
||||
PositiveX,
|
||||
NegativeX,
|
||||
PositiveY,
|
||||
NegativeY,
|
||||
PositiveZ,
|
||||
NegativeZ
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BufferUsageFlags : uint
|
||||
{
|
||||
Vertex = 1,
|
||||
Index = 2,
|
||||
Compute = 4
|
||||
}
|
||||
[Flags]
|
||||
public enum BufferUsageFlags : uint
|
||||
{
|
||||
Vertex = 1,
|
||||
Index = 2,
|
||||
Compute = 4,
|
||||
Indirect = 8
|
||||
}
|
||||
|
||||
public enum VertexElementFormat
|
||||
{
|
||||
Single,
|
||||
Vector2,
|
||||
Vector3,
|
||||
Vector4,
|
||||
Color,
|
||||
Byte4,
|
||||
Short2,
|
||||
Short4,
|
||||
NormalizedShort2,
|
||||
NormalizedShort4,
|
||||
HalfVector2,
|
||||
HalfVector4
|
||||
}
|
||||
public enum VertexElementFormat
|
||||
{
|
||||
UInt,
|
||||
Float,
|
||||
Vector2,
|
||||
Vector3,
|
||||
Vector4,
|
||||
Color,
|
||||
Byte4,
|
||||
Short2,
|
||||
Short4,
|
||||
NormalizedShort2,
|
||||
NormalizedShort4,
|
||||
HalfVector2,
|
||||
HalfVector4
|
||||
}
|
||||
|
||||
public enum VertexInputRate
|
||||
{
|
||||
Vertex,
|
||||
Instance
|
||||
}
|
||||
public enum VertexInputRate
|
||||
{
|
||||
Vertex,
|
||||
Instance
|
||||
}
|
||||
|
||||
public enum FillMode
|
||||
{
|
||||
Fill,
|
||||
Line,
|
||||
Point
|
||||
}
|
||||
public enum FillMode
|
||||
{
|
||||
Fill,
|
||||
Line
|
||||
}
|
||||
|
||||
public enum CullMode
|
||||
{
|
||||
None,
|
||||
Front,
|
||||
Back,
|
||||
FrontAndBack
|
||||
}
|
||||
public enum CullMode
|
||||
{
|
||||
None,
|
||||
Front,
|
||||
Back
|
||||
}
|
||||
|
||||
public enum FrontFace
|
||||
{
|
||||
CounterClockwise,
|
||||
Clockwise
|
||||
}
|
||||
public enum FrontFace
|
||||
{
|
||||
CounterClockwise,
|
||||
Clockwise
|
||||
}
|
||||
|
||||
public enum CompareOp
|
||||
{
|
||||
Never,
|
||||
Less,
|
||||
Equal,
|
||||
LessOrEqual,
|
||||
Greater,
|
||||
NotEqual,
|
||||
GreaterOrEqual,
|
||||
Always
|
||||
}
|
||||
public enum CompareOp
|
||||
{
|
||||
Never,
|
||||
Less,
|
||||
Equal,
|
||||
LessOrEqual,
|
||||
Greater,
|
||||
NotEqual,
|
||||
GreaterOrEqual,
|
||||
Always
|
||||
}
|
||||
|
||||
public enum StencilOp
|
||||
{
|
||||
Keep,
|
||||
Zero,
|
||||
Replace,
|
||||
IncrementAndClamp,
|
||||
DecrementAndClamp,
|
||||
Invert,
|
||||
IncrementAndWrap,
|
||||
DecrementAndWrap
|
||||
}
|
||||
public enum StencilOp
|
||||
{
|
||||
Keep,
|
||||
Zero,
|
||||
Replace,
|
||||
IncrementAndClamp,
|
||||
DecrementAndClamp,
|
||||
Invert,
|
||||
IncrementAndWrap,
|
||||
DecrementAndWrap
|
||||
}
|
||||
|
||||
public enum BlendOp
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
ReverseSubtract,
|
||||
Min,
|
||||
Max
|
||||
}
|
||||
public enum BlendOp
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
ReverseSubtract,
|
||||
Min,
|
||||
Max
|
||||
}
|
||||
|
||||
public enum LogicOp
|
||||
{
|
||||
Clear,
|
||||
And,
|
||||
AndReverse,
|
||||
Copy,
|
||||
AndInverted,
|
||||
NoOp,
|
||||
Xor,
|
||||
Or,
|
||||
Nor,
|
||||
Equivalent,
|
||||
Invert,
|
||||
OrReverse,
|
||||
CopyInverted,
|
||||
OrInverted,
|
||||
Nand,
|
||||
Set
|
||||
}
|
||||
public enum BlendFactor
|
||||
{
|
||||
Zero,
|
||||
One,
|
||||
SourceColor,
|
||||
OneMinusSourceColor,
|
||||
DestinationColor,
|
||||
OneMinusDestinationColor,
|
||||
SourceAlpha,
|
||||
OneMinusSourceAlpha,
|
||||
DestinationAlpha,
|
||||
OneMinusDestinationAlpha,
|
||||
ConstantColor,
|
||||
OneMinusConstantColor,
|
||||
SourceAlphaSaturate
|
||||
}
|
||||
|
||||
public enum BlendFactor
|
||||
{
|
||||
Zero,
|
||||
One,
|
||||
SourceColor,
|
||||
OneMinusSourceColor,
|
||||
DestinationColor,
|
||||
OneMinusDestinationColor,
|
||||
SourceAlpha,
|
||||
OneMinusSourceAlpha,
|
||||
DestinationAlpha,
|
||||
OneMinusDestinationAlpha,
|
||||
ConstantColor,
|
||||
OneMinusConstantColor,
|
||||
ConstantAlpha,
|
||||
OneMinusConstantAlpha,
|
||||
SourceAlphaSaturate,
|
||||
SourceOneColor,
|
||||
OneMinusSourceOneColor,
|
||||
SourceOneAlpha,
|
||||
OneMinusSourceOneAlpha
|
||||
}
|
||||
[Flags]
|
||||
public enum ColorComponentFlags : uint
|
||||
{
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 4,
|
||||
A = 8,
|
||||
|
||||
[Flags]
|
||||
public enum ColorComponentFlags : uint
|
||||
{
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 4,
|
||||
A = 8,
|
||||
RG = R | G,
|
||||
RB = R | B,
|
||||
RA = R | A,
|
||||
GB = G | B,
|
||||
GA = G | A,
|
||||
BA = B | A,
|
||||
|
||||
RG = R | G,
|
||||
RB = R | B,
|
||||
RA = R | A,
|
||||
GB = G | B,
|
||||
GA = G | A,
|
||||
BA = B | A,
|
||||
RGB = R | G | B,
|
||||
RGA = R | G | A,
|
||||
GBA = G | B | A,
|
||||
|
||||
RGB = R | G | B,
|
||||
RGA = R | G | A,
|
||||
GBA = G | B | A,
|
||||
RGBA = R | G | B | A,
|
||||
None = 0
|
||||
}
|
||||
|
||||
RGBA = R | G | B | A,
|
||||
None = 0
|
||||
}
|
||||
public enum Filter
|
||||
{
|
||||
Nearest,
|
||||
Linear
|
||||
}
|
||||
|
||||
public enum ShaderStageType
|
||||
{
|
||||
Vertex,
|
||||
Fragment
|
||||
}
|
||||
public enum SamplerMipmapMode
|
||||
{
|
||||
Nearest,
|
||||
Linear
|
||||
}
|
||||
|
||||
public enum Filter
|
||||
{
|
||||
Nearest,
|
||||
Linear,
|
||||
Cubic
|
||||
}
|
||||
public enum SamplerAddressMode
|
||||
{
|
||||
Repeat,
|
||||
MirroredRepeat,
|
||||
ClampToEdge,
|
||||
ClampToBorder
|
||||
}
|
||||
|
||||
public enum SamplerMipmapMode
|
||||
{
|
||||
Nearest,
|
||||
Linear
|
||||
}
|
||||
public enum BorderColor
|
||||
{
|
||||
FloatTransparentBlack,
|
||||
IntTransparentBlack,
|
||||
FloatOpaqueBlack,
|
||||
IntOpaqueBlack,
|
||||
FloatOpaqueWhite,
|
||||
IntOpaqueWhite
|
||||
}
|
||||
|
||||
public enum SamplerAddressMode
|
||||
{
|
||||
Repeat,
|
||||
MirroredRepeat,
|
||||
ClampToEdge,
|
||||
ClampToBorder
|
||||
}
|
||||
|
||||
public enum BorderColor
|
||||
{
|
||||
FloatTransparentBlack,
|
||||
IntTransparentBlack,
|
||||
FloatOpaqueBlack,
|
||||
IntOpaqueBlack,
|
||||
FloatOpaqueWhite,
|
||||
IntOpaqueWhite
|
||||
}
|
||||
public enum Backend
|
||||
{
|
||||
DontCare,
|
||||
Vulkan,
|
||||
PS5,
|
||||
Invalid
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace MoonWorks.Graphics;
|
||||
|
||||
public abstract class RefreshResource : GraphicsResource
|
||||
{
|
||||
public IntPtr Handle { get => handle; internal set => handle = value; }
|
||||
private IntPtr handle;
|
||||
|
||||
protected abstract Action<IntPtr, IntPtr> QueueDestroyFunction { get; }
|
||||
|
||||
protected RefreshResource(GraphicsDevice device) : base(device)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
// Atomically call destroy function in case this is called from the finalizer thread
|
||||
var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
|
||||
if (toDispose != IntPtr.Zero)
|
||||
{
|
||||
QueueDestroyFunction(Device.Handle, toDispose);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
|
@ -6,115 +6,352 @@ using System.Runtime.InteropServices;
|
|||
*/
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DepthStencilValue
|
||||
{
|
||||
public float Depth;
|
||||
public uint Stencil;
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DepthStencilValue
|
||||
{
|
||||
public float Depth;
|
||||
public uint Stencil;
|
||||
|
||||
// FIXME: can we do an unsafe cast somehow?
|
||||
public Refresh.DepthStencilValue ToRefresh()
|
||||
{
|
||||
return new Refresh.DepthStencilValue
|
||||
{
|
||||
depth = Depth,
|
||||
stencil = Stencil
|
||||
};
|
||||
}
|
||||
}
|
||||
public DepthStencilValue(float depth, uint stencil)
|
||||
{
|
||||
Depth = depth;
|
||||
Stencil = stencil;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Rect
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int W;
|
||||
public int H;
|
||||
// FIXME: can we do an unsafe cast somehow?
|
||||
public Refresh.DepthStencilValue ToRefresh()
|
||||
{
|
||||
return new Refresh.DepthStencilValue
|
||||
{
|
||||
depth = Depth,
|
||||
stencil = Stencil
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: can we do an unsafe cast somehow?
|
||||
public Refresh.Rect ToRefresh()
|
||||
{
|
||||
return new Refresh.Rect
|
||||
{
|
||||
x = X,
|
||||
y = Y,
|
||||
w = W,
|
||||
h = H
|
||||
};
|
||||
}
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Rect
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int W;
|
||||
public int H;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Viewport
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
||||
public float W;
|
||||
public float H;
|
||||
public float MinDepth;
|
||||
public float MaxDepth;
|
||||
}
|
||||
public Rect(int x, int y, int w, int h)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
W = w;
|
||||
H = h;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VertexBinding
|
||||
{
|
||||
public uint Binding;
|
||||
public uint Stride;
|
||||
public VertexInputRate InputRate;
|
||||
}
|
||||
public Rect(int w, int h)
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
W = w;
|
||||
H = h;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VertexAttribute
|
||||
{
|
||||
public uint Location;
|
||||
public uint Binding;
|
||||
public VertexElementFormat Format;
|
||||
public uint Offset;
|
||||
}
|
||||
// FIXME: can we do an unsafe cast somehow?
|
||||
public Refresh.Rect ToRefresh()
|
||||
{
|
||||
return new Refresh.Rect
|
||||
{
|
||||
x = X,
|
||||
y = Y,
|
||||
w = W,
|
||||
h = H
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ColorTargetDescription
|
||||
{
|
||||
public TextureFormat Format;
|
||||
public SampleCount MultisampleCount;
|
||||
public LoadOp LoadOp;
|
||||
public StoreOp StoreOp;
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Viewport
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
||||
public float W;
|
||||
public float H;
|
||||
public float MinDepth;
|
||||
public float MaxDepth;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DepthStencilTargetDescription
|
||||
{
|
||||
public TextureFormat Format;
|
||||
public LoadOp LoadOp;
|
||||
public StoreOp StoreOp;
|
||||
public LoadOp StencilLoadOp;
|
||||
public StoreOp StencilStoreOp;
|
||||
}
|
||||
public Viewport(float w, float h)
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
W = w;
|
||||
H = h;
|
||||
MinDepth = 0;
|
||||
MaxDepth = 1;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StencilOpState
|
||||
{
|
||||
public StencilOp FailOp;
|
||||
public StencilOp PassOp;
|
||||
public StencilOp DepthFailOp;
|
||||
public CompareOp CompareOp;
|
||||
public uint CompareMask;
|
||||
public uint WriteMask;
|
||||
public uint Reference;
|
||||
public Viewport(float x, float y, float w, float h)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
W = w;
|
||||
H = h;
|
||||
MinDepth = 0;
|
||||
MaxDepth = 1;
|
||||
}
|
||||
|
||||
// FIXME: can we do an explicit cast here?
|
||||
public Refresh.StencilOpState ToRefresh()
|
||||
{
|
||||
return new Refresh.StencilOpState
|
||||
{
|
||||
failOp = (Refresh.StencilOp)FailOp,
|
||||
passOp = (Refresh.StencilOp)PassOp,
|
||||
depthFailOp = (Refresh.StencilOp)DepthFailOp,
|
||||
compareOp = (Refresh.CompareOp)CompareOp,
|
||||
compareMask = CompareMask,
|
||||
writeMask = WriteMask,
|
||||
reference = Reference
|
||||
};
|
||||
}
|
||||
}
|
||||
public Viewport(float x, float y, float w, float h, float minDepth, float maxDepth)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
W = w;
|
||||
H = h;
|
||||
MinDepth = minDepth;
|
||||
MaxDepth = maxDepth;
|
||||
}
|
||||
|
||||
public Refresh.Viewport ToRefresh()
|
||||
{
|
||||
return new Refresh.Viewport
|
||||
{
|
||||
x = X,
|
||||
y = Y,
|
||||
w = W,
|
||||
h = H,
|
||||
minDepth = MinDepth,
|
||||
maxDepth = MaxDepth
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VertexBinding
|
||||
{
|
||||
public uint Binding;
|
||||
public uint Stride;
|
||||
public VertexInputRate InputRate;
|
||||
|
||||
public static VertexBinding Create<T>(uint binding = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged
|
||||
{
|
||||
return new VertexBinding
|
||||
{
|
||||
Binding = binding,
|
||||
InputRate = inputRate,
|
||||
Stride = (uint) Marshal.SizeOf<T>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VertexAttribute
|
||||
{
|
||||
public uint Location;
|
||||
public uint Binding;
|
||||
public VertexElementFormat Format;
|
||||
public uint Offset;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct StencilOpState
|
||||
{
|
||||
public StencilOp FailOp;
|
||||
public StencilOp PassOp;
|
||||
public StencilOp DepthFailOp;
|
||||
public CompareOp CompareOp;
|
||||
public uint CompareMask;
|
||||
public uint WriteMask;
|
||||
public uint Reference;
|
||||
|
||||
// FIXME: can we do an explicit cast here?
|
||||
public Refresh.StencilOpState ToRefresh()
|
||||
{
|
||||
return new Refresh.StencilOpState
|
||||
{
|
||||
failOp = (Refresh.StencilOp) FailOp,
|
||||
passOp = (Refresh.StencilOp) PassOp,
|
||||
depthFailOp = (Refresh.StencilOp) DepthFailOp,
|
||||
compareOp = (Refresh.CompareOp) CompareOp,
|
||||
compareMask = CompareMask,
|
||||
writeMask = WriteMask,
|
||||
reference = Reference
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ColorAttachmentInfo
|
||||
{
|
||||
public Texture Texture;
|
||||
public uint Depth;
|
||||
public uint Layer;
|
||||
public uint Level;
|
||||
public Color ClearColor;
|
||||
public LoadOp LoadOp;
|
||||
public StoreOp StoreOp;
|
||||
|
||||
public ColorAttachmentInfo(
|
||||
Texture texture,
|
||||
Color clearColor,
|
||||
StoreOp storeOp = StoreOp.Store
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
ClearColor = clearColor;
|
||||
LoadOp = LoadOp.Clear;
|
||||
StoreOp = storeOp;
|
||||
}
|
||||
|
||||
public ColorAttachmentInfo(
|
||||
Texture texture,
|
||||
LoadOp loadOp = LoadOp.DontCare,
|
||||
StoreOp storeOp = StoreOp.Store
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
ClearColor = Color.White;
|
||||
LoadOp = loadOp;
|
||||
StoreOp = storeOp;
|
||||
}
|
||||
|
||||
public Refresh.ColorAttachmentInfo ToRefresh()
|
||||
{
|
||||
return new Refresh.ColorAttachmentInfo
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level,
|
||||
clearColor = new Refresh.Vec4
|
||||
{
|
||||
x = ClearColor.R / 255f,
|
||||
y = ClearColor.G / 255f,
|
||||
z = ClearColor.B / 255f,
|
||||
w = ClearColor.A / 255f
|
||||
},
|
||||
loadOp = (Refresh.LoadOp) LoadOp,
|
||||
storeOp = (Refresh.StoreOp) StoreOp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DepthStencilAttachmentInfo
|
||||
{
|
||||
public Texture Texture;
|
||||
public uint Depth;
|
||||
public uint Layer;
|
||||
public uint Level;
|
||||
public DepthStencilValue DepthStencilClearValue;
|
||||
public LoadOp LoadOp;
|
||||
public StoreOp StoreOp;
|
||||
public LoadOp StencilLoadOp;
|
||||
public StoreOp StencilStoreOp;
|
||||
|
||||
public DepthStencilAttachmentInfo(
|
||||
Texture texture,
|
||||
DepthStencilValue clearValue,
|
||||
StoreOp depthStoreOp = StoreOp.Store,
|
||||
StoreOp stencilStoreOp = StoreOp.Store
|
||||
)
|
||||
{
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
DepthStencilClearValue = clearValue;
|
||||
LoadOp = LoadOp.Clear;
|
||||
StoreOp = depthStoreOp;
|
||||
StencilLoadOp = LoadOp.Clear;
|
||||
StencilStoreOp = stencilStoreOp;
|
||||
}
|
||||
|
||||
public DepthStencilAttachmentInfo(
|
||||
Texture texture,
|
||||
LoadOp loadOp = LoadOp.DontCare,
|
||||
StoreOp storeOp = StoreOp.Store,
|
||||
LoadOp stencilLoadOp = LoadOp.DontCare,
|
||||
StoreOp stencilStoreOp = StoreOp.Store
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
DepthStencilClearValue = new DepthStencilValue();
|
||||
LoadOp = loadOp;
|
||||
StoreOp = storeOp;
|
||||
StencilLoadOp = stencilLoadOp;
|
||||
StencilStoreOp = stencilStoreOp;
|
||||
}
|
||||
|
||||
public DepthStencilAttachmentInfo(
|
||||
Texture texture,
|
||||
DepthStencilValue depthStencilValue,
|
||||
LoadOp loadOp,
|
||||
StoreOp storeOp,
|
||||
LoadOp stencilLoadOp,
|
||||
StoreOp stencilStoreOp
|
||||
) {
|
||||
Texture = texture;
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
DepthStencilClearValue = depthStencilValue;
|
||||
LoadOp = loadOp;
|
||||
StoreOp = storeOp;
|
||||
StencilLoadOp = stencilLoadOp;
|
||||
StencilStoreOp = stencilStoreOp;
|
||||
}
|
||||
|
||||
public Refresh.DepthStencilAttachmentInfo ToRefresh()
|
||||
{
|
||||
return new Refresh.DepthStencilAttachmentInfo
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level,
|
||||
depthStencilClearValue = DepthStencilClearValue.ToRefresh(),
|
||||
loadOp = (Refresh.LoadOp) LoadOp,
|
||||
storeOp = (Refresh.StoreOp) StoreOp,
|
||||
stencilLoadOp = (Refresh.LoadOp) StencilLoadOp,
|
||||
stencilStoreOp = (Refresh.StoreOp) StencilStoreOp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ColorAttachmentDescription
|
||||
{
|
||||
public TextureFormat Format;
|
||||
public ColorAttachmentBlendState BlendState;
|
||||
|
||||
public ColorAttachmentDescription(
|
||||
TextureFormat format,
|
||||
ColorAttachmentBlendState blendState
|
||||
) {
|
||||
Format = format;
|
||||
BlendState = blendState;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct IndirectDrawCommand
|
||||
{
|
||||
public uint VertexCount;
|
||||
public uint InstanceCount;
|
||||
public uint FirstVertex;
|
||||
public uint FirstInstance;
|
||||
|
||||
public IndirectDrawCommand(
|
||||
uint vertexCount,
|
||||
uint instanceCount,
|
||||
uint firstVertex,
|
||||
uint firstInstance
|
||||
) {
|
||||
VertexCount = vertexCount;
|
||||
InstanceCount = instanceCount;
|
||||
FirstVertex = firstVertex;
|
||||
FirstInstance = firstInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,143 +1,141 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffers are generic data containers that can be used by the GPU.
|
||||
/// </summary>
|
||||
public class Buffer : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||
/// <summary>
|
||||
/// Buffers are generic data containers that can be used by the GPU.
|
||||
/// </summary>
|
||||
public class Buffer : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
|
||||
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
|
||||
public Buffer(
|
||||
GraphicsDevice device,
|
||||
BufferUsageFlags usageFlags,
|
||||
uint sizeInBytes
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateBuffer(
|
||||
device.Handle,
|
||||
(Refresh.BufferUsageFlags) usageFlags,
|
||||
sizeInBytes
|
||||
);
|
||||
}
|
||||
/// <summary>
|
||||
/// Size in bytes.
|
||||
/// </summary>
|
||||
public uint Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the buffer.
|
||||
/// </summary>
|
||||
/// <param name="data">An array of data to copy into the buffer.</param>
|
||||
/// <param name="offsetInElements">Specifies where to start copying out of the array.</param>
|
||||
/// <param name="lengthInElements">Specifies how many elements to copy.</param>
|
||||
public unsafe void SetData<T>(
|
||||
T[] data,
|
||||
uint offsetInElements,
|
||||
uint lengthInElements
|
||||
) where T : unmanaged
|
||||
{
|
||||
var elementSize = Marshal.SizeOf<T>();
|
||||
/// <summary>
|
||||
/// Creates a buffer of appropriate size given a type and element count.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
|
||||
/// <param name="device">The GraphicsDevice.</param>
|
||||
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
|
||||
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
|
||||
/// <returns></returns>
|
||||
public unsafe static Buffer Create<T>(
|
||||
GraphicsDevice device,
|
||||
BufferUsageFlags usageFlags,
|
||||
uint elementCount
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new Buffer(
|
||||
device,
|
||||
usageFlags,
|
||||
(uint) Marshal.SizeOf<T>() * elementCount
|
||||
);
|
||||
}
|
||||
|
||||
fixed (T* ptr = &data[0])
|
||||
{
|
||||
Refresh.Refresh_SetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(uint) (offsetInElements * elementSize),
|
||||
(IntPtr) ptr,
|
||||
(uint) (lengthInElements * elementSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a buffer.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
|
||||
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
|
||||
public Buffer(
|
||||
GraphicsDevice device,
|
||||
BufferUsageFlags usageFlags,
|
||||
uint sizeInBytes
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateBuffer(
|
||||
device.Handle,
|
||||
(Refresh.BufferUsageFlags) usageFlags,
|
||||
sizeInBytes
|
||||
);
|
||||
Size = sizeInBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the buffer.
|
||||
/// This variant of this method copies the entire array.
|
||||
/// </summary>
|
||||
/// <param name="data">An array of data to copy.</param>
|
||||
public unsafe void SetData<T>(
|
||||
T[] data
|
||||
) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &data[0])
|
||||
{
|
||||
Refresh.Refresh_SetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
0,
|
||||
(IntPtr)ptr,
|
||||
(uint) (data.Length * Marshal.SizeOf<T>())
|
||||
);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads data out of a buffer and into a span.
|
||||
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
|
||||
/// </summary>
|
||||
/// <param name="data">The span that data will be copied to.</param>
|
||||
/// <param name="dataLengthInBytes">The length of the data to read.</param>
|
||||
public unsafe void GetData<T>(
|
||||
Span<T> data,
|
||||
uint dataLengthInBytes
|
||||
) where T : unmanaged
|
||||
{
|
||||
#if DEBUG
|
||||
if (dataLengthInBytes > Size)
|
||||
{
|
||||
Logger.LogWarn("Requested too many bytes from buffer!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the buffer.
|
||||
/// </summary>
|
||||
/// <param name="data">A pointer to an array.</param>
|
||||
/// <param name="offsetInBytes">Specifies where to start copying the data, in bytes.</param>
|
||||
/// <param name="dataLengthInBytes">Specifies how many bytes of data to copy.</param>
|
||||
public void SetData(
|
||||
IntPtr data,
|
||||
uint offsetInBytes,
|
||||
uint dataLengthInBytes
|
||||
) {
|
||||
Refresh.Refresh_SetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
offsetInBytes,
|
||||
data,
|
||||
dataLengthInBytes
|
||||
);
|
||||
}
|
||||
if (dataLengthInBytes > data.Length * Marshal.SizeOf<T>())
|
||||
{
|
||||
Logger.LogWarn("Data length is larger than the provided Span!");
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the buffer.
|
||||
/// </summary>
|
||||
/// <param name="data">A pointer to an array.</param>
|
||||
/// <param name="offsetInBytes">Specifies where to start copying the data, in bytes.</param>
|
||||
/// <param name="dataLengthInBytes">Specifies how many bytes of data to copy.</param>
|
||||
public unsafe void SetData<T>(
|
||||
T* data,
|
||||
uint offsetInElements,
|
||||
uint lengthInElements
|
||||
) where T : unmanaged {
|
||||
var elementSize = Marshal.SizeOf<T>();
|
||||
Refresh.Refresh_SetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(uint) (offsetInElements * elementSize),
|
||||
(IntPtr) data,
|
||||
(uint) (lengthInElements * elementSize)
|
||||
);
|
||||
}
|
||||
fixed (T* ptr = data)
|
||||
{
|
||||
Refresh.Refresh_GetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
dataLengthInBytes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data out of a buffer and into an array.
|
||||
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait is called first.
|
||||
/// </summary>
|
||||
/// <param name="data">The array that data will be copied to.</param>
|
||||
/// <param name="dataLengthInBytes">The length of the data to read.</param>
|
||||
public unsafe void GetData<T>(
|
||||
T[] data,
|
||||
uint dataLengthInBytes
|
||||
) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &data[0])
|
||||
{
|
||||
Refresh.Refresh_GetBufferData(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr)ptr,
|
||||
dataLengthInBytes
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Reads data out of a buffer and into an array.
|
||||
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
|
||||
/// </summary>
|
||||
/// <param name="data">The span that data will be copied to.</param>
|
||||
/// <param name="dataLengthInBytes">The length of the data to read.</param>
|
||||
public unsafe void GetData<T>(
|
||||
T[] data,
|
||||
uint dataLengthInBytes
|
||||
) where T : unmanaged
|
||||
{
|
||||
GetData(new Span<T>(data), dataLengthInBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data out of a buffer and into a span.
|
||||
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
|
||||
/// Fills the span with as much data from the buffer as it can.
|
||||
/// </summary>
|
||||
/// <param name="data">The span that data will be copied to.</param>
|
||||
public unsafe void GetData<T>(
|
||||
Span<T> data
|
||||
) where T : unmanaged
|
||||
{
|
||||
var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size);
|
||||
GetData(data, (uint) lengthInBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data out of a buffer and into an array.
|
||||
/// This operation is only guaranteed to read up-to-date data if GraphicsDevice.Wait or GraphicsDevice.WaitForFences is called first.
|
||||
/// Fills the array with as much data from the buffer as it can.
|
||||
/// </summary>
|
||||
/// <param name="data">The span that data will be copied to.</param>
|
||||
public unsafe void GetData<T>(
|
||||
T[] data
|
||||
) where T : unmanaged
|
||||
{
|
||||
var lengthInBytes = System.Math.Min(data.Length * Marshal.SizeOf<T>(), Size);
|
||||
GetData(new Span<T>(data), (uint) lengthInBytes);
|
||||
}
|
||||
|
||||
public static implicit operator BufferBinding(Buffer b)
|
||||
{
|
||||
return new BufferBinding(b, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,41 @@
|
|||
using RefreshCS;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public class ComputePipeline : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
||||
/// <summary>
|
||||
/// Compute pipelines perform arbitrary parallel processing on input data.
|
||||
/// </summary>
|
||||
public class ComputePipeline : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline;
|
||||
|
||||
public ShaderStageState ComputeShaderState { get; }
|
||||
public ComputeShaderInfo ComputeShaderInfo { get; }
|
||||
|
||||
public unsafe ComputePipeline(
|
||||
GraphicsDevice device,
|
||||
ShaderStageState computeShaderState,
|
||||
uint bufferBindingCount,
|
||||
uint imageBindingCount
|
||||
) : base(device) {
|
||||
var computePipelineLayoutCreateInfo = new Refresh.ComputePipelineLayoutCreateInfo
|
||||
{
|
||||
bufferBindingCount = bufferBindingCount,
|
||||
imageBindingCount = imageBindingCount
|
||||
};
|
||||
public unsafe ComputePipeline(
|
||||
GraphicsDevice device,
|
||||
ComputeShaderInfo computeShaderInfo
|
||||
) : base(device)
|
||||
{
|
||||
var refreshComputeShaderInfo = new Refresh.ComputeShaderInfo
|
||||
{
|
||||
entryPointName = computeShaderInfo.EntryPointName,
|
||||
shaderModule = computeShaderInfo.ShaderModule.Handle,
|
||||
uniformBufferSize = computeShaderInfo.UniformBufferSize,
|
||||
bufferBindingCount = computeShaderInfo.BufferBindingCount,
|
||||
imageBindingCount = computeShaderInfo.ImageBindingCount
|
||||
};
|
||||
|
||||
var computePipelineCreateInfo = new Refresh.ComputePipelineCreateInfo
|
||||
{
|
||||
pipelineLayoutCreateInfo = computePipelineLayoutCreateInfo,
|
||||
computeShaderState = new Refresh.ShaderStageState
|
||||
{
|
||||
entryPointName = computeShaderState.EntryPointName,
|
||||
shaderModule = computeShaderState.ShaderModule.Handle,
|
||||
uniformBufferSize = computeShaderState.UniformBufferSize
|
||||
}
|
||||
};
|
||||
Handle = Refresh.Refresh_CreateComputePipeline(
|
||||
device.Handle,
|
||||
refreshComputeShaderInfo
|
||||
);
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Could not create compute pipeline!");
|
||||
}
|
||||
|
||||
Handle = Refresh.Refresh_CreateComputePipeline(
|
||||
device.Handle,
|
||||
computePipelineCreateInfo
|
||||
);
|
||||
|
||||
ComputeShaderState = computeShaderState;
|
||||
}
|
||||
|
||||
public unsafe uint PushComputeShaderUniforms<T>(
|
||||
params T[] uniforms
|
||||
) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &uniforms[0])
|
||||
{
|
||||
return Refresh.Refresh_PushComputeShaderUniforms(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
(uint) (uniforms.Length * Marshal.SizeOf<T>())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ComputeShaderInfo = computeShaderInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Fences allow you to track the status of a submitted command buffer. <br/>
|
||||
/// You should only acquire a Fence if you will need to track the command buffer. <br/>
|
||||
/// You should make sure to call GraphicsDevice.ReleaseFence when done with a Fence to avoid memory growth. <br/>
|
||||
/// The Fence object itself is basically just a wrapper for the Refresh_Fence. <br/>
|
||||
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
|
||||
/// </summary>
|
||||
public class Fence : RefreshResource
|
||||
{
|
||||
protected override Action<nint, nint> QueueDestroyFunction => Refresh.Refresh_ReleaseFence;
|
||||
|
||||
internal Fence(GraphicsDevice device) : base(device)
|
||||
{
|
||||
}
|
||||
|
||||
internal void SetHandle(nint handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A framebuffer is a collection of render targets that is rendered to during a render pass.
|
||||
/// </summary>
|
||||
public class Framebuffer : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyFramebuffer;
|
||||
|
||||
public RenderTarget DepthStencilTarget { get; }
|
||||
|
||||
private RenderTarget[] colorTargets { get; }
|
||||
public IEnumerable<RenderTarget> ColorTargets => colorTargets;
|
||||
|
||||
public RenderPass RenderPass { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a framebuffer.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the framebuffer.</param>
|
||||
/// <param name="height">The height of the framebuffer.</param>
|
||||
/// <param name="renderPass">The reference render pass for the framebuffer.</param>
|
||||
/// <param name="depthStencilTarget">The depth stencil target. Can be null.</param>
|
||||
/// <param name="colorTargets">Anywhere from 0-4 color targets can be provided.</param>
|
||||
public unsafe Framebuffer(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
RenderPass renderPass,
|
||||
RenderTarget depthStencilTarget,
|
||||
params RenderTarget[] colorTargets
|
||||
) : base(device)
|
||||
{
|
||||
IntPtr[] colorTargetHandles = new IntPtr[colorTargets.Length];
|
||||
for (var i = 0; i < colorTargets.Length; i += 1)
|
||||
{
|
||||
colorTargetHandles[i] = colorTargets[i].Handle;
|
||||
}
|
||||
|
||||
IntPtr depthStencilTargetHandle;
|
||||
if (depthStencilTarget == null)
|
||||
{
|
||||
depthStencilTargetHandle = IntPtr.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
depthStencilTargetHandle = depthStencilTarget.Handle;
|
||||
}
|
||||
|
||||
fixed (IntPtr* colorTargetHandlesPtr = colorTargetHandles)
|
||||
{
|
||||
Refresh.FramebufferCreateInfo framebufferCreateInfo = new Refresh.FramebufferCreateInfo
|
||||
{
|
||||
width = width,
|
||||
height = height,
|
||||
colorTargetCount = (uint) colorTargets.Length,
|
||||
pColorTargets = (IntPtr) colorTargetHandlesPtr,
|
||||
depthStencilTarget = depthStencilTargetHandle,
|
||||
renderPass = renderPass.Handle
|
||||
};
|
||||
|
||||
Handle = Refresh.Refresh_CreateFramebuffer(device.Handle, framebufferCreateInfo);
|
||||
}
|
||||
|
||||
DepthStencilTarget = depthStencilTarget;
|
||||
|
||||
this.colorTargets = new RenderTarget[colorTargets.Length];
|
||||
for (var i = 0; i < colorTargets.Length; i++)
|
||||
{
|
||||
this.colorTargets[i] = colorTargets[i];
|
||||
}
|
||||
|
||||
RenderPass = renderPass;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,162 +1,125 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Graphics pipelines encapsulate all of the render state in a single object.
|
||||
/// These pipelines are bound before draw calls are issued.
|
||||
/// </summary>
|
||||
public class GraphicsPipeline : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||
/// <summary>
|
||||
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
|
||||
/// These pipelines are bound before draw calls are issued.
|
||||
/// </summary>
|
||||
public class GraphicsPipeline : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline;
|
||||
|
||||
public ShaderStageState VertexShaderState { get; }
|
||||
public ShaderStageState FragmentShaderState { get; }
|
||||
public RenderPass RenderPass { get; }
|
||||
public GraphicsShaderInfo VertexShaderInfo { get; }
|
||||
public GraphicsShaderInfo FragmentShaderInfo { get; }
|
||||
public SampleCount SampleCount { get; }
|
||||
|
||||
public unsafe GraphicsPipeline(
|
||||
GraphicsDevice device,
|
||||
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
ColorBlendState colorBlendState = graphicsPipelineCreateInfo.ColorBlendState;
|
||||
DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState;
|
||||
ShaderStageState vertexShaderState = graphicsPipelineCreateInfo.VertexShaderState;
|
||||
ShaderStageState fragmentShaderState = graphicsPipelineCreateInfo.FragmentShaderState;
|
||||
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
|
||||
GraphicsPipelineLayoutInfo pipelineLayoutInfo = graphicsPipelineCreateInfo.PipelineLayoutInfo;
|
||||
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
|
||||
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
|
||||
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
|
||||
ViewportState viewportState = graphicsPipelineCreateInfo.ViewportState;
|
||||
RenderPass renderPass = graphicsPipelineCreateInfo.RenderPass;
|
||||
#if DEBUG
|
||||
internal GraphicsPipelineAttachmentInfo AttachmentInfo { get; }
|
||||
#endif
|
||||
|
||||
var vertexAttributesHandle = GCHandle.Alloc(
|
||||
vertexInputState.VertexAttributes,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
var vertexBindingsHandle = GCHandle.Alloc(
|
||||
vertexInputState.VertexBindings,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
var viewportHandle = GCHandle.Alloc(
|
||||
viewportState.Viewports,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
var scissorHandle = GCHandle.Alloc(
|
||||
viewportState.Scissors,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
public unsafe GraphicsPipeline(
|
||||
GraphicsDevice device,
|
||||
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
DepthStencilState depthStencilState = graphicsPipelineCreateInfo.DepthStencilState;
|
||||
GraphicsShaderInfo vertexShaderInfo = graphicsPipelineCreateInfo.VertexShaderInfo;
|
||||
GraphicsShaderInfo fragmentShaderInfo = graphicsPipelineCreateInfo.FragmentShaderInfo;
|
||||
MultisampleState multisampleState = graphicsPipelineCreateInfo.MultisampleState;
|
||||
RasterizerState rasterizerState = graphicsPipelineCreateInfo.RasterizerState;
|
||||
PrimitiveType primitiveType = graphicsPipelineCreateInfo.PrimitiveType;
|
||||
VertexInputState vertexInputState = graphicsPipelineCreateInfo.VertexInputState;
|
||||
GraphicsPipelineAttachmentInfo attachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
|
||||
BlendConstants blendConstants = graphicsPipelineCreateInfo.BlendConstants;
|
||||
|
||||
var colorTargetBlendStates = stackalloc Refresh.ColorTargetBlendState[
|
||||
colorBlendState.ColorTargetBlendStates.Length
|
||||
];
|
||||
var vertexAttributesHandle = GCHandle.Alloc(
|
||||
vertexInputState.VertexAttributes,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
var vertexBindingsHandle = GCHandle.Alloc(
|
||||
vertexInputState.VertexBindings,
|
||||
GCHandleType.Pinned
|
||||
);
|
||||
|
||||
for (var i = 0; i < colorBlendState.ColorTargetBlendStates.Length; i += 1)
|
||||
{
|
||||
colorTargetBlendStates[i] = colorBlendState.ColorTargetBlendStates[i].ToRefreshColorTargetBlendState();
|
||||
}
|
||||
var colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
|
||||
(int) attachmentInfo.ColorAttachmentDescriptions.Length
|
||||
];
|
||||
|
||||
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
|
||||
for (var i = 0; i < attachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
|
||||
{
|
||||
colorAttachmentDescriptions[i].format = (Refresh.TextureFormat) attachmentInfo.ColorAttachmentDescriptions[i].Format;
|
||||
colorAttachmentDescriptions[i].blendState = attachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
|
||||
}
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.logicOpEnable = Conversions.BoolToByte(colorBlendState.LogicOpEnable);
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.logicOp = (Refresh.LogicOp) colorBlendState.LogicOp;
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.blendStates = (IntPtr) colorTargetBlendStates;
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.blendStateCount = (uint) colorBlendState.ColorTargetBlendStates.Length;
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[0] = colorBlendState.BlendConstants.R;
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[1] = colorBlendState.BlendConstants.G;
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[2] = colorBlendState.BlendConstants.B;
|
||||
refreshGraphicsPipelineCreateInfo.colorBlendState.blendConstants[3] = colorBlendState.BlendConstants.A;
|
||||
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable);
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.depthTestEnable = Conversions.BoolToByte(depthStencilState.DepthTestEnable);
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.depthWriteEnable = Conversions.BoolToByte(depthStencilState.DepthWriteEnable);
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.maxDepthBounds = depthStencilState.MaxDepthBounds;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable);
|
||||
refreshGraphicsPipelineCreateInfo.blendConstants[0] = blendConstants.R;
|
||||
refreshGraphicsPipelineCreateInfo.blendConstants[1] = blendConstants.G;
|
||||
refreshGraphicsPipelineCreateInfo.blendConstants[2] = blendConstants.B;
|
||||
refreshGraphicsPipelineCreateInfo.blendConstants[3] = blendConstants.A;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderState.entryPointName = vertexShaderState.EntryPointName;
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderState.shaderModule = vertexShaderState.ShaderModule.Handle;
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderState.uniformBufferSize = vertexShaderState.UniformBufferSize;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.backStencilState = depthStencilState.BackStencilState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.compareOp = (Refresh.CompareOp) depthStencilState.CompareOp;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.depthBoundsTestEnable = Conversions.BoolToByte(depthStencilState.DepthBoundsTestEnable);
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.depthTestEnable = Conversions.BoolToByte(depthStencilState.DepthTestEnable);
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.depthWriteEnable = Conversions.BoolToByte(depthStencilState.DepthWriteEnable);
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.frontStencilState = depthStencilState.FrontStencilState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.maxDepthBounds = depthStencilState.MaxDepthBounds;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.minDepthBounds = depthStencilState.MinDepthBounds;
|
||||
refreshGraphicsPipelineCreateInfo.depthStencilState.stencilTestEnable = Conversions.BoolToByte(depthStencilState.StencilTestEnable);
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderState.entryPointName = fragmentShaderState.EntryPointName;
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderState.shaderModule = fragmentShaderState.ShaderModule.Handle;
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderState.uniformBufferSize = fragmentShaderState.UniformBufferSize;
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.entryPointName = vertexShaderInfo.EntryPointName;
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.shaderModule = vertexShaderInfo.ShaderModule.Handle;
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.uniformBufferSize = vertexShaderInfo.UniformBufferSize;
|
||||
refreshGraphicsPipelineCreateInfo.vertexShaderInfo.samplerBindingCount = vertexShaderInfo.SamplerBindingCount;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount)multisampleState.MultisampleCount;
|
||||
refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask;
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.entryPointName = fragmentShaderInfo.EntryPointName;
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.shaderModule = fragmentShaderInfo.ShaderModule.Handle;
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.uniformBufferSize = fragmentShaderInfo.UniformBufferSize;
|
||||
refreshGraphicsPipelineCreateInfo.fragmentShaderInfo.samplerBindingCount = fragmentShaderInfo.SamplerBindingCount;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.pipelineLayoutCreateInfo.vertexSamplerBindingCount = pipelineLayoutInfo.VertexSamplerBindingCount;
|
||||
refreshGraphicsPipelineCreateInfo.pipelineLayoutCreateInfo.fragmentSamplerBindingCount = pipelineLayoutInfo.FragmentSamplerBindingCount;
|
||||
refreshGraphicsPipelineCreateInfo.multisampleState.multisampleCount = (Refresh.SampleCount) multisampleState.MultisampleCount;
|
||||
refreshGraphicsPipelineCreateInfo.multisampleState.sampleMask = multisampleState.SampleMask;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode)rasterizerState.CullMode;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable);
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthClampEnable = Conversions.BoolToByte(rasterizerState.DepthClampEnable);
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode)rasterizerState.FillMode;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace)rasterizerState.FrontFace;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.lineWidth = rasterizerState.LineWidth;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.cullMode = (Refresh.CullMode) rasterizerState.CullMode;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasClamp = rasterizerState.DepthBiasClamp;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasConstantFactor = rasterizerState.DepthBiasConstantFactor;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasEnable = Conversions.BoolToByte(rasterizerState.DepthBiasEnable);
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.depthBiasSlopeFactor = rasterizerState.DepthBiasSlopeFactor;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.fillMode = (Refresh.FillMode) rasterizerState.FillMode;
|
||||
refreshGraphicsPipelineCreateInfo.rasterizerState.frontFace = (Refresh.FrontFace) rasterizerState.FrontFace;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject();
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length;
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject();
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindingCount = (uint) vertexInputState.VertexBindings.Length;
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributes = vertexAttributesHandle.AddrOfPinnedObject();
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexAttributeCount = (uint) vertexInputState.VertexAttributes.Length;
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindings = vertexBindingsHandle.AddrOfPinnedObject();
|
||||
refreshGraphicsPipelineCreateInfo.vertexInputState.vertexBindingCount = (uint) vertexInputState.VertexBindings.Length;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.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.renderPass = renderPass.Handle;
|
||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentCount = (uint) attachmentInfo.ColorAttachmentDescriptions.Length;
|
||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.colorAttachmentDescriptions = (IntPtr) colorAttachmentDescriptions;
|
||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.depthStencilFormat = (Refresh.TextureFormat) attachmentInfo.DepthStencilFormat;
|
||||
refreshGraphicsPipelineCreateInfo.attachmentInfo.hasDepthStencilAttachment = Conversions.BoolToByte(attachmentInfo.HasDepthStencilAttachment);
|
||||
|
||||
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
|
||||
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Could not create graphics pipeline!");
|
||||
}
|
||||
|
||||
vertexAttributesHandle.Free();
|
||||
vertexBindingsHandle.Free();
|
||||
viewportHandle.Free();
|
||||
scissorHandle.Free();
|
||||
vertexAttributesHandle.Free();
|
||||
vertexBindingsHandle.Free();
|
||||
|
||||
VertexShaderState = vertexShaderState;
|
||||
FragmentShaderState = fragmentShaderState;
|
||||
RenderPass = renderPass;
|
||||
}
|
||||
VertexShaderInfo = vertexShaderInfo;
|
||||
FragmentShaderInfo = fragmentShaderInfo;
|
||||
SampleCount = multisampleState.MultisampleCount;
|
||||
|
||||
public unsafe uint PushVertexShaderUniforms<T>(
|
||||
params T[] uniforms
|
||||
) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &uniforms[0])
|
||||
{
|
||||
return Refresh.Refresh_PushVertexShaderUniforms(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
(uint) (uniforms.Length * Marshal.SizeOf<T>())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe uint PushFragmentShaderUniforms<T>(
|
||||
params T[] uniforms
|
||||
) where T : unmanaged
|
||||
{
|
||||
fixed (T* ptr = &uniforms[0])
|
||||
{
|
||||
return Refresh.Refresh_PushFragmentShaderUniforms(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
(IntPtr) ptr,
|
||||
(uint) (uniforms.Length * Marshal.SizeOf<T>())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
AttachmentInfo = attachmentInfo;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A render pass describes the kind of render targets that will be used in rendering.
|
||||
/// </summary>
|
||||
public class RenderPass : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyRenderPass;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render pass using color target descriptions.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="colorTargetDescriptions">Up to 4 color target descriptions may be provided.</param>
|
||||
public unsafe RenderPass(
|
||||
GraphicsDevice device,
|
||||
params ColorTargetDescription[] colorTargetDescriptions
|
||||
) : base(device)
|
||||
{
|
||||
fixed (ColorTargetDescription* ptr = colorTargetDescriptions)
|
||||
{
|
||||
Refresh.RenderPassCreateInfo renderPassCreateInfo;
|
||||
renderPassCreateInfo.colorTargetCount = (uint) colorTargetDescriptions.Length;
|
||||
renderPassCreateInfo.colorTargetDescriptions = (IntPtr) ptr;
|
||||
renderPassCreateInfo.depthStencilTargetDescription = IntPtr.Zero;
|
||||
|
||||
Handle = Refresh.Refresh_CreateRenderPass(device.Handle, renderPassCreateInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render pass using a depth/stencil target description and optional color target descriptions.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="depthStencilTargetDescription">A depth/stencil target description.</param>
|
||||
/// <param name="colorTargetDescriptions">Up to 4 color target descriptions may be provided.</param>
|
||||
public unsafe RenderPass(
|
||||
GraphicsDevice device,
|
||||
in DepthStencilTargetDescription depthStencilTargetDescription,
|
||||
params ColorTargetDescription[] colorTargetDescriptions
|
||||
) : base(device)
|
||||
{
|
||||
|
||||
fixed (DepthStencilTargetDescription* depthStencilPtr = &depthStencilTargetDescription)
|
||||
fixed (ColorTargetDescription* colorPtr = colorTargetDescriptions)
|
||||
{
|
||||
Refresh.RenderPassCreateInfo renderPassCreateInfo;
|
||||
renderPassCreateInfo.colorTargetCount = (uint)colorTargetDescriptions.Length;
|
||||
renderPassCreateInfo.colorTargetDescriptions = (IntPtr)colorPtr;
|
||||
renderPassCreateInfo.depthStencilTargetDescription = (IntPtr) depthStencilPtr;
|
||||
|
||||
Handle = Refresh.Refresh_CreateRenderPass(device.Handle, renderPassCreateInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +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;
|
||||
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyRenderTarget;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render target backed by a texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the render target.</param>
|
||||
/// <param name="height">The height of the render target.</param>
|
||||
/// <param name="format">The format of the render target.</param>
|
||||
/// <param name="canBeSampled">Whether the render target can be used by a sampler.</param>
|
||||
/// <param name="sampleCount">The multisample count of the render target.</param>
|
||||
/// <param name="levelCount">The mip level of the render target.</param>
|
||||
/// <returns></returns>
|
||||
public static RenderTarget CreateBackedRenderTarget(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
TextureFormat format,
|
||||
bool canBeSampled,
|
||||
SampleCount sampleCount = SampleCount.One,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
TextureUsageFlags flags = 0;
|
||||
|
||||
if (
|
||||
format == TextureFormat.D16 ||
|
||||
format == TextureFormat.D32 ||
|
||||
format == TextureFormat.D16S8 ||
|
||||
format == TextureFormat.D32S8
|
||||
) {
|
||||
flags |= TextureUsageFlags.DepthStencilTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
flags |= TextureUsageFlags.ColorTarget;
|
||||
}
|
||||
|
||||
if (canBeSampled)
|
||||
{
|
||||
flags |= TextureUsageFlags.Sampler;
|
||||
}
|
||||
|
||||
var texture = Texture.CreateTexture2D(
|
||||
device,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
flags,
|
||||
sampleCount,
|
||||
levelCount
|
||||
);
|
||||
|
||||
return new RenderTarget(device, new TextureSlice(texture), sampleCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a render target using a texture slice and an optional sample count.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="textureSlice">The texture slice that will be rendered to.</param>
|
||||
/// <param name="sampleCount">The desired multisample count of the render target.</param>
|
||||
public RenderTarget(
|
||||
GraphicsDevice device,
|
||||
in TextureSlice textureSlice,
|
||||
SampleCount sampleCount = SampleCount.One
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateRenderTarget(
|
||||
device.Handle,
|
||||
textureSlice.ToRefreshTextureSlice(),
|
||||
(Refresh.SampleCount) sampleCount
|
||||
);
|
||||
TextureSlice = textureSlice;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,24 @@
|
|||
using System;
|
||||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A sampler specifies how a texture will be sampled in a shader.
|
||||
/// </summary>
|
||||
public class Sampler : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||
/// <summary>
|
||||
/// A sampler specifies how a texture will be sampled in a shader.
|
||||
/// </summary>
|
||||
public class Sampler : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler;
|
||||
|
||||
public Sampler(
|
||||
GraphicsDevice device,
|
||||
in SamplerCreateInfo samplerCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateSampler(
|
||||
device.Handle,
|
||||
samplerCreateInfo.ToRefreshSamplerStateCreateInfo()
|
||||
);
|
||||
}
|
||||
}
|
||||
public Sampler(
|
||||
GraphicsDevice device,
|
||||
in SamplerCreateInfo samplerCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateSampler(
|
||||
device.Handle,
|
||||
samplerCreateInfo.ToRefreshSamplerStateCreateInfo()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,42 @@
|
|||
using RefreshCS;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader modules expect input in SPIR-V bytecode format.
|
||||
/// </summary>
|
||||
public class ShaderModule : GraphicsResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||
/// <summary>
|
||||
/// Shader modules expect input in Refresh bytecode format.
|
||||
/// </summary>
|
||||
public class ShaderModule : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule;
|
||||
|
||||
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
|
||||
{
|
||||
var bytecode = Bytecode.ReadBytecodeAsUInt32(filePath);
|
||||
public unsafe ShaderModule(GraphicsDevice device, string filePath) : base(device)
|
||||
{
|
||||
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
Handle = CreateFromStream(device, stream);
|
||||
}
|
||||
|
||||
fixed (uint* ptr = bytecode)
|
||||
{
|
||||
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
|
||||
shaderModuleCreateInfo.codeSize = (UIntPtr) (bytecode.Length * sizeof(uint));
|
||||
shaderModuleCreateInfo.byteCode = (IntPtr) ptr;
|
||||
public unsafe ShaderModule(GraphicsDevice device, Stream stream) : base(device)
|
||||
{
|
||||
Handle = CreateFromStream(device, stream);
|
||||
}
|
||||
|
||||
Handle = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static unsafe IntPtr CreateFromStream(GraphicsDevice device, Stream stream)
|
||||
{
|
||||
var bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
|
||||
var bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
|
||||
stream.ReadExactly(bytecodeSpan);
|
||||
|
||||
Refresh.ShaderModuleCreateInfo shaderModuleCreateInfo;
|
||||
shaderModuleCreateInfo.codeSize = (nuint) stream.Length;
|
||||
shaderModuleCreateInfo.byteCode = (nint) bytecodeBuffer;
|
||||
|
||||
var shaderModule = Refresh.Refresh_CreateShaderModule(device.Handle, shaderModuleCreateInfo);
|
||||
|
||||
NativeMemory.Free(bytecodeBuffer);
|
||||
return shaderModule;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,229 +5,739 @@ using RefreshCS;
|
|||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for pixel data.
|
||||
/// </summary>
|
||||
public class Texture : GraphicsResource
|
||||
{
|
||||
public uint Width { get; }
|
||||
public uint Height { get; }
|
||||
public TextureFormat Format { get; }
|
||||
/// <summary>
|
||||
/// A container for pixel data.
|
||||
/// </summary>
|
||||
public class Texture : RefreshResource
|
||||
{
|
||||
public uint Width { get; internal set; }
|
||||
public uint Height { get; internal set; }
|
||||
public uint Depth { get; }
|
||||
public TextureFormat Format { get; internal set; }
|
||||
public bool IsCube { get; }
|
||||
public uint LevelCount { get; }
|
||||
public SampleCount SampleCount { get; }
|
||||
public TextureUsageFlags UsageFlags { get; }
|
||||
public uint Size { get; }
|
||||
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
|
||||
// FIXME: this allocates a delegate instance
|
||||
protected override Action<IntPtr, IntPtr> QueueDestroyFunction => Refresh.Refresh_QueueDestroyTexture;
|
||||
|
||||
public static Texture LoadPNG(GraphicsDevice device, string filePath)
|
||||
{
|
||||
var pixels = Refresh.Refresh_Image_Load(
|
||||
filePath,
|
||||
out var width,
|
||||
out var height,
|
||||
out var channels
|
||||
);
|
||||
/// <summary>
|
||||
/// Creates a 2D Texture using PNG or QOI data from raw byte data.
|
||||
/// </summary>
|
||||
public static unsafe Texture FromImageBytes(
|
||||
GraphicsDevice device,
|
||||
CommandBuffer commandBuffer,
|
||||
Span<byte> data
|
||||
) {
|
||||
Texture texture;
|
||||
|
||||
TextureCreateInfo textureCreateInfo;
|
||||
textureCreateInfo.Width = (uint)width;
|
||||
textureCreateInfo.Height = (uint)height;
|
||||
textureCreateInfo.Depth = 1;
|
||||
textureCreateInfo.Format = TextureFormat.R8G8B8A8;
|
||||
textureCreateInfo.IsCube = false;
|
||||
textureCreateInfo.LevelCount = 1;
|
||||
textureCreateInfo.SampleCount = SampleCount.One;
|
||||
textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
|
||||
fixed (byte *dataPtr = data)
|
||||
{
|
||||
var pixels = Refresh.Refresh_Image_Load((nint) dataPtr, data.Length, out var width, out var height, out var len);
|
||||
|
||||
var texture = new Texture(device, textureCreateInfo);
|
||||
TextureCreateInfo textureCreateInfo = new TextureCreateInfo();
|
||||
textureCreateInfo.Width = (uint) width;
|
||||
textureCreateInfo.Height = (uint) height;
|
||||
textureCreateInfo.Depth = 1;
|
||||
textureCreateInfo.Format = TextureFormat.R8G8B8A8;
|
||||
textureCreateInfo.IsCube = false;
|
||||
textureCreateInfo.LevelCount = 1;
|
||||
textureCreateInfo.SampleCount = SampleCount.One;
|
||||
textureCreateInfo.UsageFlags = TextureUsageFlags.Sampler;
|
||||
|
||||
texture.SetData(pixels, (uint)(width * height * 4));
|
||||
texture = new Texture(device, textureCreateInfo);
|
||||
commandBuffer.SetTextureData(texture, pixels, (uint) len);
|
||||
|
||||
Refresh.Refresh_Image_Free(pixels);
|
||||
return texture;
|
||||
}
|
||||
Refresh.Refresh_Image_Free(pixels);
|
||||
}
|
||||
|
||||
public unsafe static void SavePNG(string path, int width, int height, byte[] pixels)
|
||||
{
|
||||
fixed (byte* ptr = &pixels[0])
|
||||
{
|
||||
Refresh.Refresh_Image_SavePNG(path, width, height, (IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the texture.</param>
|
||||
/// <param name="height">The height of the texture.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="sampleCount">Specifies the multisample count.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTexture2D(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
SampleCount sampleCount = SampleCount.One,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
var textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = 1,
|
||||
IsCube = false,
|
||||
SampleCount = sampleCount,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
/// <summary>
|
||||
/// Creates a 2D Texture using PNG or QOI data from a stream.
|
||||
/// </summary>
|
||||
public static unsafe Texture FromImageStream(
|
||||
GraphicsDevice device,
|
||||
CommandBuffer commandBuffer,
|
||||
Stream stream
|
||||
) {
|
||||
var length = stream.Length;
|
||||
var buffer = NativeMemory.Alloc((nuint) length);
|
||||
var span = new Span<byte>(buffer, (int) length);
|
||||
stream.ReadExactly(span);
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
var texture = FromImageBytes(device, commandBuffer, span);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 3D texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the texture.</param>
|
||||
/// <param name="height">The height of the texture.</param>
|
||||
/// <param name="depth">The depth of the texture.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="sampleCount">Specifies the multisample count.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTexture3D(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
uint depth,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
SampleCount sampleCount = SampleCount.One,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
var textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = depth,
|
||||
IsCube = false,
|
||||
SampleCount = sampleCount,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
NativeMemory.Free((void*) buffer);
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cube texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="size">The length of one side of the cube.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="sampleCount">Specifies the multisample count.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTextureCube(
|
||||
GraphicsDevice device,
|
||||
uint size,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
SampleCount sampleCount = SampleCount.One,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
var textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = size,
|
||||
Height = size,
|
||||
Depth = 1,
|
||||
IsCube = true,
|
||||
SampleCount = sampleCount,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
/// <summary>
|
||||
/// Creates a 2D Texture using PNG or QOI data from a file.
|
||||
/// </summary>
|
||||
public static Texture FromImageFile(
|
||||
GraphicsDevice device,
|
||||
CommandBuffer commandBuffer,
|
||||
string path
|
||||
) {
|
||||
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
return FromImageStream(device, commandBuffer, fileStream);
|
||||
}
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
public static unsafe void SetDataFromImageBytes(
|
||||
CommandBuffer commandBuffer,
|
||||
TextureSlice textureSlice,
|
||||
Span<byte> data
|
||||
) {
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
var pixels = Refresh.Refresh_Image_Load(
|
||||
(nint) ptr,
|
||||
(int) data.Length,
|
||||
out var w,
|
||||
out var h,
|
||||
out var len
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new texture using a TextureCreateInfo struct.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="textureCreateInfo">The parameters to use when creating the texture.</param>
|
||||
public Texture(
|
||||
GraphicsDevice device,
|
||||
in TextureCreateInfo textureCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateTexture(
|
||||
device.Handle,
|
||||
textureCreateInfo.ToRefreshTextureCreateInfo()
|
||||
);
|
||||
commandBuffer.SetTextureData(textureSlice, pixels, (uint) len);
|
||||
|
||||
Format = textureCreateInfo.Format;
|
||||
Width = textureCreateInfo.Width;
|
||||
Height = textureCreateInfo.Height;
|
||||
}
|
||||
Refresh.Refresh_Image_Free(pixels);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the texture.
|
||||
/// </summary>
|
||||
/// <param name="textureSlice">The texture slice to copy into.</param>
|
||||
/// <param name="dataPtr">A pointer to an array of data to copy from.</param>
|
||||
/// <param name="dataLengthInBytes">The amount of data to copy from the array.</param>
|
||||
public void SetData(in TextureSlice textureSlice, IntPtr dataPtr, uint dataLengthInBytes)
|
||||
{
|
||||
Refresh.Refresh_SetTextureData(
|
||||
Device.Handle,
|
||||
textureSlice.ToRefreshTextureSlice(),
|
||||
dataPtr,
|
||||
dataLengthInBytes
|
||||
);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets data for a texture slice using PNG or QOI data from a stream.
|
||||
/// </summary>
|
||||
public static unsafe void SetDataFromImageStream(
|
||||
CommandBuffer commandBuffer,
|
||||
TextureSlice textureSlice,
|
||||
Stream stream
|
||||
) {
|
||||
var length = stream.Length;
|
||||
var buffer = NativeMemory.Alloc((nuint) length);
|
||||
var span = new Span<byte>(buffer, (int) length);
|
||||
stream.ReadExactly(span);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the texture.
|
||||
/// This variant copies into the entire texture.
|
||||
/// </summary>
|
||||
/// <param name="dataPtr">A pointer to an array of data to copy from.</param>
|
||||
/// <param name="dataLengthInBytes">The amount of data to copy from the array.</param>
|
||||
public void SetData(IntPtr dataPtr, uint dataLengthInBytes)
|
||||
{
|
||||
SetData(new TextureSlice(this), dataPtr, dataLengthInBytes);
|
||||
}
|
||||
SetDataFromImageBytes(commandBuffer, textureSlice, span);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the texture.
|
||||
/// </summary>
|
||||
/// <param name="textureSlice">The texture slice to copy into.</param>
|
||||
/// <param name="data">An array of data to copy into the texture.</param>
|
||||
public unsafe void SetData<T>(in TextureSlice textureSlice, T[] data) where T : unmanaged
|
||||
{
|
||||
var size = Marshal.SizeOf<T>();
|
||||
NativeMemory.Free((void*) buffer);
|
||||
}
|
||||
|
||||
fixed (T* ptr = &data[0])
|
||||
{
|
||||
Refresh.Refresh_SetTextureData(
|
||||
Device.Handle,
|
||||
textureSlice.ToRefreshTextureSlice(),
|
||||
(IntPtr) ptr,
|
||||
(uint) (data.Length * size)
|
||||
);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets data for a texture slice using PNG or QOI data from a file.
|
||||
/// </summary>
|
||||
public static void SetDataFromImageFile(
|
||||
CommandBuffer commandBuffer,
|
||||
TextureSlice textureSlice,
|
||||
string path
|
||||
) {
|
||||
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
SetDataFromImageStream(commandBuffer, textureSlice, fileStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously copies data into the texture.
|
||||
/// This variant copies data into the entire texture.
|
||||
/// </summary>
|
||||
/// <param name="data">An array of data to copy into the texture.</param>
|
||||
public unsafe void SetData<T>(T[] data) where T : unmanaged
|
||||
{
|
||||
SetData(new TextureSlice(this), data);
|
||||
}
|
||||
}
|
||||
public unsafe static Texture LoadDDS(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, System.IO.Stream stream)
|
||||
{
|
||||
using var reader = new BinaryReader(stream);
|
||||
Texture texture;
|
||||
int faces;
|
||||
ParseDDS(reader, out var format, out var width, out var height, out var levels, out var isCube);
|
||||
|
||||
if (isCube)
|
||||
{
|
||||
texture = CreateTextureCube(graphicsDevice, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
|
||||
faces = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = CreateTexture2D(graphicsDevice, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
|
||||
faces = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < faces; i += 1)
|
||||
{
|
||||
for (int j = 0; j < levels; j += 1)
|
||||
{
|
||||
var levelWidth = width >> j;
|
||||
var levelHeight = height >> j;
|
||||
|
||||
var levelSize = CalculateDDSLevelSize(levelWidth, levelHeight, format);
|
||||
var byteBuffer = NativeMemory.Alloc((nuint) levelSize);
|
||||
var byteSpan = new Span<byte>(byteBuffer, levelSize);
|
||||
stream.ReadExactly(byteSpan);
|
||||
|
||||
var textureSlice = new TextureSlice(texture, new Rect(0, 0, levelWidth, levelHeight), 0, (uint) i, (uint) j);
|
||||
commandBuffer.SetTextureData(textureSlice, (nint) byteBuffer, (uint) levelSize);
|
||||
|
||||
NativeMemory.Free(byteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the texture.</param>
|
||||
/// <param name="height">The height of the texture.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTexture2D(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1,
|
||||
SampleCount sampleCount = SampleCount.One
|
||||
) {
|
||||
var textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = 1,
|
||||
IsCube = false,
|
||||
LevelCount = levelCount,
|
||||
SampleCount = sampleCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 3D texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the texture.</param>
|
||||
/// <param name="height">The height of the texture.</param>
|
||||
/// <param name="depth">The depth of the texture.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTexture3D(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
uint depth,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
var textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = depth,
|
||||
IsCube = false,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cube texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="size">The length of one side of the cube.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTextureCube(
|
||||
GraphicsDevice device,
|
||||
uint size,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
var textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = size,
|
||||
Height = size,
|
||||
Depth = 1,
|
||||
IsCube = true,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new texture using a TextureCreateInfo struct.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="textureCreateInfo">The parameters to use when creating the texture.</param>
|
||||
public Texture(
|
||||
GraphicsDevice device,
|
||||
in TextureCreateInfo textureCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateTexture(
|
||||
device.Handle,
|
||||
textureCreateInfo.ToRefreshTextureCreateInfo()
|
||||
);
|
||||
|
||||
Format = textureCreateInfo.Format;
|
||||
Width = textureCreateInfo.Width;
|
||||
Height = textureCreateInfo.Height;
|
||||
Depth = textureCreateInfo.Depth;
|
||||
IsCube = textureCreateInfo.IsCube;
|
||||
LevelCount = textureCreateInfo.LevelCount;
|
||||
SampleCount = textureCreateInfo.SampleCount;
|
||||
UsageFlags = textureCreateInfo.UsageFlags;
|
||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
||||
}
|
||||
|
||||
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
|
||||
|
||||
// Used by AcquireSwapchainTexture.
|
||||
// Should not be tracked, because swapchain textures are managed by Vulkan.
|
||||
internal Texture(
|
||||
GraphicsDevice device,
|
||||
TextureFormat format
|
||||
) : base(device)
|
||||
{
|
||||
Handle = IntPtr.Zero;
|
||||
|
||||
Format = format;
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
Depth = 1;
|
||||
IsCube = false;
|
||||
LevelCount = 1;
|
||||
SampleCount = SampleCount.One;
|
||||
UsageFlags = TextureUsageFlags.ColorTarget;
|
||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
||||
}
|
||||
|
||||
// DDS loading extension, based on MojoDDS
|
||||
// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194
|
||||
private static void ParseDDS(
|
||||
BinaryReader reader,
|
||||
out TextureFormat format,
|
||||
out int width,
|
||||
out int height,
|
||||
out int levels,
|
||||
out bool isCube
|
||||
) {
|
||||
// A whole bunch of magic numbers, yay DDS!
|
||||
const uint DDS_MAGIC = 0x20534444;
|
||||
const uint DDS_HEADERSIZE = 124;
|
||||
const uint DDS_PIXFMTSIZE = 32;
|
||||
const uint DDSD_HEIGHT = 0x2;
|
||||
const uint DDSD_WIDTH = 0x4;
|
||||
const uint DDSD_PITCH = 0x8;
|
||||
const uint DDSD_LINEARSIZE = 0x80000;
|
||||
const uint DDSD_REQ = (
|
||||
DDSD_HEIGHT | DDSD_WIDTH
|
||||
);
|
||||
const uint DDSCAPS_MIPMAP = 0x400000;
|
||||
const uint DDSCAPS_TEXTURE = 0x1000;
|
||||
const uint DDSCAPS2_CUBEMAP = 0x200;
|
||||
const uint DDPF_FOURCC = 0x4;
|
||||
const uint DDPF_RGB = 0x40;
|
||||
const uint FOURCC_DXT1 = 0x31545844;
|
||||
const uint FOURCC_DXT3 = 0x33545844;
|
||||
const uint FOURCC_DXT5 = 0x35545844;
|
||||
const uint FOURCC_DX10 = 0x30315844;
|
||||
const uint pitchAndLinear = (
|
||||
DDSD_PITCH | DDSD_LINEARSIZE
|
||||
);
|
||||
|
||||
// File should start with 'DDS '
|
||||
if (reader.ReadUInt32() != DDS_MAGIC)
|
||||
{
|
||||
throw new NotSupportedException("Not a DDS!");
|
||||
}
|
||||
|
||||
// Texture info
|
||||
uint size = reader.ReadUInt32();
|
||||
if (size != DDS_HEADERSIZE)
|
||||
{
|
||||
throw new NotSupportedException("Invalid DDS header!");
|
||||
}
|
||||
uint flags = reader.ReadUInt32();
|
||||
if ((flags & DDSD_REQ) != DDSD_REQ)
|
||||
{
|
||||
throw new NotSupportedException("Invalid DDS flags!");
|
||||
}
|
||||
if ((flags & pitchAndLinear) == pitchAndLinear)
|
||||
{
|
||||
throw new NotSupportedException("Invalid DDS flags!");
|
||||
}
|
||||
height = reader.ReadInt32();
|
||||
width = reader.ReadInt32();
|
||||
reader.ReadUInt32(); // dwPitchOrLinearSize, unused
|
||||
reader.ReadUInt32(); // dwDepth, unused
|
||||
levels = reader.ReadInt32();
|
||||
|
||||
// "Reserved"
|
||||
reader.ReadBytes(4 * 11);
|
||||
|
||||
// Format info
|
||||
uint formatSize = reader.ReadUInt32();
|
||||
if (formatSize != DDS_PIXFMTSIZE)
|
||||
{
|
||||
throw new NotSupportedException("Bogus PIXFMTSIZE!");
|
||||
}
|
||||
uint formatFlags = reader.ReadUInt32();
|
||||
uint formatFourCC = reader.ReadUInt32();
|
||||
uint formatRGBBitCount = reader.ReadUInt32();
|
||||
uint formatRBitMask = reader.ReadUInt32();
|
||||
uint formatGBitMask = reader.ReadUInt32();
|
||||
uint formatBBitMask = reader.ReadUInt32();
|
||||
uint formatABitMask = reader.ReadUInt32();
|
||||
|
||||
// dwCaps "stuff"
|
||||
uint caps = reader.ReadUInt32();
|
||||
if ((caps & DDSCAPS_TEXTURE) == 0)
|
||||
{
|
||||
throw new NotSupportedException("Not a texture!");
|
||||
}
|
||||
|
||||
isCube = false;
|
||||
|
||||
uint caps2 = reader.ReadUInt32();
|
||||
if (caps2 != 0)
|
||||
{
|
||||
if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP)
|
||||
{
|
||||
isCube = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Invalid caps2!");
|
||||
}
|
||||
}
|
||||
|
||||
reader.ReadUInt32(); // dwCaps3, unused
|
||||
reader.ReadUInt32(); // dwCaps4, unused
|
||||
|
||||
// "Reserved"
|
||||
reader.ReadUInt32();
|
||||
|
||||
// Mipmap sanity check
|
||||
if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP)
|
||||
{
|
||||
levels = 1;
|
||||
}
|
||||
|
||||
// Determine texture format
|
||||
if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC)
|
||||
{
|
||||
switch (formatFourCC)
|
||||
{
|
||||
case 0x71: // D3DFMT_A16B16G16R16F
|
||||
format = TextureFormat.R16G16B16A16_SFLOAT;
|
||||
break;
|
||||
case 0x74: // D3DFMT_A32B32G32R32F
|
||||
format = TextureFormat.R32G32B32A32_SFLOAT;
|
||||
break;
|
||||
case FOURCC_DXT1:
|
||||
format = TextureFormat.BC1;
|
||||
break;
|
||||
case FOURCC_DXT3:
|
||||
format = TextureFormat.BC2;
|
||||
break;
|
||||
case FOURCC_DXT5:
|
||||
format = TextureFormat.BC3;
|
||||
break;
|
||||
case FOURCC_DX10:
|
||||
// If the fourCC is DX10, there is an extra header with additional format information.
|
||||
uint dxgiFormat = reader.ReadUInt32();
|
||||
|
||||
// These values are taken from the DXGI_FORMAT enum.
|
||||
switch (dxgiFormat)
|
||||
{
|
||||
case 2:
|
||||
format = TextureFormat.R32G32B32A32_SFLOAT;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
format = TextureFormat.R16G16B16A16_SFLOAT;
|
||||
break;
|
||||
|
||||
case 71:
|
||||
format = TextureFormat.BC1;
|
||||
break;
|
||||
|
||||
case 74:
|
||||
format = TextureFormat.BC2;
|
||||
break;
|
||||
|
||||
case 77:
|
||||
format = TextureFormat.BC3;
|
||||
break;
|
||||
|
||||
case 98:
|
||||
format = TextureFormat.BC7;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"Unsupported DDS texture format"
|
||||
);
|
||||
}
|
||||
|
||||
uint resourceDimension = reader.ReadUInt32();
|
||||
|
||||
// These values are taken from the D3D10_RESOURCE_DIMENSION enum.
|
||||
switch (resourceDimension)
|
||||
{
|
||||
case 0: // Unknown
|
||||
case 1: // Buffer
|
||||
throw new NotSupportedException(
|
||||
"Unsupported DDS texture format"
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* This flag seemingly only indicates if the texture is a cube map.
|
||||
* This is already determined above. Cool!
|
||||
*/
|
||||
uint miscFlag = reader.ReadUInt32();
|
||||
|
||||
/*
|
||||
* Indicates the number of elements in the texture array.
|
||||
* We don't support texture arrays so just throw if it's greater than 1.
|
||||
*/
|
||||
uint arraySize = reader.ReadUInt32();
|
||||
|
||||
if (arraySize > 1)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Unsupported DDS texture format"
|
||||
);
|
||||
}
|
||||
|
||||
reader.ReadUInt32(); // reserved
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"Unsupported DDS texture format"
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ((formatFlags & DDPF_RGB) == DDPF_RGB)
|
||||
{
|
||||
if ( formatRGBBitCount != 32 ||
|
||||
formatRBitMask != 0x00FF0000 ||
|
||||
formatGBitMask != 0x0000FF00 ||
|
||||
formatBBitMask != 0x000000FF ||
|
||||
formatABitMask != 0xFF000000 )
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Unsupported DDS texture format"
|
||||
);
|
||||
}
|
||||
|
||||
format = TextureFormat.B8G8R8A8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Unsupported DDS texture format"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static int CalculateDDSLevelSize(
|
||||
int width,
|
||||
int height,
|
||||
TextureFormat format
|
||||
) {
|
||||
if (format == TextureFormat.R8G8B8A8)
|
||||
{
|
||||
return (((width * 32) + 7) / 8) * height;
|
||||
}
|
||||
else if (format == TextureFormat.R16G16B16A16_SFLOAT)
|
||||
{
|
||||
return (((width * 64) + 7) / 8) * height;
|
||||
}
|
||||
else if (format == TextureFormat.R32G32B32A32_SFLOAT)
|
||||
{
|
||||
return (((width * 128) + 7) / 8) * height;
|
||||
}
|
||||
else
|
||||
{
|
||||
int blockSize = 16;
|
||||
if (format == TextureFormat.BC1)
|
||||
{
|
||||
blockSize = 8;
|
||||
}
|
||||
width = System.Math.Max(width, 1);
|
||||
height = System.Math.Max(height, 1);
|
||||
return (
|
||||
((width + 3) / 4) *
|
||||
((height + 3) / 4) *
|
||||
blockSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously saves RGBA or BGRA pixel data to a file in PNG format. <br/>
|
||||
/// Warning: this is expensive and will block to wait for data download from GPU! <br/>
|
||||
/// You can avoid blocking by calling this method from a thread.
|
||||
/// </summary>
|
||||
public unsafe void SavePNG(string path)
|
||||
{
|
||||
#if DEBUG
|
||||
if (Format != TextureFormat.R8G8B8A8 && Format != TextureFormat.B8G8R8A8)
|
||||
{
|
||||
throw new ArgumentException("Texture format must be RGBA or BGRA!", "format");
|
||||
}
|
||||
#endif
|
||||
|
||||
var buffer = new Buffer(Device, 0, Width * Height * 4); // this creates garbage... oh well
|
||||
|
||||
// immediately request the data copy
|
||||
var commandBuffer = Device.AcquireCommandBuffer();
|
||||
commandBuffer.CopyTextureToBuffer(this, buffer);
|
||||
var fence = Device.SubmitAndAcquireFence(commandBuffer);
|
||||
|
||||
var byteCount = buffer.Size;
|
||||
|
||||
var pixelsPtr = NativeMemory.Alloc((nuint) byteCount);
|
||||
var pixelsSpan = new Span<byte>(pixelsPtr, (int) byteCount);
|
||||
|
||||
Device.WaitForFences(fence); // make sure the data transfer is done...
|
||||
Device.ReleaseFence(fence); // and then release the fence
|
||||
|
||||
buffer.GetData(pixelsSpan);
|
||||
|
||||
if (Format == TextureFormat.B8G8R8A8)
|
||||
{
|
||||
var rgbaPtr = NativeMemory.Alloc((nuint) byteCount);
|
||||
var rgbaSpan = new Span<byte>(rgbaPtr, (int) byteCount);
|
||||
|
||||
for (var i = 0; i < byteCount; i += 4)
|
||||
{
|
||||
rgbaSpan[i] = pixelsSpan[i + 2];
|
||||
rgbaSpan[i + 1] = pixelsSpan[i + 1];
|
||||
rgbaSpan[i + 2] = pixelsSpan[i];
|
||||
rgbaSpan[i + 3] = pixelsSpan[i + 3];
|
||||
}
|
||||
|
||||
Refresh.Refresh_Image_SavePNG(path, (nint) rgbaPtr, (int) Width, (int) Height);
|
||||
|
||||
NativeMemory.Free((void*) rgbaPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed (byte* ptr = pixelsSpan)
|
||||
{
|
||||
Refresh.Refresh_Image_SavePNG(path, (nint) ptr, (int) Width, (int) Height);
|
||||
}
|
||||
}
|
||||
|
||||
NativeMemory.Free(pixelsPtr);
|
||||
}
|
||||
|
||||
public static uint BytesPerPixel(TextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureFormat.R8:
|
||||
case TextureFormat.R8_UINT:
|
||||
return 1;
|
||||
case TextureFormat.R5G6B5:
|
||||
case TextureFormat.B4G4R4A4:
|
||||
case TextureFormat.A1R5G5B5:
|
||||
case TextureFormat.R16_SFLOAT:
|
||||
case TextureFormat.R8G8_SNORM:
|
||||
case TextureFormat.R8G8_UINT:
|
||||
case TextureFormat.R16_UINT:
|
||||
case TextureFormat.D16:
|
||||
return 2;
|
||||
case TextureFormat.D16S8:
|
||||
return 3;
|
||||
case TextureFormat.R8G8B8A8:
|
||||
case TextureFormat.B8G8R8A8:
|
||||
case TextureFormat.R32_SFLOAT:
|
||||
case TextureFormat.R16G16:
|
||||
case TextureFormat.R16G16_SFLOAT:
|
||||
case TextureFormat.R8G8B8A8_SNORM:
|
||||
case TextureFormat.A2R10G10B10:
|
||||
case TextureFormat.R8G8B8A8_UINT:
|
||||
case TextureFormat.R16G16_UINT:
|
||||
case TextureFormat.D32:
|
||||
return 4;
|
||||
case TextureFormat.D32S8:
|
||||
return 5;
|
||||
case TextureFormat.R16G16B16A16_SFLOAT:
|
||||
case TextureFormat.R16G16B16A16:
|
||||
case TextureFormat.R32G32_SFLOAT:
|
||||
case TextureFormat.R16G16B16A16_UINT:
|
||||
case TextureFormat.BC1:
|
||||
return 8;
|
||||
case TextureFormat.R32G32B32A32_SFLOAT:
|
||||
case TextureFormat.BC2:
|
||||
case TextureFormat.BC3:
|
||||
case TextureFormat.BC7:
|
||||
return 16;
|
||||
default:
|
||||
Logger.LogError("Texture format not recognized!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint BlockSizeSquared(TextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureFormat.BC1:
|
||||
case TextureFormat.BC2:
|
||||
case TextureFormat.BC3:
|
||||
case TextureFormat.BC7:
|
||||
return 16;
|
||||
case TextureFormat.R8G8B8A8:
|
||||
case TextureFormat.B8G8R8A8:
|
||||
case TextureFormat.R5G6B5:
|
||||
case TextureFormat.A1R5G5B5:
|
||||
case TextureFormat.B4G4R4A4:
|
||||
case TextureFormat.A2R10G10B10:
|
||||
case TextureFormat.R16G16:
|
||||
case TextureFormat.R16G16B16A16:
|
||||
case TextureFormat.R8:
|
||||
case TextureFormat.R8G8_SNORM:
|
||||
case TextureFormat.R8G8B8A8_SNORM:
|
||||
case TextureFormat.R16_SFLOAT:
|
||||
case TextureFormat.R16G16_SFLOAT:
|
||||
case TextureFormat.R16G16B16A16_SFLOAT:
|
||||
case TextureFormat.R32_SFLOAT:
|
||||
case TextureFormat.R32G32_SFLOAT:
|
||||
case TextureFormat.R32G32B32A32_SFLOAT:
|
||||
case TextureFormat.R8_UINT:
|
||||
case TextureFormat.R8G8_UINT:
|
||||
case TextureFormat.R8G8B8A8_UINT:
|
||||
case TextureFormat.R16_UINT:
|
||||
case TextureFormat.R16G16_UINT:
|
||||
case TextureFormat.R16G16B16A16_UINT:
|
||||
case TextureFormat.D16:
|
||||
case TextureFormat.D32:
|
||||
case TextureFormat.D16S8:
|
||||
case TextureFormat.D32S8:
|
||||
return 1;
|
||||
default:
|
||||
Logger.LogError("Texture format not recognized!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines how color blending will be performed in a GraphicsPipeline.
|
||||
/// </summary>
|
||||
public struct ColorAttachmentBlendState
|
||||
{
|
||||
/// <summary>
|
||||
/// If disabled, no blending will occur.
|
||||
/// </summary>
|
||||
public bool BlendEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend operation to use with alpha values.
|
||||
/// </summary>
|
||||
public BlendOp AlphaBlendOp;
|
||||
/// <summary>
|
||||
/// Selects which blend operation to use with color values.
|
||||
/// </summary>
|
||||
public BlendOp ColorBlendOp;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which of the RGBA components are enabled for writing.
|
||||
/// </summary>
|
||||
public ColorComponentFlags ColorWriteMask;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the alpha destination factor.
|
||||
/// </summary>
|
||||
public BlendFactor DestinationAlphaBlendFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the color destination factor.
|
||||
/// </summary>
|
||||
public BlendFactor DestinationColorBlendFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the alpha source factor.
|
||||
/// </summary>
|
||||
public BlendFactor SourceAlphaBlendFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the color source factor.
|
||||
/// </summary>
|
||||
public BlendFactor SourceColorBlendFactor;
|
||||
|
||||
public static readonly ColorAttachmentBlendState Additive = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.SourceAlpha,
|
||||
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
|
||||
DestinationColorBlendFactor = BlendFactor.One,
|
||||
DestinationAlphaBlendFactor = BlendFactor.One
|
||||
};
|
||||
|
||||
public static readonly ColorAttachmentBlendState AlphaBlend = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.One,
|
||||
SourceAlphaBlendFactor = BlendFactor.One,
|
||||
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
|
||||
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
|
||||
};
|
||||
|
||||
public static readonly ColorAttachmentBlendState NonPremultiplied = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.SourceAlpha,
|
||||
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
|
||||
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
|
||||
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
|
||||
};
|
||||
|
||||
public static readonly ColorAttachmentBlendState Opaque = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.One,
|
||||
SourceAlphaBlendFactor = BlendFactor.One,
|
||||
DestinationColorBlendFactor = BlendFactor.Zero,
|
||||
DestinationAlphaBlendFactor = BlendFactor.Zero
|
||||
};
|
||||
|
||||
public static readonly ColorAttachmentBlendState None = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = false,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA
|
||||
};
|
||||
|
||||
public static readonly ColorAttachmentBlendState Disable = new ColorAttachmentBlendState
|
||||
{
|
||||
BlendEnable = false,
|
||||
ColorWriteMask = ColorComponentFlags.None
|
||||
};
|
||||
|
||||
public Refresh.ColorAttachmentBlendState ToRefresh()
|
||||
{
|
||||
return new Refresh.ColorAttachmentBlendState
|
||||
{
|
||||
blendEnable = Conversions.BoolToByte(BlendEnable),
|
||||
alphaBlendOp = (Refresh.BlendOp) AlphaBlendOp,
|
||||
colorBlendOp = (Refresh.BlendOp) ColorBlendOp,
|
||||
colorWriteMask = (Refresh.ColorComponentFlags) ColorWriteMask,
|
||||
destinationAlphaBlendFactor = (Refresh.BlendFactor) DestinationAlphaBlendFactor,
|
||||
destinationColorBlendFactor = (Refresh.BlendFactor) DestinationColorBlendFactor,
|
||||
sourceAlphaBlendFactor = (Refresh.BlendFactor) SourceAlphaBlendFactor,
|
||||
sourceColorBlendFactor = (Refresh.BlendFactor) SourceColorBlendFactor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct ColorTargetBlendState
|
||||
{
|
||||
/// <summary>
|
||||
/// If disabled, no blending will occur.
|
||||
/// </summary>
|
||||
public bool BlendEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend operation to use with alpha values.
|
||||
/// </summary>
|
||||
public BlendOp AlphaBlendOp;
|
||||
/// <summary>
|
||||
/// Selects which blend operation to use with color values.
|
||||
/// </summary>
|
||||
public BlendOp ColorBlendOp;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which of the RGBA components are enabled for writing.
|
||||
/// </summary>
|
||||
public ColorComponentFlags ColorWriteMask;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the alpha destination factor.
|
||||
/// </summary>
|
||||
public BlendFactor DestinationAlphaBlendFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the color destination factor.
|
||||
/// </summary>
|
||||
public BlendFactor DestinationColorBlendFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the alpha source factor.
|
||||
/// </summary>
|
||||
public BlendFactor SourceAlphaBlendFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Selects which blend factor is used to determine the color source factor.
|
||||
/// </summary>
|
||||
public BlendFactor SourceColorBlendFactor;
|
||||
|
||||
public static readonly ColorTargetBlendState Additive = new ColorTargetBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.SourceAlpha,
|
||||
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
|
||||
DestinationColorBlendFactor = BlendFactor.One,
|
||||
DestinationAlphaBlendFactor = BlendFactor.One
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState AlphaBlend = new ColorTargetBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.One,
|
||||
SourceAlphaBlendFactor = BlendFactor.One,
|
||||
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
|
||||
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState NonPremultiplied = new ColorTargetBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.SourceAlpha,
|
||||
SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
|
||||
DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
|
||||
DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState Opaque = new ColorTargetBlendState
|
||||
{
|
||||
BlendEnable = true,
|
||||
AlphaBlendOp = BlendOp.Add,
|
||||
ColorBlendOp = BlendOp.Add,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA,
|
||||
SourceColorBlendFactor = BlendFactor.One,
|
||||
SourceAlphaBlendFactor = BlendFactor.One,
|
||||
DestinationColorBlendFactor = BlendFactor.Zero,
|
||||
DestinationAlphaBlendFactor = BlendFactor.Zero
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState None = new ColorTargetBlendState
|
||||
{
|
||||
BlendEnable = false,
|
||||
ColorWriteMask = ColorComponentFlags.RGBA
|
||||
};
|
||||
|
||||
public static readonly ColorTargetBlendState Disable = new ColorTargetBlendState
|
||||
{
|
||||
BlendEnable = false,
|
||||
ColorWriteMask = ColorComponentFlags.None
|
||||
};
|
||||
|
||||
public Refresh.ColorTargetBlendState ToRefreshColorTargetBlendState()
|
||||
{
|
||||
return new Refresh.ColorTargetBlendState
|
||||
{
|
||||
blendEnable = Conversions.BoolToByte(BlendEnable),
|
||||
alphaBlendOp = (Refresh.BlendOp)AlphaBlendOp,
|
||||
colorBlendOp = (Refresh.BlendOp)ColorBlendOp,
|
||||
colorWriteMask = (Refresh.ColorComponentFlags)ColorWriteMask,
|
||||
destinationAlphaBlendFactor = (Refresh.BlendFactor)DestinationAlphaBlendFactor,
|
||||
destinationColorBlendFactor = (Refresh.BlendFactor)DestinationColorBlendFactor,
|
||||
sourceAlphaBlendFactor = (Refresh.BlendFactor)SourceAlphaBlendFactor,
|
||||
sourceColorBlendFactor = (Refresh.BlendFactor)SourceColorBlendFactor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Information that the compute pipeline needs about a compute shader.
|
||||
/// </summary>
|
||||
public struct ComputeShaderInfo
|
||||
{
|
||||
public ShaderModule ShaderModule;
|
||||
public string EntryPointName;
|
||||
public uint UniformBufferSize;
|
||||
public uint BufferBindingCount;
|
||||
public uint ImageBindingCount;
|
||||
|
||||
public unsafe static ComputeShaderInfo Create<T>(
|
||||
ShaderModule shaderModule,
|
||||
string entryPointName,
|
||||
uint bufferBindingCount,
|
||||
uint imageBindingCount
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new ComputeShaderInfo
|
||||
{
|
||||
ShaderModule = shaderModule,
|
||||
EntryPointName = entryPointName,
|
||||
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
|
||||
BufferBindingCount = bufferBindingCount,
|
||||
ImageBindingCount = imageBindingCount
|
||||
};
|
||||
}
|
||||
|
||||
public static ComputeShaderInfo Create(
|
||||
ShaderModule shaderModule,
|
||||
string entryPointName,
|
||||
uint bufferBindingCount,
|
||||
uint imageBindingCount
|
||||
)
|
||||
{
|
||||
return new ComputeShaderInfo
|
||||
{
|
||||
ShaderModule = shaderModule,
|
||||
EntryPointName = entryPointName,
|
||||
UniformBufferSize = 0,
|
||||
BufferBindingCount = bufferBindingCount,
|
||||
ImageBindingCount = imageBindingCount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,79 +1,79 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines how data is written to and read from the depth/stencil buffer.
|
||||
/// </summary>
|
||||
public struct DepthStencilState
|
||||
{
|
||||
/// <summary>
|
||||
/// If disabled, no depth culling will occur.
|
||||
/// </summary>
|
||||
public bool DepthTestEnable;
|
||||
/// <summary>
|
||||
/// Determines how data is written to and read from the depth/stencil buffer.
|
||||
/// </summary>
|
||||
public struct DepthStencilState
|
||||
{
|
||||
/// <summary>
|
||||
/// If disabled, no depth culling will occur.
|
||||
/// </summary>
|
||||
public bool DepthTestEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the stencil operation for back-facing primitives.
|
||||
/// </summary>
|
||||
public StencilOpState BackStencilState;
|
||||
/// <summary>
|
||||
/// Describes the stencil operation for back-facing primitives.
|
||||
/// </summary>
|
||||
public StencilOpState BackStencilState;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the stencil operation for front-facing primitives.
|
||||
/// </summary>
|
||||
public StencilOpState FrontStencilState;
|
||||
/// <summary>
|
||||
/// Describes the stencil operation for front-facing primitives.
|
||||
/// </summary>
|
||||
public StencilOpState FrontStencilState;
|
||||
|
||||
/// <summary>
|
||||
/// The comparison operator used in the depth test.
|
||||
/// </summary>
|
||||
public CompareOp CompareOp;
|
||||
/// <summary>
|
||||
/// The comparison operator used in the depth test.
|
||||
/// </summary>
|
||||
public CompareOp CompareOp;
|
||||
|
||||
/// <summary>
|
||||
/// If depth lies outside of these bounds the pixel will be culled.
|
||||
/// </summary>
|
||||
public bool DepthBoundsTestEnable;
|
||||
/// <summary>
|
||||
/// If depth lies outside of these bounds the pixel will be culled.
|
||||
/// </summary>
|
||||
public bool DepthBoundsTestEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether depth values will be written to the buffer during rendering.
|
||||
/// </summary>
|
||||
public bool DepthWriteEnable;
|
||||
/// <summary>
|
||||
/// Specifies whether depth values will be written to the buffer during rendering.
|
||||
/// </summary>
|
||||
public bool DepthWriteEnable;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum depth value in the depth bounds test.
|
||||
/// </summary>
|
||||
public float MinDepthBounds;
|
||||
/// <summary>
|
||||
/// The minimum depth value in the depth bounds test.
|
||||
/// </summary>
|
||||
public float MinDepthBounds;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum depth value in the depth bounds test.
|
||||
/// </summary>
|
||||
public float MaxDepthBounds;
|
||||
/// <summary>
|
||||
/// The maximum depth value in the depth bounds test.
|
||||
/// </summary>
|
||||
public float MaxDepthBounds;
|
||||
|
||||
/// <summary>
|
||||
/// If disabled, no stencil culling will occur.
|
||||
/// </summary>
|
||||
public bool StencilTestEnable;
|
||||
/// <summary>
|
||||
/// If disabled, no stencil culling will occur.
|
||||
/// </summary>
|
||||
public bool StencilTestEnable;
|
||||
|
||||
public static readonly DepthStencilState DepthReadWrite = new DepthStencilState
|
||||
{
|
||||
DepthTestEnable = true,
|
||||
DepthWriteEnable = true,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = false,
|
||||
CompareOp = CompareOp.LessOrEqual
|
||||
};
|
||||
public static readonly DepthStencilState DepthReadWrite = new DepthStencilState
|
||||
{
|
||||
DepthTestEnable = true,
|
||||
DepthWriteEnable = true,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = false,
|
||||
CompareOp = CompareOp.LessOrEqual
|
||||
};
|
||||
|
||||
public static readonly DepthStencilState DepthRead = new DepthStencilState
|
||||
{
|
||||
DepthTestEnable = true,
|
||||
DepthWriteEnable = false,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = false,
|
||||
CompareOp = CompareOp.LessOrEqual
|
||||
};
|
||||
public static readonly DepthStencilState DepthRead = new DepthStencilState
|
||||
{
|
||||
DepthTestEnable = true,
|
||||
DepthWriteEnable = false,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = false,
|
||||
CompareOp = CompareOp.LessOrEqual
|
||||
};
|
||||
|
||||
public static readonly DepthStencilState Disable = new DepthStencilState
|
||||
{
|
||||
DepthTestEnable = false,
|
||||
DepthWriteEnable = false,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = false
|
||||
};
|
||||
}
|
||||
public static readonly DepthStencilState Disable = new DepthStencilState
|
||||
{
|
||||
DepthTestEnable = false,
|
||||
DepthWriteEnable = false,
|
||||
DepthBoundsTestEnable = false,
|
||||
StencilTestEnable = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the kind of attachments that will be used with this pipeline.
|
||||
/// </summary>
|
||||
public struct GraphicsPipelineAttachmentInfo
|
||||
{
|
||||
public ColorAttachmentDescription[] ColorAttachmentDescriptions;
|
||||
public bool HasDepthStencilAttachment;
|
||||
public TextureFormat DepthStencilFormat;
|
||||
|
||||
public GraphicsPipelineAttachmentInfo(
|
||||
params ColorAttachmentDescription[] colorAttachmentDescriptions
|
||||
) {
|
||||
ColorAttachmentDescriptions = colorAttachmentDescriptions;
|
||||
HasDepthStencilAttachment = false;
|
||||
DepthStencilFormat = TextureFormat.D16;
|
||||
}
|
||||
|
||||
public GraphicsPipelineAttachmentInfo(
|
||||
TextureFormat depthStencilFormat,
|
||||
params ColorAttachmentDescription[] colorAttachmentDescriptions
|
||||
) {
|
||||
ColorAttachmentDescriptions = colorAttachmentDescriptions;
|
||||
HasDepthStencilAttachment = true;
|
||||
DepthStencilFormat = depthStencilFormat;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,18 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct GraphicsPipelineCreateInfo
|
||||
{
|
||||
public ColorBlendState ColorBlendState;
|
||||
public DepthStencilState DepthStencilState;
|
||||
public ShaderStageState VertexShaderState;
|
||||
public ShaderStageState FragmentShaderState;
|
||||
public MultisampleState MultisampleState;
|
||||
public GraphicsPipelineLayoutInfo PipelineLayoutInfo;
|
||||
public RasterizerState RasterizerState;
|
||||
public PrimitiveType PrimitiveType;
|
||||
public VertexInputState VertexInputState;
|
||||
public ViewportState ViewportState;
|
||||
public RenderPass RenderPass;
|
||||
}
|
||||
/// <summary>
|
||||
/// All of the information that is used to create a GraphicsPipeline.
|
||||
/// </summary>
|
||||
public struct GraphicsPipelineCreateInfo
|
||||
{
|
||||
public DepthStencilState DepthStencilState;
|
||||
public GraphicsShaderInfo VertexShaderInfo;
|
||||
public GraphicsShaderInfo FragmentShaderInfo;
|
||||
public MultisampleState MultisampleState;
|
||||
public RasterizerState RasterizerState;
|
||||
public PrimitiveType PrimitiveType;
|
||||
public VertexInputState VertexInputState;
|
||||
public GraphicsPipelineAttachmentInfo AttachmentInfo;
|
||||
public BlendConstants BlendConstants;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes how many samplers will be used in each shader stage.
|
||||
/// </summary>
|
||||
public struct GraphicsPipelineLayoutInfo
|
||||
{
|
||||
public uint VertexSamplerBindingCount;
|
||||
public uint FragmentSamplerBindingCount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Information that the pipeline needs about a graphics shader.
|
||||
/// </summary>
|
||||
public struct GraphicsShaderInfo
|
||||
{
|
||||
public ShaderModule ShaderModule;
|
||||
public string EntryPointName;
|
||||
public uint UniformBufferSize;
|
||||
public uint SamplerBindingCount;
|
||||
|
||||
public unsafe static GraphicsShaderInfo Create<T>(
|
||||
ShaderModule shaderModule,
|
||||
string entryPointName,
|
||||
uint samplerBindingCount
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new GraphicsShaderInfo
|
||||
{
|
||||
ShaderModule = shaderModule,
|
||||
EntryPointName = entryPointName,
|
||||
UniformBufferSize = (uint) Marshal.SizeOf<T>(),
|
||||
SamplerBindingCount = samplerBindingCount
|
||||
};
|
||||
}
|
||||
|
||||
public static GraphicsShaderInfo Create(
|
||||
ShaderModule shaderModule,
|
||||
string entryPointName,
|
||||
uint samplerBindingCount
|
||||
) {
|
||||
return new GraphicsShaderInfo
|
||||
{
|
||||
ShaderModule = shaderModule,
|
||||
EntryPointName = entryPointName,
|
||||
UniformBufferSize = 0,
|
||||
SamplerBindingCount = samplerBindingCount
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,25 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how many samples should be used in rasterization.
|
||||
/// </summary>
|
||||
public struct MultisampleState
|
||||
{
|
||||
public SampleCount MultisampleCount;
|
||||
public uint SampleMask;
|
||||
/// <summary>
|
||||
/// Specifies how many samples should be used in rasterization.
|
||||
/// </summary>
|
||||
public struct MultisampleState
|
||||
{
|
||||
public SampleCount MultisampleCount;
|
||||
public uint SampleMask;
|
||||
|
||||
public static readonly MultisampleState None = new MultisampleState
|
||||
{
|
||||
MultisampleCount = SampleCount.One,
|
||||
SampleMask = uint.MaxValue
|
||||
};
|
||||
}
|
||||
public static readonly MultisampleState None = new MultisampleState
|
||||
{
|
||||
MultisampleCount = SampleCount.One,
|
||||
SampleMask = uint.MaxValue
|
||||
};
|
||||
|
||||
public MultisampleState(
|
||||
SampleCount sampleCount,
|
||||
uint sampleMask = uint.MaxValue
|
||||
) {
|
||||
MultisampleCount = sampleCount;
|
||||
SampleMask = sampleMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,121 +1,107 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how the rasterizer should be configured for a graphics pipeline.
|
||||
/// </summary>
|
||||
public struct RasterizerState
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies whether front faces, back faces, none, or both should be culled.
|
||||
/// </summary>
|
||||
public CullMode CullMode;
|
||||
/// <summary>
|
||||
/// Specifies how the rasterizer should be configured for a graphics pipeline.
|
||||
/// </summary>
|
||||
public struct RasterizerState
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies whether front faces, back faces, none, or both should be culled.
|
||||
/// </summary>
|
||||
public CullMode CullMode;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies maximum depth bias of a fragment. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public float DepthBiasClamp;
|
||||
/// <summary>
|
||||
/// Specifies maximum depth bias of a fragment. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public float DepthBiasClamp;
|
||||
|
||||
/// <summary>
|
||||
/// The constant depth value added to each fragment. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public float DepthBiasConstantFactor;
|
||||
/// <summary>
|
||||
/// The constant depth value added to each fragment. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public float DepthBiasConstantFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether depth biasing is enabled. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public bool DepthBiasEnable;
|
||||
/// <summary>
|
||||
/// Specifies whether depth biasing is enabled. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public bool DepthBiasEnable;
|
||||
|
||||
/// <summary>
|
||||
/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public float DepthBiasSlopeFactor;
|
||||
public bool DepthClampEnable;
|
||||
/// <summary>
|
||||
/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
|
||||
/// </summary>
|
||||
public float DepthBiasSlopeFactor;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how triangles should be drawn.
|
||||
/// </summary>
|
||||
public FillMode FillMode;
|
||||
/// <summary>
|
||||
/// Specifies how triangles should be drawn.
|
||||
/// </summary>
|
||||
public FillMode FillMode;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which triangle winding order is designated as front-facing.
|
||||
/// </summary>
|
||||
public FrontFace FrontFace;
|
||||
/// <summary>
|
||||
/// Specifies which triangle winding order is designated as front-facing.
|
||||
/// </summary>
|
||||
public FrontFace FrontFace;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the width of the line rendering in terms of pixels.
|
||||
/// </summary>
|
||||
public float LineWidth;
|
||||
public static readonly RasterizerState CW_CullFront = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Front,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_CullFront = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Front,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
public static readonly RasterizerState CW_CullBack = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Back,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_CullBack = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Back,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
public static readonly RasterizerState CW_CullNone = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_CullNone = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
public static readonly RasterizerState CW_Wireframe = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Line,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CW_Wireframe = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.Clockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
public static readonly RasterizerState CCW_CullFront = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Front,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_CullFront = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Front,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
public static readonly RasterizerState CCW_CullBack = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Back,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_CullBack = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.Back,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
public static readonly RasterizerState CCW_CullNone = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_CullNone = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
|
||||
public static readonly RasterizerState CCW_Wireframe = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Fill,
|
||||
DepthBiasEnable = false,
|
||||
LineWidth = 1f
|
||||
};
|
||||
}
|
||||
public static readonly RasterizerState CCW_Wireframe = new RasterizerState
|
||||
{
|
||||
CullMode = CullMode.None,
|
||||
FrontFace = FrontFace.CounterClockwise,
|
||||
FillMode = FillMode.Line,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,134 +2,173 @@
|
|||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct SamplerCreateInfo
|
||||
{
|
||||
public Filter MinFilter;
|
||||
public Filter MagFilter;
|
||||
public SamplerMipmapMode MipmapMode;
|
||||
public SamplerAddressMode AddressModeU;
|
||||
public SamplerAddressMode AddressModeV;
|
||||
public SamplerAddressMode AddressModeW;
|
||||
public float MipLodBias;
|
||||
public bool AnisotropyEnable;
|
||||
public float MaxAnisotropy;
|
||||
public bool CompareEnable;
|
||||
public CompareOp CompareOp;
|
||||
public float MinLod;
|
||||
public float MaxLod;
|
||||
public BorderColor BorderColor;
|
||||
/// <summary>
|
||||
/// All of the information that is used to create a sampler.
|
||||
/// </summary>
|
||||
public struct SamplerCreateInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Minification filter mode. Used when the image is downscaled.
|
||||
/// </summary>
|
||||
public Filter MinFilter;
|
||||
/// <summary>
|
||||
/// Magnification filter mode. Used when the image is upscaled.
|
||||
/// </summary>
|
||||
public Filter MagFilter;
|
||||
/// <summary>
|
||||
/// Filter mode applied to mipmap lookups.
|
||||
/// </summary>
|
||||
public SamplerMipmapMode MipmapMode;
|
||||
/// <summary>
|
||||
/// Horizontal addressing mode.
|
||||
/// </summary>
|
||||
public SamplerAddressMode AddressModeU;
|
||||
/// <summary>
|
||||
/// Vertical addressing mode.
|
||||
/// </summary>
|
||||
public SamplerAddressMode AddressModeV;
|
||||
/// <summary>
|
||||
/// Depth addressing mode.
|
||||
/// </summary>
|
||||
public SamplerAddressMode AddressModeW;
|
||||
/// <summary>
|
||||
/// Bias value added to mipmap level of detail calculation.
|
||||
/// </summary>
|
||||
public float MipLodBias;
|
||||
/// <summary>
|
||||
/// Enables anisotropic filtering.
|
||||
/// </summary>
|
||||
public bool AnisotropyEnable;
|
||||
/// <summary>
|
||||
/// Maximum anisotropy value.
|
||||
/// </summary>
|
||||
public float MaxAnisotropy;
|
||||
public bool CompareEnable;
|
||||
public CompareOp CompareOp;
|
||||
/// <summary>
|
||||
/// Clamps the LOD value to a specified minimum.
|
||||
/// </summary>
|
||||
public float MinLod;
|
||||
/// <summary>
|
||||
/// Clamps the LOD value to a specified maximum.
|
||||
/// </summary>
|
||||
public float MaxLod;
|
||||
/// <summary>
|
||||
/// If an address mode is set to ClampToBorder, will replace color with this color when samples are outside the [0, 1) range.
|
||||
/// </summary>
|
||||
public BorderColor BorderColor;
|
||||
|
||||
public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeV = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeW = SamplerAddressMode.ClampToEdge,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = true,
|
||||
MaxAnisotropy = 4,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
|
||||
};
|
||||
public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeV = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeW = SamplerAddressMode.ClampToEdge,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = true,
|
||||
MaxAnisotropy = 4,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
|
||||
};
|
||||
|
||||
public static readonly SamplerCreateInfo AnisotropicWrap = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = true,
|
||||
MaxAnisotropy = 4,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
|
||||
};
|
||||
public static readonly SamplerCreateInfo AnisotropicWrap = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = true,
|
||||
MaxAnisotropy = 4,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
|
||||
};
|
||||
|
||||
public static readonly SamplerCreateInfo LinearClamp = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeV = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeW = SamplerAddressMode.ClampToEdge,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
public static readonly SamplerCreateInfo LinearClamp = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeV = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeW = SamplerAddressMode.ClampToEdge,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
|
||||
public static readonly SamplerCreateInfo LinearWrap = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
public static readonly SamplerCreateInfo LinearWrap = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Linear,
|
||||
MagFilter = Filter.Linear,
|
||||
MipmapMode = SamplerMipmapMode.Linear,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
|
||||
public static readonly SamplerCreateInfo PointClamp = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Nearest,
|
||||
MagFilter = Filter.Nearest,
|
||||
MipmapMode = SamplerMipmapMode.Nearest,
|
||||
AddressModeU = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeV = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeW = SamplerAddressMode.ClampToEdge,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
public static readonly SamplerCreateInfo PointClamp = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Nearest,
|
||||
MagFilter = Filter.Nearest,
|
||||
MipmapMode = SamplerMipmapMode.Nearest,
|
||||
AddressModeU = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeV = SamplerAddressMode.ClampToEdge,
|
||||
AddressModeW = SamplerAddressMode.ClampToEdge,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
|
||||
public static readonly SamplerCreateInfo PointWrap = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Nearest,
|
||||
MagFilter = Filter.Nearest,
|
||||
MipmapMode = SamplerMipmapMode.Nearest,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
public static readonly SamplerCreateInfo PointWrap = new SamplerCreateInfo
|
||||
{
|
||||
MinFilter = Filter.Nearest,
|
||||
MagFilter = Filter.Nearest,
|
||||
MipmapMode = SamplerMipmapMode.Nearest,
|
||||
AddressModeU = SamplerAddressMode.Repeat,
|
||||
AddressModeV = SamplerAddressMode.Repeat,
|
||||
AddressModeW = SamplerAddressMode.Repeat,
|
||||
CompareEnable = false,
|
||||
AnisotropyEnable = false,
|
||||
MipLodBias = 0f,
|
||||
MinLod = 0,
|
||||
MaxLod = 1000
|
||||
};
|
||||
|
||||
public Refresh.SamplerStateCreateInfo ToRefreshSamplerStateCreateInfo()
|
||||
{
|
||||
return new Refresh.SamplerStateCreateInfo
|
||||
{
|
||||
minFilter = (Refresh.Filter)MinFilter,
|
||||
magFilter = (Refresh.Filter)MagFilter,
|
||||
mipmapMode = (Refresh.SamplerMipmapMode)MipmapMode,
|
||||
addressModeU = (Refresh.SamplerAddressMode)AddressModeU,
|
||||
addressModeV = (Refresh.SamplerAddressMode)AddressModeV,
|
||||
addressModeW = (Refresh.SamplerAddressMode)AddressModeW,
|
||||
mipLodBias = MipLodBias,
|
||||
anisotropyEnable = Conversions.BoolToByte(AnisotropyEnable),
|
||||
maxAnisotropy = MaxAnisotropy,
|
||||
compareEnable = Conversions.BoolToByte(CompareEnable),
|
||||
compareOp = (Refresh.CompareOp)CompareOp,
|
||||
minLod = MinLod,
|
||||
maxLod = MaxLod,
|
||||
borderColor = (Refresh.BorderColor)BorderColor
|
||||
};
|
||||
}
|
||||
}
|
||||
public Refresh.SamplerStateCreateInfo ToRefreshSamplerStateCreateInfo()
|
||||
{
|
||||
return new Refresh.SamplerStateCreateInfo
|
||||
{
|
||||
minFilter = (Refresh.Filter) MinFilter,
|
||||
magFilter = (Refresh.Filter) MagFilter,
|
||||
mipmapMode = (Refresh.SamplerMipmapMode) MipmapMode,
|
||||
addressModeU = (Refresh.SamplerAddressMode) AddressModeU,
|
||||
addressModeV = (Refresh.SamplerAddressMode) AddressModeV,
|
||||
addressModeW = (Refresh.SamplerAddressMode) AddressModeW,
|
||||
mipLodBias = MipLodBias,
|
||||
anisotropyEnable = Conversions.BoolToByte(AnisotropyEnable),
|
||||
maxAnisotropy = MaxAnisotropy,
|
||||
compareEnable = Conversions.BoolToByte(CompareEnable),
|
||||
compareOp = (Refresh.CompareOp) CompareOp,
|
||||
minLod = MinLod,
|
||||
maxLod = MaxLod,
|
||||
borderColor = (Refresh.BorderColor) BorderColor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -2,30 +2,33 @@
|
|||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct TextureCreateInfo
|
||||
{
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
public uint Depth;
|
||||
public bool IsCube;
|
||||
public SampleCount SampleCount;
|
||||
public uint LevelCount;
|
||||
public TextureFormat Format;
|
||||
public TextureUsageFlags UsageFlags;
|
||||
/// <summary>
|
||||
/// All of the information that is used to create a texture.
|
||||
/// </summary>
|
||||
public struct TextureCreateInfo
|
||||
{
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
public uint Depth;
|
||||
public bool IsCube;
|
||||
public uint LevelCount;
|
||||
public SampleCount SampleCount;
|
||||
public TextureFormat Format;
|
||||
public TextureUsageFlags UsageFlags;
|
||||
|
||||
public Refresh.TextureCreateInfo ToRefreshTextureCreateInfo()
|
||||
{
|
||||
return new Refresh.TextureCreateInfo
|
||||
{
|
||||
width = Width,
|
||||
height = Height,
|
||||
depth = Depth,
|
||||
isCube = Conversions.BoolToByte(IsCube),
|
||||
sampleCount = (Refresh.SampleCount) SampleCount,
|
||||
levelCount = LevelCount,
|
||||
format = (Refresh.TextureFormat) Format,
|
||||
usageFlags = (Refresh.TextureUsageFlags) UsageFlags
|
||||
};
|
||||
}
|
||||
}
|
||||
public Refresh.TextureCreateInfo ToRefreshTextureCreateInfo()
|
||||
{
|
||||
return new Refresh.TextureCreateInfo
|
||||
{
|
||||
width = Width,
|
||||
height = Height,
|
||||
depth = Depth,
|
||||
isCube = Conversions.BoolToByte(IsCube),
|
||||
levelCount = LevelCount,
|
||||
sampleCount = (Refresh.SampleCount) SampleCount,
|
||||
format = (Refresh.TextureFormat) Format,
|
||||
usageFlags = (Refresh.TextureUsageFlags) UsageFlags
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,70 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how to interpet vertex data in a buffer to be passed to the vertex shader.
|
||||
/// </summary>
|
||||
public struct VertexInputState
|
||||
{
|
||||
public VertexBinding[] VertexBindings;
|
||||
public VertexAttribute[] VertexAttributes;
|
||||
}
|
||||
/// <summary>
|
||||
/// Specifies how the vertex shader will interpet vertex data in a buffer.
|
||||
/// </summary>
|
||||
public struct VertexInputState
|
||||
{
|
||||
public VertexBinding[] VertexBindings;
|
||||
public VertexAttribute[] VertexAttributes;
|
||||
|
||||
public static readonly VertexInputState Empty = new VertexInputState
|
||||
{
|
||||
VertexBindings = System.Array.Empty<VertexBinding>(),
|
||||
VertexAttributes = System.Array.Empty<VertexAttribute>()
|
||||
};
|
||||
|
||||
public VertexInputState(
|
||||
VertexBinding vertexBinding,
|
||||
VertexAttribute[] vertexAttributes
|
||||
) {
|
||||
VertexBindings = new VertexBinding[] { vertexBinding };
|
||||
VertexAttributes = vertexAttributes;
|
||||
}
|
||||
|
||||
public VertexInputState(
|
||||
VertexBinding[] vertexBindings,
|
||||
VertexAttribute[] vertexAttributes
|
||||
) {
|
||||
VertexBindings = vertexBindings;
|
||||
VertexAttributes = vertexAttributes;
|
||||
}
|
||||
|
||||
public VertexInputState(
|
||||
VertexBindingAndAttributes bindingAndAttributes
|
||||
) {
|
||||
VertexBindings = new VertexBinding[] { bindingAndAttributes.VertexBinding };
|
||||
VertexAttributes = bindingAndAttributes.VertexAttributes;
|
||||
}
|
||||
|
||||
public VertexInputState(
|
||||
VertexBindingAndAttributes[] bindingAndAttributesArray
|
||||
) {
|
||||
VertexBindings = new VertexBinding[bindingAndAttributesArray.Length];
|
||||
var attributesLength = 0;
|
||||
|
||||
for (var i = 0; i < bindingAndAttributesArray.Length; i += 1)
|
||||
{
|
||||
VertexBindings[i] = bindingAndAttributesArray[i].VertexBinding;
|
||||
attributesLength += bindingAndAttributesArray[i].VertexAttributes.Length;
|
||||
}
|
||||
|
||||
VertexAttributes = new VertexAttribute[attributesLength];
|
||||
|
||||
var attributeIndex = 0;
|
||||
for (var i = 0; i < bindingAndAttributesArray.Length; i += 1)
|
||||
{
|
||||
for (var j = 0; j < bindingAndAttributesArray[i].VertexAttributes.Length; j += 1)
|
||||
{
|
||||
VertexAttributes[attributeIndex] = bindingAndAttributesArray[i].VertexAttributes[j];
|
||||
attributeIndex += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static VertexInputState CreateSingleBinding<T>() where T : unmanaged, IVertexType
|
||||
{
|
||||
return new VertexInputState(VertexBindingAndAttributes.Create<T>(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,34 @@
|
|||
#version 450
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2D msdf;
|
||||
|
||||
layout(location = 0) in vec2 inTexCoord;
|
||||
layout(location = 1) in vec4 inColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(binding = 0, set = 3) uniform UBO
|
||||
{
|
||||
float pxRange;
|
||||
} ubo;
|
||||
|
||||
float median(float r, float g, float b)
|
||||
{
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
float screenPxRange()
|
||||
{
|
||||
vec2 unitRange = vec2(ubo.pxRange)/vec2(textureSize(msdf, 0));
|
||||
vec2 screenTexSize = vec2(1.0)/fwidth(inTexCoord);
|
||||
return max(0.5*dot(unitRange, screenTexSize), 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 msd = texture(msdf, inTexCoord).rgb;
|
||||
float sd = median(msd.r, msd.g, msd.b);
|
||||
float screenPxDistance = screenPxRange() * (sd - 0.5);
|
||||
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||
outColor = mix(vec4(0.0, 0.0, 0.0, 0.0), inColor, opacity);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 inPos;
|
||||
layout(location = 1) in vec2 inTexCoord;
|
||||
layout(location = 2) in vec4 inColor;
|
||||
|
||||
layout(location = 0) out vec2 outTexCoord;
|
||||
layout(location = 1) out vec4 outColor;
|
||||
|
||||
layout(binding = 0, set = 2) uniform UBO
|
||||
{
|
||||
mat4 ViewProjection;
|
||||
} ubo;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ubo.ViewProjection * vec4(inPos, 1.0);
|
||||
outTexCoord = inTexCoord;
|
||||
outColor = inColor;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#version 450
|
||||
|
||||
layout(location = 0) out vec2 outTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(outTexCoord * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* This effect is based on the YUV-to-RGBA GLSL shader found in SDL.
|
||||
* Thus, it also released under the zlib license:
|
||||
* http://libsdl.org/license.php
|
||||
*/
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
layout(binding = 0, set = 1) uniform sampler2D YSampler;
|
||||
layout(binding = 1, set = 1) uniform sampler2D USampler;
|
||||
layout(binding = 2, set = 1) uniform sampler2D VSampler;
|
||||
|
||||
/* More info about colorspace conversion:
|
||||
* http://www.equasys.de/colorconversion.html
|
||||
* http://www.equasys.de/colorformat.html
|
||||
*/
|
||||
|
||||
const vec3 offset = vec3(-0.0625, -0.5, -0.5);
|
||||
const vec3 Rcoeff = vec3(1.164, 0.000, 1.793);
|
||||
const vec3 Gcoeff = vec3(1.164, -0.213, -0.533);
|
||||
const vec3 Bcoeff = vec3(1.164, 2.112, 0.000);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 yuv;
|
||||
yuv.x = texture(YSampler, TexCoord).r;
|
||||
yuv.y = texture(USampler, TexCoord).r;
|
||||
yuv.z = texture(VSampler, TexCoord).r;
|
||||
yuv += offset;
|
||||
|
||||
FragColor.r = dot(yuv, Rcoeff);
|
||||
FragColor.g = dot(yuv, Gcoeff);
|
||||
FragColor.b = dot(yuv, Bcoeff);
|
||||
FragColor.a = 1.0;
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
namespace MoonWorks.Graphics
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
public struct BlendConstants
|
||||
{
|
||||
public float R;
|
||||
public float G;
|
||||
public float B;
|
||||
public float A;
|
||||
}
|
||||
public struct BlendConstants
|
||||
{
|
||||
public float R;
|
||||
public float G;
|
||||
public float B;
|
||||
public float A;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,54 +2,56 @@
|
|||
|
||||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A texture slice specifies a subregion of a texture.
|
||||
/// Many operations can use texture slices in place of textures for the sake of convenience.
|
||||
/// </summary>
|
||||
public struct TextureSlice
|
||||
{
|
||||
public Texture Texture { get; }
|
||||
public Rect Rectangle { get; }
|
||||
public uint Depth { get; }
|
||||
public uint Layer { get; }
|
||||
public uint Level { get; }
|
||||
/// <summary>
|
||||
/// A texture slice specifies a subregion of a texture.
|
||||
/// Many operations can use texture slices in place of textures for the sake of convenience.
|
||||
/// </summary>
|
||||
public struct TextureSlice
|
||||
{
|
||||
public Texture Texture { get; }
|
||||
public Rect Rectangle { get; }
|
||||
public uint Depth { get; }
|
||||
public uint Layer { get; }
|
||||
public uint Level { get; }
|
||||
|
||||
public TextureSlice(Texture texture)
|
||||
{
|
||||
Texture = texture;
|
||||
Rectangle = new Rect
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
W = (int) texture.Width,
|
||||
H = (int) texture.Height
|
||||
};
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
}
|
||||
public uint Size => (uint) (Rectangle.W * Rectangle.H * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format));
|
||||
|
||||
public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0)
|
||||
{
|
||||
Texture = texture;
|
||||
Rectangle = rectangle;
|
||||
Depth = depth;
|
||||
Layer = layer;
|
||||
Level = level;
|
||||
}
|
||||
public TextureSlice(Texture texture)
|
||||
{
|
||||
Texture = texture;
|
||||
Rectangle = new Rect
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
W = (int) texture.Width,
|
||||
H = (int) texture.Height
|
||||
};
|
||||
Depth = 0;
|
||||
Layer = 0;
|
||||
Level = 0;
|
||||
}
|
||||
|
||||
public Refresh.TextureSlice ToRefreshTextureSlice()
|
||||
{
|
||||
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
rectangle = Rectangle.ToRefresh(),
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level
|
||||
};
|
||||
public TextureSlice(Texture texture, Rect rectangle, uint depth = 0, uint layer = 0, uint level = 0)
|
||||
{
|
||||
Texture = texture;
|
||||
Rectangle = rectangle;
|
||||
Depth = depth;
|
||||
Layer = layer;
|
||||
Level = level;
|
||||
}
|
||||
|
||||
return textureSlice;
|
||||
}
|
||||
}
|
||||
public Refresh.TextureSlice ToRefreshTextureSlice()
|
||||
{
|
||||
Refresh.TextureSlice textureSlice = new Refresh.TextureSlice
|
||||
{
|
||||
texture = Texture.Handle,
|
||||
rectangle = Rectangle.ToRefresh(),
|
||||
depth = Depth,
|
||||
layer = Layer,
|
||||
level = Level
|
||||
};
|
||||
|
||||
return textureSlice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
namespace MoonWorks
|
||||
{
|
||||
public static class Conversions
|
||||
{
|
||||
public static byte BoolToByte(bool b)
|
||||
{
|
||||
return (byte)(b ? 1 : 0);
|
||||
}
|
||||
|
||||
public static bool ByteToBool(byte b)
|
||||
{
|
||||
return b == 0 ? false : true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
namespace MoonWorks.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// A convenience structure for pairing a vertex binding with its associated attributes.
|
||||
/// </summary>
|
||||
public struct VertexBindingAndAttributes
|
||||
{
|
||||
public VertexBinding VertexBinding { get; }
|
||||
public VertexAttribute[] VertexAttributes { get; }
|
||||
|
||||
public VertexBindingAndAttributes(VertexBinding binding, VertexAttribute[] attributes)
|
||||
{
|
||||
VertexBinding = binding;
|
||||
VertexAttributes = attributes;
|
||||
}
|
||||
|
||||
public static VertexBindingAndAttributes Create<T>(uint bindingIndex, uint locationOffset = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged, IVertexType
|
||||
{
|
||||
VertexBinding binding = VertexBinding.Create<T>(bindingIndex, inputRate);
|
||||
VertexAttribute[] attributes = new VertexAttribute[T.Formats.Length];
|
||||
uint offset = 0;
|
||||
|
||||
for (uint i = 0; i < T.Formats.Length; i += 1)
|
||||
{
|
||||
var format = T.Formats[i];
|
||||
|
||||
attributes[i] = new VertexAttribute
|
||||
{
|
||||
Binding = bindingIndex,
|
||||
Location = locationOffset + i,
|
||||
Format = format,
|
||||
Offset = offset
|
||||
};
|
||||
|
||||
offset += Conversions.VertexElementFormatSize(format);
|
||||
}
|
||||
|
||||
return new VertexBindingAndAttributes(binding, attributes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using MoonWorks.Math;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a specific joystick direction on a gamepad.
|
||||
/// </summary>
|
||||
public class Axis
|
||||
{
|
||||
public Gamepad Parent { get; }
|
||||
SDL.SDL_GameControllerAxis SDL_Axis;
|
||||
|
||||
public AxisCode Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// An axis value between -1 and 1.
|
||||
/// </summary>
|
||||
public float Value { get; private set; }
|
||||
|
||||
public Axis(
|
||||
Gamepad parent,
|
||||
AxisCode code,
|
||||
SDL.SDL_GameControllerAxis sdlAxis
|
||||
) {
|
||||
Parent = parent;
|
||||
SDL_Axis = sdlAxis;
|
||||
Code = code;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
Value = MathHelper.Normalize(
|
||||
SDL.SDL_GameControllerGetAxis(Parent.Handle, SDL_Axis),
|
||||
short.MinValue, short.MaxValue,
|
||||
-1, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used to access a gamepad axis virtual button without a direct reference to the button object.
|
||||
/// </summary>
|
||||
public enum AxisButtonCode
|
||||
{
|
||||
LeftX_Left,
|
||||
LeftX_Right,
|
||||
LeftY_Up,
|
||||
LeftY_Down,
|
||||
RightX_Left,
|
||||
RightX_Right,
|
||||
RightY_Up,
|
||||
RightY_Down
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used to access a gamepad axis without a direct reference to the axis object.
|
||||
/// Enum values are equivalent to SDL_GameControllerAxis.
|
||||
/// </summary>
|
||||
public enum AxisCode
|
||||
{
|
||||
LeftX,
|
||||
LeftY,
|
||||
RightX,
|
||||
RightY
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used to access a gamepad button without a direct reference to the button object.
|
||||
/// Enum values are equivalent to the SDL GameControllerButton value.
|
||||
/// </summary>
|
||||
public enum GamepadButtonCode
|
||||
{
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
Back,
|
||||
Guide,
|
||||
Start,
|
||||
LeftStick,
|
||||
RightStick,
|
||||
LeftShoulder,
|
||||
RightShoulder,
|
||||
DpadUp,
|
||||
DpadDown,
|
||||
DpadLeft,
|
||||
DpadRight
|
||||
}
|
||||
}
|
|
@ -1,31 +1,97 @@
|
|||
namespace MoonWorks.Input
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class ButtonState
|
||||
{
|
||||
private ButtonStatus ButtonStatus { get; set; }
|
||||
/// <summary>
|
||||
/// Container for the current state of a binary input.
|
||||
/// </summary>
|
||||
public struct ButtonState
|
||||
{
|
||||
public ButtonStatus ButtonStatus { get; }
|
||||
|
||||
public bool IsPressed => ButtonStatus == ButtonStatus.Pressed;
|
||||
public bool IsHeld => ButtonStatus == ButtonStatus.Held;
|
||||
public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held;
|
||||
public bool IsReleased => ButtonStatus == ButtonStatus.Released;
|
||||
/// <summary>
|
||||
/// True if the button was pressed this frame.
|
||||
/// </summary>
|
||||
public bool IsPressed => ButtonStatus == ButtonStatus.Pressed;
|
||||
|
||||
internal void Update(bool isPressed)
|
||||
{
|
||||
if (isPressed)
|
||||
{
|
||||
if (ButtonStatus == ButtonStatus.Pressed)
|
||||
{
|
||||
ButtonStatus = ButtonStatus.Held;
|
||||
}
|
||||
else if (ButtonStatus == ButtonStatus.Released)
|
||||
{
|
||||
ButtonStatus = ButtonStatus.Pressed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ButtonStatus = ButtonStatus.Released;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// True if the button was pressed this frame and the previous frame.
|
||||
/// </summary>
|
||||
public bool IsHeld => ButtonStatus == ButtonStatus.Held;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was either pressed or continued to be held this frame.
|
||||
/// </summary>
|
||||
public bool IsDown => ButtonStatus == ButtonStatus.Pressed || ButtonStatus == ButtonStatus.Held;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was let go this frame.
|
||||
/// </summary>
|
||||
public bool IsReleased => ButtonStatus == ButtonStatus.Released;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was not pressed this frame or the previous frame.
|
||||
/// </summary>
|
||||
public bool IsIdle => ButtonStatus == ButtonStatus.Idle;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was either idle or released this frame.
|
||||
/// </summary>
|
||||
public bool IsUp => ButtonStatus == ButtonStatus.Idle || ButtonStatus == ButtonStatus.Released;
|
||||
|
||||
public ButtonState(ButtonStatus buttonStatus)
|
||||
{
|
||||
ButtonStatus = buttonStatus;
|
||||
}
|
||||
|
||||
internal ButtonState Update(bool isPressed)
|
||||
{
|
||||
if (isPressed)
|
||||
{
|
||||
if (IsUp)
|
||||
{
|
||||
return new ButtonState(ButtonStatus.Pressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ButtonState(ButtonStatus.Held);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsDown)
|
||||
{
|
||||
return new ButtonState(ButtonStatus.Released);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ButtonState(ButtonStatus.Idle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines two button states. Useful for alt control sets or input buffering.
|
||||
/// </summary>
|
||||
public static ButtonState operator |(ButtonState a, ButtonState b)
|
||||
{
|
||||
if (a.ButtonStatus == ButtonStatus.Idle || a.ButtonStatus == ButtonStatus.Released)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else if (a.ButtonStatus == ButtonStatus.Pressed)
|
||||
{
|
||||
if (b.ButtonStatus == ButtonStatus.Held)
|
||||
{
|
||||
return new ButtonState(ButtonStatus.Held);
|
||||
}
|
||||
else
|
||||
{
|
||||
return a;
|
||||
}
|
||||
}
|
||||
else // held
|
||||
{
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
namespace MoonWorks.Input
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
internal enum ButtonStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the input is not pressed.
|
||||
/// </summary>
|
||||
Released,
|
||||
/// <summary>
|
||||
/// Indicates that the input was pressed this frame.
|
||||
/// </summary>
|
||||
Pressed,
|
||||
/// <summary>
|
||||
/// Indicates that the input has been held for multiple frames.
|
||||
/// </summary>
|
||||
Held
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents the current status of a binary input.
|
||||
/// </summary>
|
||||
public enum ButtonStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the button was not pressed last frame and is still not pressed.
|
||||
/// </summary>
|
||||
Idle,
|
||||
/// <summary>
|
||||
/// Indicates that the button was released this frame.
|
||||
/// </summary>
|
||||
Released,
|
||||
/// <summary>
|
||||
/// Indicates that the button was pressed this frame.
|
||||
/// </summary>
|
||||
Pressed,
|
||||
/// <summary>
|
||||
/// Indicates that the button has been held for multiple frames.
|
||||
/// </summary>
|
||||
Held
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +1,325 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MoonWorks.Math;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class Gamepad
|
||||
{
|
||||
internal IntPtr Handle;
|
||||
/// <summary>
|
||||
/// A Gamepad input abstraction that represents input coming from a console controller or other such devices.
|
||||
/// The button names map to a standard Xbox 360 controller.
|
||||
/// For different controllers the relative position of the face buttons will determine the button mapping.
|
||||
/// For example on a DualShock controller the Cross button will map to the A button.
|
||||
/// </summary>
|
||||
public class Gamepad
|
||||
{
|
||||
internal IntPtr Handle;
|
||||
internal int JoystickInstanceID;
|
||||
|
||||
public ButtonState A { get; } = new ButtonState();
|
||||
public ButtonState B { get; } = new ButtonState();
|
||||
public ButtonState X { get; } = new ButtonState();
|
||||
public ButtonState Y { get; } = new ButtonState();
|
||||
public ButtonState Back { get; } = new ButtonState();
|
||||
public ButtonState Guide { get; } = new ButtonState();
|
||||
public ButtonState Start { get; } = new ButtonState();
|
||||
public ButtonState LeftStick { get; } = new ButtonState();
|
||||
public ButtonState RightStick { get; } = new ButtonState();
|
||||
public ButtonState LeftShoulder { get; } = new ButtonState();
|
||||
public ButtonState RightShoulder { get; } = new ButtonState();
|
||||
public ButtonState DpadUp { get; } = new ButtonState();
|
||||
public ButtonState DpadDown { get; } = new ButtonState();
|
||||
public ButtonState DpadLeft { get; } = new ButtonState();
|
||||
public ButtonState DpadRight { get; } = new ButtonState();
|
||||
public int Slot { get; internal set; }
|
||||
|
||||
public float LeftX { get; private set; }
|
||||
public float LeftY { get; private set; }
|
||||
public float RightX { get; private set; }
|
||||
public float RightY { get; private set; }
|
||||
public float TriggerLeft { get; private set; }
|
||||
public float TriggerRight { get; private set; }
|
||||
public GamepadButton A { get; }
|
||||
public GamepadButton B { get; }
|
||||
public GamepadButton X { get; }
|
||||
public GamepadButton Y { get; }
|
||||
public GamepadButton Back { get; }
|
||||
public GamepadButton Guide { get; }
|
||||
public GamepadButton Start { get; }
|
||||
public GamepadButton LeftStick { get; }
|
||||
public GamepadButton RightStick { get; }
|
||||
public GamepadButton LeftShoulder { get; }
|
||||
public GamepadButton RightShoulder { get; }
|
||||
public GamepadButton DpadUp { get; }
|
||||
public GamepadButton DpadDown { get; }
|
||||
public GamepadButton DpadLeft { get; }
|
||||
public GamepadButton DpadRight { get; }
|
||||
|
||||
internal Gamepad(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
public Axis LeftX { get; }
|
||||
public Axis LeftY { get; }
|
||||
public Axis RightX { get; }
|
||||
public Axis RightY { get; }
|
||||
|
||||
public bool SetVibration(float leftMotor, float rightMotor, uint durationInMilliseconds)
|
||||
{
|
||||
return SDL.SDL_GameControllerRumble(
|
||||
Handle,
|
||||
(ushort)(MathHelper.Clamp(leftMotor, 0f, 1f) * 0xFFFF),
|
||||
(ushort)(MathHelper.Clamp(rightMotor, 0f, 1f) * 0xFFFF),
|
||||
durationInMilliseconds
|
||||
) == 0;
|
||||
}
|
||||
public AxisButton LeftXLeft { get; }
|
||||
public AxisButton LeftXRight { get; }
|
||||
public AxisButton LeftYUp { get; }
|
||||
public AxisButton LeftYDown { get; }
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
A.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A));
|
||||
B.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B));
|
||||
X.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X));
|
||||
Y.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y));
|
||||
Back.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK));
|
||||
Guide.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE));
|
||||
Start.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START));
|
||||
LeftStick.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK));
|
||||
RightStick.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK));
|
||||
LeftShoulder.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER));
|
||||
RightShoulder.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER));
|
||||
DpadUp.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP));
|
||||
DpadDown.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN));
|
||||
DpadLeft.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT));
|
||||
DpadRight.Update(IsPressed(SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT));
|
||||
public AxisButton RightXLeft { get; }
|
||||
public AxisButton RightXRight { get; }
|
||||
public AxisButton RightYUp { get; }
|
||||
public AxisButton RightYDown { get; }
|
||||
|
||||
LeftX = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX);
|
||||
LeftY = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY);
|
||||
RightX = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX);
|
||||
RightY = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY);
|
||||
TriggerLeft = UpdateTrigger(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
||||
TriggerRight = UpdateTrigger(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
||||
}
|
||||
public Trigger TriggerLeft { get; }
|
||||
public Trigger TriggerRight { get; }
|
||||
|
||||
private bool IsPressed(SDL.SDL_GameControllerButton button)
|
||||
{
|
||||
return MoonWorks.Conversions.ByteToBool(SDL.SDL_GameControllerGetButton(Handle, button));
|
||||
}
|
||||
public TriggerButton TriggerLeftButton { get; }
|
||||
public TriggerButton TriggerRightButton { get; }
|
||||
|
||||
private float UpdateAxis(SDL.SDL_GameControllerAxis axis)
|
||||
{
|
||||
var axisValue = SDL.SDL_GameControllerGetAxis(Handle, axis);
|
||||
return Normalize(axisValue, short.MinValue, short.MaxValue, -1, 1);
|
||||
}
|
||||
public bool IsDummy => Handle == IntPtr.Zero;
|
||||
|
||||
// Triggers only go from 0 to short.MaxValue
|
||||
private float UpdateTrigger(SDL.SDL_GameControllerAxis trigger)
|
||||
{
|
||||
var triggerValue = SDL.SDL_GameControllerGetAxis(Handle, trigger);
|
||||
return Normalize(triggerValue, 0, short.MaxValue, 0, 1);
|
||||
}
|
||||
/// <summary>
|
||||
/// True if any input on the gamepad is active. Useful for input remapping.
|
||||
/// </summary>
|
||||
public bool AnyPressed { get; private set; }
|
||||
|
||||
private float Normalize(float value, short min, short max, short newMin, short newMax)
|
||||
{
|
||||
return ((value - min) * (newMax - newMin)) / (max - min) + newMin;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Contains a reference to an arbitrary VirtualButton that was pressed on the gamepad this frame. Useful for input remapping.
|
||||
/// </summary>
|
||||
public VirtualButton AnyPressedButton { get; private set; }
|
||||
|
||||
private Dictionary<SDL.SDL_GameControllerButton, GamepadButton> EnumToButton;
|
||||
private Dictionary<SDL.SDL_GameControllerAxis, Axis> EnumToAxis;
|
||||
private Dictionary<SDL.SDL_GameControllerAxis, Trigger> EnumToTrigger;
|
||||
|
||||
private Dictionary<AxisButtonCode, AxisButton> AxisButtonCodeToAxisButton;
|
||||
private Dictionary<TriggerCode, TriggerButton> TriggerCodeToTriggerButton;
|
||||
|
||||
private VirtualButton[] VirtualButtons;
|
||||
|
||||
internal Gamepad(IntPtr handle, int slot)
|
||||
{
|
||||
Handle = handle;
|
||||
Slot = slot;
|
||||
|
||||
IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(Handle);
|
||||
JoystickInstanceID = SDL.SDL_JoystickInstanceID(joystickHandle);
|
||||
|
||||
AnyPressed = false;
|
||||
|
||||
A = new GamepadButton(this, GamepadButtonCode.A, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A);
|
||||
B = new GamepadButton(this, GamepadButtonCode.B, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B);
|
||||
X = new GamepadButton(this, GamepadButtonCode.X, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X);
|
||||
Y = new GamepadButton(this, GamepadButtonCode.Y, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y);
|
||||
|
||||
Back = new GamepadButton(this, GamepadButtonCode.Back, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK);
|
||||
Guide = new GamepadButton(this, GamepadButtonCode.Guide, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE);
|
||||
Start = new GamepadButton(this, GamepadButtonCode.Start, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START);
|
||||
|
||||
LeftStick = new GamepadButton(this, GamepadButtonCode.LeftStick, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK);
|
||||
RightStick = new GamepadButton(this, GamepadButtonCode.RightStick, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK);
|
||||
|
||||
LeftShoulder = new GamepadButton(this, GamepadButtonCode.LeftShoulder, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
|
||||
RightShoulder = new GamepadButton(this, GamepadButtonCode.RightShoulder, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
|
||||
|
||||
DpadUp = new GamepadButton(this, GamepadButtonCode.DpadUp, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP);
|
||||
DpadDown = new GamepadButton(this, GamepadButtonCode.DpadDown, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN);
|
||||
DpadLeft = new GamepadButton(this, GamepadButtonCode.DpadLeft, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT);
|
||||
DpadRight = new GamepadButton(this, GamepadButtonCode.DpadRight, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
|
||||
|
||||
LeftX = new Axis(this, AxisCode.LeftX, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX);
|
||||
LeftY = new Axis(this, AxisCode.LeftY, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY);
|
||||
RightX = new Axis(this, AxisCode.RightX, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX);
|
||||
RightY = new Axis(this, AxisCode.RightY, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY);
|
||||
|
||||
LeftXLeft = new AxisButton(LeftX, false);
|
||||
LeftXRight = new AxisButton(LeftX, true);
|
||||
LeftYUp = new AxisButton(LeftY, true);
|
||||
LeftYDown = new AxisButton(LeftY, false);
|
||||
|
||||
RightXLeft = new AxisButton(RightX, false);
|
||||
RightXRight = new AxisButton(RightX, true);
|
||||
RightYUp = new AxisButton(RightY, true);
|
||||
RightYDown = new AxisButton(RightY, false);
|
||||
|
||||
TriggerLeft = new Trigger(this, TriggerCode.Left, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
||||
TriggerRight = new Trigger(this, TriggerCode.Right, SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
||||
|
||||
TriggerLeftButton = new TriggerButton(TriggerLeft);
|
||||
TriggerRightButton = new TriggerButton(TriggerRight);
|
||||
|
||||
EnumToButton = new Dictionary<SDL.SDL_GameControllerButton, GamepadButton>
|
||||
{
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A, A },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B, B },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X, X },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y, Y },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK, Back },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE, Guide },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START, Start },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK, LeftStick },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK, RightStick },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, LeftShoulder },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, RightShoulder },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP, DpadUp },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN, DpadDown },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT, DpadLeft },
|
||||
{ SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, DpadRight }
|
||||
};
|
||||
|
||||
EnumToAxis = new Dictionary<SDL.SDL_GameControllerAxis, Axis>
|
||||
{
|
||||
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX, LeftX },
|
||||
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY, LeftY },
|
||||
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX, RightX },
|
||||
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY, RightY }
|
||||
};
|
||||
|
||||
EnumToTrigger = new Dictionary<SDL.SDL_GameControllerAxis, Trigger>
|
||||
{
|
||||
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT, TriggerLeft },
|
||||
{ SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT, TriggerRight }
|
||||
};
|
||||
|
||||
AxisButtonCodeToAxisButton = new Dictionary<AxisButtonCode, AxisButton>
|
||||
{
|
||||
{ AxisButtonCode.LeftX_Left, LeftXLeft },
|
||||
{ AxisButtonCode.LeftX_Right, LeftXRight },
|
||||
{ AxisButtonCode.LeftY_Down, LeftYDown },
|
||||
{ AxisButtonCode.LeftY_Up, LeftYUp },
|
||||
{ AxisButtonCode.RightX_Left, RightXLeft },
|
||||
{ AxisButtonCode.RightX_Right, RightXRight },
|
||||
{ AxisButtonCode.RightY_Up, RightYUp },
|
||||
{ AxisButtonCode.RightY_Down, RightYDown }
|
||||
};
|
||||
|
||||
TriggerCodeToTriggerButton = new Dictionary<TriggerCode, TriggerButton>
|
||||
{
|
||||
{ TriggerCode.Left, TriggerLeftButton },
|
||||
{ TriggerCode.Right, TriggerRightButton }
|
||||
};
|
||||
|
||||
VirtualButtons = new VirtualButton[]
|
||||
{
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
Back,
|
||||
Guide,
|
||||
Start,
|
||||
LeftStick,
|
||||
RightStick,
|
||||
LeftShoulder,
|
||||
RightShoulder,
|
||||
DpadUp,
|
||||
DpadDown,
|
||||
DpadLeft,
|
||||
DpadRight,
|
||||
LeftXLeft,
|
||||
LeftXRight,
|
||||
LeftYUp,
|
||||
LeftYDown,
|
||||
RightXLeft,
|
||||
RightXRight,
|
||||
RightYUp,
|
||||
RightYDown,
|
||||
TriggerLeftButton,
|
||||
TriggerRightButton
|
||||
};
|
||||
}
|
||||
|
||||
internal void Register(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
|
||||
IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(Handle);
|
||||
JoystickInstanceID = SDL.SDL_JoystickInstanceID(joystickHandle);
|
||||
}
|
||||
|
||||
internal void Unregister()
|
||||
{
|
||||
Handle = IntPtr.Zero;
|
||||
JoystickInstanceID = -1;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
AnyPressed = false;
|
||||
|
||||
if (!IsDummy)
|
||||
{
|
||||
foreach (var button in EnumToButton.Values)
|
||||
{
|
||||
button.Update();
|
||||
}
|
||||
|
||||
foreach (var axis in EnumToAxis.Values)
|
||||
{
|
||||
axis.Update();
|
||||
}
|
||||
|
||||
foreach (var trigger in EnumToTrigger.Values)
|
||||
{
|
||||
trigger.Update();
|
||||
}
|
||||
|
||||
LeftXLeft.Update();
|
||||
LeftXRight.Update();
|
||||
LeftYUp.Update();
|
||||
LeftYDown.Update();
|
||||
RightXLeft.Update();
|
||||
RightXRight.Update();
|
||||
RightYUp.Update();
|
||||
RightYDown.Update();
|
||||
|
||||
TriggerLeftButton.Update();
|
||||
TriggerRightButton.Update();
|
||||
|
||||
foreach (var button in VirtualButtons)
|
||||
{
|
||||
if (button.IsPressed)
|
||||
{
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets vibration values on the left and right motors.
|
||||
/// </summary>
|
||||
public bool SetVibration(float leftMotor, float rightMotor, uint durationInMilliseconds)
|
||||
{
|
||||
return SDL.SDL_GameControllerRumble(
|
||||
Handle,
|
||||
(ushort) (MathHelper.Clamp(leftMotor, 0f, 1f) * 0xFFFF),
|
||||
(ushort) (MathHelper.Clamp(rightMotor, 0f, 1f) * 0xFFFF),
|
||||
durationInMilliseconds
|
||||
) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a gamepad button object given a button code.
|
||||
/// </summary>
|
||||
public GamepadButton Button(GamepadButtonCode buttonCode)
|
||||
{
|
||||
return EnumToButton[(SDL.SDL_GameControllerButton) buttonCode];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an axis button object given a button code.
|
||||
/// </summary>
|
||||
public AxisButton Button(AxisButtonCode axisButtonCode)
|
||||
{
|
||||
return AxisButtonCodeToAxisButton[axisButtonCode];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains a trigger button object given a button code.
|
||||
/// </summary>
|
||||
public TriggerButton Button(TriggerCode triggerCode)
|
||||
{
|
||||
return TriggerCodeToTriggerButton[triggerCode];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the axis value given an AxisCode.
|
||||
/// </summary>
|
||||
/// <returns>A value between -1 and 1.</returns>
|
||||
public float AxisValue(AxisCode axisCode)
|
||||
{
|
||||
return EnumToAxis[(SDL.SDL_GameControllerAxis) axisCode].Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains the trigger value given an TriggerCode.
|
||||
/// </summary>
|
||||
/// <returns>A value between 0 and 1.</returns>
|
||||
public float TriggerValue(TriggerCode triggerCode)
|
||||
{
|
||||
return EnumToTrigger[(SDL.SDL_GameControllerAxis) triggerCode].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +1,182 @@
|
|||
using SDL2;
|
||||
using SDL2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class Inputs
|
||||
{
|
||||
public Keyboard Keyboard { get; }
|
||||
public Mouse Mouse { get; }
|
||||
/// <summary>
|
||||
/// The main container class for all input tracking.
|
||||
/// Your Game class will automatically have a reference to this class.
|
||||
/// </summary>
|
||||
public class Inputs
|
||||
{
|
||||
public const int MAX_GAMEPADS = 4;
|
||||
|
||||
List<Gamepad> gamepads = new List<Gamepad>();
|
||||
/// <summary>
|
||||
/// The reference to the Keyboard input abstraction.
|
||||
/// </summary>
|
||||
public Keyboard Keyboard { get; }
|
||||
|
||||
public static event Action<char> TextInput;
|
||||
/// <summary>
|
||||
/// The reference to the Mouse input abstraction.
|
||||
/// </summary>
|
||||
public Mouse Mouse { get; }
|
||||
|
||||
internal Inputs()
|
||||
{
|
||||
Keyboard = new Keyboard();
|
||||
Mouse = new Mouse();
|
||||
Gamepad[] Gamepads;
|
||||
|
||||
for (int i = 0; i < SDL.SDL_NumJoysticks(); i++)
|
||||
{
|
||||
if (SDL.SDL_IsGameController(i) == SDL.SDL_bool.SDL_TRUE)
|
||||
{
|
||||
gamepads.Add(new Gamepad(SDL.SDL_GameControllerOpen(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
public static event Action<char> TextInput;
|
||||
|
||||
// Assumes that SDL_PumpEvents has been called!
|
||||
internal void Update()
|
||||
{
|
||||
Keyboard.Update();
|
||||
Mouse.Update();
|
||||
/// <summary>
|
||||
/// True if any input on any input device is active. Useful for input remapping.
|
||||
/// </summary>
|
||||
public bool AnyPressed { get; private set; }
|
||||
|
||||
foreach (var gamepad in gamepads)
|
||||
{
|
||||
gamepad.Update();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Contains a reference to an arbitrary VirtualButton that was pressed this frame. Useful for input remapping.
|
||||
/// </summary>
|
||||
public VirtualButton AnyPressedButton { get; private set; }
|
||||
|
||||
public bool GamepadExists(int slot)
|
||||
{
|
||||
return slot < gamepads.Count;
|
||||
}
|
||||
public delegate void OnGamepadConnectedFunc(int slot);
|
||||
|
||||
public Gamepad GetGamepad(int slot)
|
||||
{
|
||||
return gamepads[slot];
|
||||
}
|
||||
/// <summary>
|
||||
/// Called when a gamepad has been connected.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot where the connection occurred.</param>
|
||||
public OnGamepadConnectedFunc OnGamepadConnected = delegate { };
|
||||
|
||||
internal static void OnTextInput(char c)
|
||||
{
|
||||
if (TextInput != null)
|
||||
{
|
||||
TextInput(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
public delegate void OnGamepadDisconnectedFunc(int slot);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a gamepad has been disconnected.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot where the disconnection occurred.</param>
|
||||
public OnGamepadDisconnectedFunc OnGamepadDisconnected = delegate { };
|
||||
|
||||
internal Inputs()
|
||||
{
|
||||
Keyboard = new Keyboard();
|
||||
Mouse = new Mouse();
|
||||
|
||||
Gamepads = new Gamepad[MAX_GAMEPADS];
|
||||
|
||||
// initialize dummy controllers
|
||||
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
||||
{
|
||||
Gamepads[slot] = new Gamepad(IntPtr.Zero, slot);
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes that SDL_PumpEvents has been called!
|
||||
internal void Update()
|
||||
{
|
||||
AnyPressed = false;
|
||||
AnyPressedButton = default; // DeviceKind.None
|
||||
|
||||
Keyboard.Update();
|
||||
|
||||
if (Keyboard.AnyPressed)
|
||||
{
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = Keyboard.AnyPressedButton;
|
||||
}
|
||||
|
||||
Mouse.Update();
|
||||
|
||||
if (Mouse.AnyPressed)
|
||||
{
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = Mouse.AnyPressedButton;
|
||||
}
|
||||
|
||||
foreach (var gamepad in Gamepads)
|
||||
{
|
||||
gamepad.Update();
|
||||
|
||||
if (gamepad.AnyPressed)
|
||||
{
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = gamepad.AnyPressedButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if a gamepad is currently connected in the given slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">Range: 0-3</param>
|
||||
/// <returns></returns>
|
||||
public bool GamepadExists(int slot)
|
||||
{
|
||||
if (slot < 0 || slot >= MAX_GAMEPADS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !Gamepads[slot].IsDummy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a gamepad associated with the given slot.
|
||||
/// The first n slots are guaranteed to occupied with gamepads if they are connected.
|
||||
/// If a gamepad does not exist for the given slot, a dummy object with all inputs in default state will be returned.
|
||||
/// You can check if a gamepad is connected in a slot with the GamepadExists function.
|
||||
/// </summary>
|
||||
/// <param name="slot">Range: 0-3</param>
|
||||
public Gamepad GetGamepad(int slot)
|
||||
{
|
||||
return Gamepads[slot];
|
||||
}
|
||||
|
||||
internal void AddGamepad(int index)
|
||||
{
|
||||
for (var slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
||||
{
|
||||
if (!GamepadExists(slot))
|
||||
{
|
||||
var openResult = SDL.SDL_GameControllerOpen(index);
|
||||
if (openResult == 0)
|
||||
{
|
||||
Logger.LogError("Error opening gamepad!");
|
||||
Logger.LogError(SDL.SDL_GetError());
|
||||
}
|
||||
else
|
||||
{
|
||||
Gamepads[slot].Register(openResult);
|
||||
Logger.LogInfo($"Gamepad added to slot {slot}!");
|
||||
|
||||
if (OnGamepadConnected != null)
|
||||
{
|
||||
OnGamepadConnected(slot);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInfo("Too many gamepads already!");
|
||||
}
|
||||
|
||||
internal void RemoveGamepad(int joystickInstanceID)
|
||||
{
|
||||
for (int slot = 0; slot < MAX_GAMEPADS; slot += 1)
|
||||
{
|
||||
if (joystickInstanceID == Gamepads[slot].JoystickInstanceID)
|
||||
{
|
||||
SDL.SDL_GameControllerClose(Gamepads[slot].Handle);
|
||||
Gamepads[slot].Unregister();
|
||||
Logger.LogInfo($"Removing gamepad from slot {slot}!");
|
||||
OnGamepadDisconnected(slot);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void OnTextInput(char c)
|
||||
{
|
||||
if (TextInput != null)
|
||||
{
|
||||
TextInput(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used to determine key state without a direct reference to the virtual button object.
|
||||
/// Enum values are equivalent to the SDL Scancode value.
|
||||
/// </summary>
|
||||
public enum KeyCode : int
|
||||
{
|
||||
Unknown = 0,
|
||||
A = 4,
|
||||
B = 5,
|
||||
C = 6,
|
||||
D = 7,
|
||||
E = 8,
|
||||
F = 9,
|
||||
G = 10,
|
||||
H = 11,
|
||||
I = 12,
|
||||
J = 13,
|
||||
K = 14,
|
||||
L = 15,
|
||||
M = 16,
|
||||
N = 17,
|
||||
O = 18,
|
||||
P = 19,
|
||||
Q = 20,
|
||||
R = 21,
|
||||
S = 22,
|
||||
T = 23,
|
||||
U = 24,
|
||||
V = 25,
|
||||
W = 26,
|
||||
X = 27,
|
||||
Y = 28,
|
||||
Z = 29,
|
||||
D1 = 30,
|
||||
D2 = 31,
|
||||
D3 = 32,
|
||||
D4 = 33,
|
||||
D5 = 34,
|
||||
D6 = 35,
|
||||
D7 = 36,
|
||||
D8 = 37,
|
||||
D9 = 38,
|
||||
D0 = 39,
|
||||
Return = 40,
|
||||
Escape = 41,
|
||||
Backspace = 42,
|
||||
Tab = 43,
|
||||
Space = 44,
|
||||
Minus = 45,
|
||||
Equals = 46,
|
||||
LeftBracket = 47,
|
||||
RightBracket = 48,
|
||||
Backslash = 49,
|
||||
NonUSHash = 50,
|
||||
Semicolon = 51,
|
||||
Apostrophe = 52,
|
||||
Grave = 53,
|
||||
Comma = 54,
|
||||
Period = 55,
|
||||
Slash = 56,
|
||||
CapsLock = 57,
|
||||
F1 = 58,
|
||||
F2 = 59,
|
||||
F3 = 60,
|
||||
F4 = 61,
|
||||
F5 = 62,
|
||||
F6 = 63,
|
||||
F7 = 64,
|
||||
F8 = 65,
|
||||
F9 = 66,
|
||||
F10 = 67,
|
||||
F11 = 68,
|
||||
F12 = 69,
|
||||
PrintScreen = 70,
|
||||
ScrollLock = 71,
|
||||
Pause = 72,
|
||||
Insert = 73,
|
||||
Home = 74,
|
||||
PageUp = 75,
|
||||
Delete = 76,
|
||||
End = 77,
|
||||
PageDown = 78,
|
||||
Right = 79,
|
||||
Left = 80,
|
||||
Down = 81,
|
||||
Up = 82,
|
||||
NumLockClear = 83,
|
||||
KeypadDivide = 84,
|
||||
KeypadMultiply = 85,
|
||||
KeypadMinus = 86,
|
||||
KeypadPlus = 87,
|
||||
KeypadEnter = 88,
|
||||
Keypad1 = 89,
|
||||
Keypad2 = 90,
|
||||
Keypad3 = 91,
|
||||
Keypad4 = 92,
|
||||
Keypad5 = 93,
|
||||
Keypad6 = 94,
|
||||
Keypad7 = 95,
|
||||
Keypad8 = 96,
|
||||
Keypad9 = 97,
|
||||
Keypad0 = 98,
|
||||
KeypadPeriod = 99,
|
||||
NonUSBackslash = 100,
|
||||
LeftControl = 224,
|
||||
LeftShift = 225,
|
||||
LeftAlt = 226,
|
||||
LeftMeta = 227, // Windows, Command, Meta
|
||||
RightControl = 228,
|
||||
RightShift = 229,
|
||||
RightAlt = 230,
|
||||
RightMeta = 231 // Windows, Command, Meta
|
||||
}
|
||||
}
|
|
@ -1,14 +1,29 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class Keyboard
|
||||
{
|
||||
private ButtonState[] Keys { get; }
|
||||
private int numKeys;
|
||||
/// <summary>
|
||||
/// The keyboard input device abstraction.
|
||||
/// </summary>
|
||||
public class Keyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// True if any button on the keyboard is active. Useful for input remapping.
|
||||
/// </summary>
|
||||
public bool AnyPressed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Contains a reference to an arbitrary KeyboardButton that was pressed this frame. Useful for input remapping.
|
||||
/// </summary>
|
||||
public KeyboardButton AnyPressedButton { get; private set; }
|
||||
|
||||
internal IntPtr State { get; private set; }
|
||||
|
||||
private KeyCode[] KeyCodes;
|
||||
private KeyboardButton[] Keys { get; }
|
||||
private int numKeys;
|
||||
|
||||
private static readonly char[] TextInputCharacters = new char[]
|
||||
{
|
||||
|
@ -21,69 +36,120 @@ namespace MoonWorks.Input
|
|||
(char) 22 // Ctrl+V (Paste)
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Keycode, int> TextInputBindings = new Dictionary<Keycode, int>()
|
||||
private static readonly Dictionary<KeyCode, int> TextInputBindings = new Dictionary<KeyCode, int>()
|
||||
{
|
||||
{ Keycode.Home, 0 },
|
||||
{ Keycode.End, 1 },
|
||||
{ Keycode.Backspace, 2 },
|
||||
{ Keycode.Tab, 3 },
|
||||
{ Keycode.Return, 4 },
|
||||
{ Keycode.Delete, 5 }
|
||||
{ KeyCode.Home, 0 },
|
||||
{ KeyCode.End, 1 },
|
||||
{ KeyCode.Backspace, 2 },
|
||||
{ KeyCode.Tab, 3 },
|
||||
{ KeyCode.Return, 4 },
|
||||
{ KeyCode.Delete, 5 }
|
||||
// Ctrl+V is special!
|
||||
};
|
||||
|
||||
internal Keyboard()
|
||||
{
|
||||
SDL.SDL_GetKeyboardState(out numKeys);
|
||||
internal Keyboard()
|
||||
{
|
||||
SDL.SDL_GetKeyboardState(out numKeys);
|
||||
|
||||
Keys = new ButtonState[numKeys];
|
||||
foreach (Keycode keycode in Enum.GetValues(typeof(Keycode)))
|
||||
{
|
||||
Keys[(int)keycode] = new ButtonState();
|
||||
}
|
||||
}
|
||||
KeyCodes = Enum.GetValues<KeyCode>();
|
||||
Keys = new KeyboardButton[numKeys];
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
IntPtr keyboardState = SDL.SDL_GetKeyboardState(out _);
|
||||
foreach (KeyCode keycode in KeyCodes)
|
||||
{
|
||||
Keys[(int) keycode] = new KeyboardButton(this, keycode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int keycode in Enum.GetValues(typeof(Keycode)))
|
||||
{
|
||||
var keyDown = Marshal.ReadByte(keyboardState, keycode);
|
||||
Keys[keycode].Update(Conversions.ByteToBool(keyDown));
|
||||
internal void Update()
|
||||
{
|
||||
AnyPressed = false;
|
||||
|
||||
if (Conversions.ByteToBool(keyDown))
|
||||
{
|
||||
if (TextInputBindings.TryGetValue((Keycode)keycode, out var textIndex))
|
||||
{
|
||||
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
|
||||
}
|
||||
else if (IsDown(Keycode.LeftControl) && (Keycode)keycode == Keycode.V)
|
||||
{
|
||||
Inputs.OnTextInput(TextInputCharacters[6]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
State = SDL.SDL_GetKeyboardState(out _);
|
||||
|
||||
public bool IsDown(Keycode keycode)
|
||||
{
|
||||
return Keys[(int)keycode].IsDown;
|
||||
}
|
||||
foreach (KeyCode keycode in KeyCodes)
|
||||
{
|
||||
var button = Keys[(int) keycode];
|
||||
button.Update();
|
||||
|
||||
public bool IsPressed(Keycode keycode)
|
||||
{
|
||||
return Keys[(int)keycode].IsPressed;
|
||||
}
|
||||
if (button.IsPressed)
|
||||
{
|
||||
if (TextInputBindings.TryGetValue(keycode, out var textIndex))
|
||||
{
|
||||
Inputs.OnTextInput(TextInputCharacters[(textIndex)]);
|
||||
}
|
||||
else if (IsDown(KeyCode.LeftControl) && keycode == KeyCode.V)
|
||||
{
|
||||
Inputs.OnTextInput(TextInputCharacters[6]);
|
||||
}
|
||||
|
||||
public bool IsHeld(Keycode keycode)
|
||||
{
|
||||
return Keys[(int)keycode].IsHeld;
|
||||
}
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReleased(Keycode keycode)
|
||||
{
|
||||
return Keys[(int)keycode].IsReleased;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// True if the button was pressed this frame.
|
||||
/// </summary>
|
||||
public bool IsPressed(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsPressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was pressed this frame and the previous frame.
|
||||
/// </summary>
|
||||
public bool IsHeld(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsHeld;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was either pressed or continued to be held this frame.
|
||||
/// </summary>
|
||||
public bool IsDown(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsDown;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was let go this frame.
|
||||
/// </summary>
|
||||
public bool IsReleased(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsReleased;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was not pressed this frame or the previous frame.
|
||||
/// </summary>
|
||||
public bool IsIdle(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsIdle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was either idle or released this frame.
|
||||
/// </summary>
|
||||
public bool IsUp(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].IsUp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to a keyboard button object using a key code.
|
||||
/// </summary>
|
||||
public KeyboardButton Button(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of a keyboard button from a key code.
|
||||
/// </summary>
|
||||
public ButtonState ButtonState(KeyCode keycode)
|
||||
{
|
||||
return Keys[(int) keycode].State;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
// Enum values are equivalent to the SDL Scancode value.
|
||||
public enum Keycode : int
|
||||
{
|
||||
Unknown = 0,
|
||||
A = 4,
|
||||
B = 5,
|
||||
C = 6,
|
||||
D = 7,
|
||||
E = 8,
|
||||
F = 9,
|
||||
G = 10,
|
||||
H = 11,
|
||||
I = 12,
|
||||
J = 13,
|
||||
K = 14,
|
||||
L = 15,
|
||||
M = 16,
|
||||
N = 17,
|
||||
O = 18,
|
||||
P = 19,
|
||||
Q = 20,
|
||||
R = 21,
|
||||
S = 22,
|
||||
T = 23,
|
||||
U = 24,
|
||||
V = 25,
|
||||
W = 26,
|
||||
X = 27,
|
||||
Y = 28,
|
||||
Z = 29,
|
||||
D1 = 30,
|
||||
D2 = 31,
|
||||
D3 = 32,
|
||||
D4 = 33,
|
||||
D5 = 34,
|
||||
D6 = 35,
|
||||
D7 = 36,
|
||||
D8 = 37,
|
||||
D9 = 38,
|
||||
D0 = 39,
|
||||
Return = 40,
|
||||
Escape = 41,
|
||||
Backspace = 42,
|
||||
Tab = 43,
|
||||
Space = 44,
|
||||
Minus = 45,
|
||||
Equals = 46,
|
||||
LeftBracket = 47,
|
||||
RightBracket = 48,
|
||||
Backslash = 49,
|
||||
NonUSHash = 50,
|
||||
Semicolon = 51,
|
||||
Apostrophe = 52,
|
||||
Grave = 53,
|
||||
Comma = 54,
|
||||
Period = 55,
|
||||
Slash = 56,
|
||||
CapsLock = 57,
|
||||
F1 = 58,
|
||||
F2 = 59,
|
||||
F3 = 60,
|
||||
F4 = 61,
|
||||
F5 = 62,
|
||||
F6 = 63,
|
||||
F7 = 64,
|
||||
F8 = 65,
|
||||
F9 = 66,
|
||||
F10 = 67,
|
||||
F11 = 68,
|
||||
F12 = 69,
|
||||
PrintScreen = 70,
|
||||
ScrollLock = 71,
|
||||
Pause = 72,
|
||||
Insert = 73,
|
||||
Home = 74,
|
||||
PageUp = 75,
|
||||
Delete = 76,
|
||||
End = 77,
|
||||
PageDown = 78,
|
||||
Right = 79,
|
||||
Left = 80,
|
||||
Down = 81,
|
||||
Up = 82,
|
||||
NumLockClear = 83,
|
||||
KeypadDivide = 84,
|
||||
KeypadMultiply = 85,
|
||||
KeypadMinus = 86,
|
||||
KeypadPlus = 87,
|
||||
KeypadEnter = 88,
|
||||
Keypad1 = 89,
|
||||
Keypad2 = 90,
|
||||
Keypad3 = 91,
|
||||
Keypad4 = 92,
|
||||
Keypad5 = 93,
|
||||
Keypad6 = 94,
|
||||
Keypad7 = 95,
|
||||
Keypad8 = 96,
|
||||
Keypad9 = 97,
|
||||
Keypad0 = 98,
|
||||
KeypadPeriod = 99,
|
||||
NonUSBackslash = 100,
|
||||
LeftControl = 224,
|
||||
LeftShift = 225,
|
||||
LeftAlt = 226,
|
||||
LeftMeta = 227, // Windows, Command, Meta
|
||||
RightControl = 228,
|
||||
RightShift = 229,
|
||||
RightAlt = 230,
|
||||
RightMeta = 231 // Windows, Command, Meta
|
||||
}
|
||||
}
|
|
@ -1,53 +1,135 @@
|
|||
using SDL2;
|
||||
using System.Collections.Generic;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
public class Mouse
|
||||
{
|
||||
public ButtonState LeftButton { get; } = new ButtonState();
|
||||
public ButtonState MiddleButton { get; } = new ButtonState();
|
||||
public ButtonState RightButton { get; } = new ButtonState();
|
||||
/// <summary>
|
||||
/// The mouse input device abstraction.
|
||||
/// </summary>
|
||||
public class Mouse
|
||||
{
|
||||
public MouseButton LeftButton { get; }
|
||||
public MouseButton MiddleButton { get; }
|
||||
public MouseButton RightButton { get; }
|
||||
public MouseButton X1Button { get; }
|
||||
public MouseButton X2Button { get; }
|
||||
|
||||
public int X { get; private set; }
|
||||
public int Y { get; private set; }
|
||||
public int DeltaX { get; private set; }
|
||||
public int DeltaY { get; private set; }
|
||||
public int X { get; private set; }
|
||||
public int Y { get; private set; }
|
||||
public int DeltaX { get; private set; }
|
||||
public int DeltaY { get; private set; }
|
||||
|
||||
public int Wheel { get; internal set; }
|
||||
// note that this is a delta value
|
||||
public int Wheel { get; private set; }
|
||||
internal int WheelRaw;
|
||||
private int previousWheelRaw = 0;
|
||||
|
||||
private bool relativeMode;
|
||||
public bool RelativeMode
|
||||
{
|
||||
get => relativeMode;
|
||||
set
|
||||
{
|
||||
relativeMode = value;
|
||||
SDL.SDL_SetRelativeMouseMode(
|
||||
relativeMode ?
|
||||
SDL.SDL_bool.SDL_TRUE :
|
||||
SDL.SDL_bool.SDL_FALSE
|
||||
);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// True if any button on the keyboard is active. Useful for input remapping.
|
||||
/// </summary>
|
||||
public bool AnyPressed { get; private set; }
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
var buttonMask = SDL.SDL_GetMouseState(out var x, out var y);
|
||||
var _ = SDL.SDL_GetRelativeMouseState(out var deltaX, out var deltaY);
|
||||
/// <summary>
|
||||
/// Contains a reference to an arbitrary MouseButton that was pressed this frame. Useful for input remapping.
|
||||
/// </summary>
|
||||
public MouseButton AnyPressedButton { get; private set; }
|
||||
|
||||
X = x;
|
||||
Y = y;
|
||||
DeltaX = deltaX;
|
||||
DeltaY = deltaY;
|
||||
internal uint ButtonMask { get; private set; }
|
||||
|
||||
LeftButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_LMASK));
|
||||
MiddleButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_MMASK));
|
||||
RightButton.Update(IsPressed(buttonMask, SDL.SDL_BUTTON_RMASK));
|
||||
}
|
||||
private bool relativeMode;
|
||||
/// <summary>
|
||||
/// If set to true, the cursor is hidden, the mouse position is constrained to the window,
|
||||
/// and relative mouse motion will be reported even if the mouse is at the edge of the window.
|
||||
/// </summary>
|
||||
public bool RelativeMode
|
||||
{
|
||||
get => relativeMode;
|
||||
set
|
||||
{
|
||||
relativeMode = value;
|
||||
SDL.SDL_SetRelativeMouseMode(
|
||||
relativeMode ?
|
||||
SDL.SDL_bool.SDL_TRUE :
|
||||
SDL.SDL_bool.SDL_FALSE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPressed(uint buttonMask, uint buttonFlag)
|
||||
{
|
||||
return (buttonMask & buttonFlag) != 0;
|
||||
}
|
||||
}
|
||||
private bool hidden;
|
||||
/// <summary>
|
||||
/// If set to true, the OS cursor will not be shown in your application window.
|
||||
/// </summary>
|
||||
public bool Hidden
|
||||
{
|
||||
get => hidden;
|
||||
set
|
||||
{
|
||||
hidden = value;
|
||||
SDL.SDL_ShowCursor(hidden ? SDL.SDL_DISABLE : SDL.SDL_ENABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<MouseButtonCode, MouseButton> CodeToButton;
|
||||
|
||||
internal Mouse()
|
||||
{
|
||||
LeftButton = new MouseButton(this, MouseButtonCode.Left, SDL.SDL_BUTTON_LMASK);
|
||||
MiddleButton = new MouseButton(this, MouseButtonCode.Middle, SDL.SDL_BUTTON_MMASK);
|
||||
RightButton = new MouseButton(this, MouseButtonCode.Right, SDL.SDL_BUTTON_RMASK);
|
||||
X1Button = new MouseButton(this, MouseButtonCode.X1, SDL.SDL_BUTTON_X1MASK);
|
||||
X2Button = new MouseButton(this, MouseButtonCode.X2, SDL.SDL_BUTTON_X2MASK);
|
||||
|
||||
CodeToButton = new Dictionary<MouseButtonCode, MouseButton>
|
||||
{
|
||||
{ MouseButtonCode.Left, LeftButton },
|
||||
{ MouseButtonCode.Right, RightButton },
|
||||
{ MouseButtonCode.Middle, MiddleButton },
|
||||
{ MouseButtonCode.X1, X1Button },
|
||||
{ MouseButtonCode.X2, X2Button }
|
||||
};
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
AnyPressed = false;
|
||||
|
||||
ButtonMask = SDL.SDL_GetMouseState(out var x, out var y);
|
||||
var _ = SDL.SDL_GetRelativeMouseState(out var deltaX, out var deltaY);
|
||||
|
||||
X = x;
|
||||
Y = y;
|
||||
DeltaX = deltaX;
|
||||
DeltaY = deltaY;
|
||||
|
||||
Wheel = WheelRaw - previousWheelRaw;
|
||||
previousWheelRaw = WheelRaw;
|
||||
|
||||
foreach (var button in CodeToButton.Values)
|
||||
{
|
||||
button.Update();
|
||||
|
||||
if (button.IsPressed)
|
||||
{
|
||||
AnyPressed = true;
|
||||
AnyPressedButton = button;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a button from the mouse given a MouseButtonCode.
|
||||
/// </summary>
|
||||
public MouseButton Button(MouseButtonCode buttonCode)
|
||||
{
|
||||
return CodeToButton[buttonCode];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a button state from a mouse button corresponding to the given MouseButtonCode.
|
||||
/// </summary>
|
||||
public ButtonState ButtonState(MouseButtonCode buttonCode)
|
||||
{
|
||||
return CodeToButton[buttonCode].State;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used to determine virtual mouse button state without a direct reference to the button object.
|
||||
/// </summary>
|
||||
public enum MouseButtonCode
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
X1,
|
||||
X2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using MoonWorks.Math;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a trigger input on a gamepad.
|
||||
/// </summary>
|
||||
public class Trigger
|
||||
{
|
||||
public Gamepad Parent { get; }
|
||||
public SDL.SDL_GameControllerAxis SDL_Axis;
|
||||
|
||||
public TriggerCode Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A trigger value between 0 and 1.
|
||||
/// </summary>
|
||||
public float Value { get; private set; }
|
||||
|
||||
public Trigger(
|
||||
Gamepad parent,
|
||||
TriggerCode code,
|
||||
SDL.SDL_GameControllerAxis sdlAxis
|
||||
) {
|
||||
Parent = parent;
|
||||
Code = code;
|
||||
SDL_Axis = sdlAxis;
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
Value = MathHelper.Normalize(
|
||||
SDL.SDL_GameControllerGetAxis(Parent.Handle, SDL_Axis),
|
||||
0, short.MaxValue,
|
||||
0, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be used to determine trigger state or trigger virtual button state without direct reference to the trigger object or virtual button object.
|
||||
/// Enum values correspond to SDL_GameControllerAxis.
|
||||
/// </summary>
|
||||
public enum TriggerCode
|
||||
{
|
||||
Left = 4,
|
||||
Right = 5
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// VirtualButtons map inputs to binary inputs, like a trigger threshold or joystick axis threshold.
|
||||
/// </summary>
|
||||
public abstract class VirtualButton
|
||||
{
|
||||
public ButtonState State { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was pressed this exact frame.
|
||||
/// </summary>
|
||||
public bool IsPressed => State.IsPressed;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button has been continuously held for more than one frame.
|
||||
/// </summary>
|
||||
public bool IsHeld => State.IsHeld;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is pressed or held.
|
||||
/// </summary>
|
||||
public bool IsDown => State.IsDown;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was released this frame.
|
||||
/// </summary>
|
||||
public bool IsReleased => State.IsReleased;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button was not pressed the previous or current frame.
|
||||
/// </summary>
|
||||
public bool IsIdle => State.IsIdle;
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is idle or released.
|
||||
/// </summary>
|
||||
public bool IsUp => State.IsUp;
|
||||
|
||||
internal virtual void Update()
|
||||
{
|
||||
State = State.Update(CheckPressed());
|
||||
}
|
||||
|
||||
internal abstract bool CheckPressed();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// A virtual button corresponding to a direction on a joystick.
|
||||
/// If the axis value exceeds the threshold, it will be treated as a press.
|
||||
/// </summary>
|
||||
public class AxisButton : VirtualButton
|
||||
{
|
||||
public Axis Parent { get; }
|
||||
public AxisButtonCode Code { get; }
|
||||
|
||||
private float threshold = 0.5f;
|
||||
public float Threshold
|
||||
{
|
||||
get => threshold;
|
||||
set => threshold = System.Math.Clamp(value, 0, 1);
|
||||
}
|
||||
|
||||
private int Sign;
|
||||
|
||||
internal AxisButton(Axis parent, bool positive)
|
||||
{
|
||||
Parent = parent;
|
||||
Sign = positive ? 1 : -1;
|
||||
|
||||
if (parent.Code == AxisCode.LeftX)
|
||||
{
|
||||
if (positive)
|
||||
{
|
||||
Code = AxisButtonCode.LeftX_Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
Code = AxisButtonCode.LeftX_Left;
|
||||
}
|
||||
}
|
||||
else if (parent.Code == AxisCode.LeftY)
|
||||
{
|
||||
if (positive)
|
||||
{
|
||||
Code = AxisButtonCode.LeftY_Up;
|
||||
}
|
||||
else
|
||||
{
|
||||
Code = AxisButtonCode.LeftY_Down;
|
||||
}
|
||||
}
|
||||
else if (parent.Code == AxisCode.RightX)
|
||||
{
|
||||
if (positive)
|
||||
{
|
||||
Code = AxisButtonCode.RightX_Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
Code = AxisButtonCode.RightX_Left;
|
||||
}
|
||||
}
|
||||
else if (parent.Code == AxisCode.RightY)
|
||||
{
|
||||
if (positive)
|
||||
{
|
||||
Code = AxisButtonCode.RightY_Up;
|
||||
}
|
||||
else
|
||||
{
|
||||
Code = AxisButtonCode.RightY_Down;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return Sign * Parent.Value >= threshold;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// A dummy button that can never be pressed. Used for the dummy gamepad.
|
||||
/// </summary>
|
||||
public class EmptyButton : VirtualButton
|
||||
{
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// A virtual button corresponding to a gamepad button.
|
||||
/// </summary>
|
||||
public class GamepadButton : VirtualButton
|
||||
{
|
||||
public Gamepad Parent { get; }
|
||||
SDL.SDL_GameControllerButton SDL_Button;
|
||||
public GamepadButtonCode Code { get; }
|
||||
|
||||
internal GamepadButton(Gamepad parent, GamepadButtonCode code, SDL.SDL_GameControllerButton sdlButton)
|
||||
{
|
||||
Parent = parent;
|
||||
Code = code;
|
||||
SDL_Button = sdlButton;
|
||||
}
|
||||
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return MoonWorks.Conversions.ByteToBool(SDL.SDL_GameControllerGetButton(Parent.Handle, SDL_Button));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// A virtual button corresponding to a keyboard button.
|
||||
/// </summary>
|
||||
public class KeyboardButton : VirtualButton
|
||||
{
|
||||
Keyboard Parent;
|
||||
public KeyCode KeyCode { get; }
|
||||
|
||||
internal KeyboardButton(Keyboard parent, KeyCode keyCode)
|
||||
{
|
||||
Parent = parent;
|
||||
KeyCode = keyCode;
|
||||
}
|
||||
|
||||
internal unsafe override bool CheckPressed()
|
||||
{
|
||||
return Conversions.ByteToBool(((byte*) Parent.State)[(int) KeyCode]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// A virtual button corresponding to a mouse button.
|
||||
/// </summary>
|
||||
public class MouseButton : VirtualButton
|
||||
{
|
||||
Mouse Parent;
|
||||
uint ButtonMask;
|
||||
|
||||
public MouseButtonCode Code { get; private set; }
|
||||
|
||||
internal MouseButton(Mouse parent, MouseButtonCode code, uint buttonMask)
|
||||
{
|
||||
Parent = parent;
|
||||
Code = code;
|
||||
ButtonMask = buttonMask;
|
||||
}
|
||||
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return (Parent.ButtonMask & ButtonMask) != 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
namespace MoonWorks.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// A virtual button corresponding to a trigger on a gamepad.
|
||||
/// If the trigger value exceeds the threshold, it will be treated as a press.
|
||||
/// </summary>
|
||||
public class TriggerButton : VirtualButton
|
||||
{
|
||||
public Trigger Parent { get; }
|
||||
public TriggerCode Code => Parent.Code;
|
||||
|
||||
private float threshold = 0.7f;
|
||||
public float Threshold
|
||||
{
|
||||
get => threshold;
|
||||
set => threshold = System.Math.Clamp(value, 0, 1);
|
||||
}
|
||||
|
||||
internal TriggerButton(Trigger parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
internal override bool CheckPressed()
|
||||
{
|
||||
return Parent.Value >= Threshold;
|
||||
}
|
||||
}
|
||||
}
|
121
src/Logger.cs
121
src/Logger.cs
|
@ -1,69 +1,80 @@
|
|||
using System;
|
||||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
public static Action<string> LogInfo;
|
||||
public static Action<string> LogWarn;
|
||||
public static Action<string> LogError;
|
||||
public static class Logger
|
||||
{
|
||||
public static Action<string> LogInfo = LogInfoDefault;
|
||||
public static Action<string> LogWarn = LogWarnDefault;
|
||||
public static Action<string> LogError = LogErrorDefault;
|
||||
|
||||
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
|
||||
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
|
||||
private static RefreshCS.Refresh.Refresh_LogFunc LogErrorFunc = RefreshLogError;
|
||||
private static RefreshCS.Refresh.Refresh_LogFunc LogInfoFunc = RefreshLogInfo;
|
||||
private static RefreshCS.Refresh.Refresh_LogFunc LogWarnFunc = RefreshLogWarn;
|
||||
private static RefreshCS.Refresh.Refresh_LogFunc LogErrorFunc = RefreshLogError;
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
if (Logger.LogInfo == null)
|
||||
{
|
||||
Logger.LogInfo = Console.WriteLine;
|
||||
}
|
||||
if (Logger.LogWarn == null)
|
||||
{
|
||||
Logger.LogWarn = Console.WriteLine;
|
||||
}
|
||||
if (Logger.LogError == null)
|
||||
{
|
||||
Logger.LogError = Console.WriteLine;
|
||||
}
|
||||
internal static void Initialize()
|
||||
{
|
||||
Refresh.Refresh_HookLogFunctions(
|
||||
LogInfoFunc,
|
||||
LogWarnFunc,
|
||||
LogErrorFunc
|
||||
);
|
||||
}
|
||||
|
||||
Refresh.Refresh_HookLogFunctions(
|
||||
LogInfoFunc,
|
||||
LogWarnFunc,
|
||||
LogErrorFunc
|
||||
);
|
||||
}
|
||||
private static void LogInfoDefault(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("INFO: ");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine(str);
|
||||
}
|
||||
|
||||
private static void RefreshLogInfo(IntPtr msg)
|
||||
{
|
||||
LogInfo(UTF8_ToManaged(msg));
|
||||
}
|
||||
private static void LogWarnDefault(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.Write("WARN: ");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine(str);
|
||||
}
|
||||
|
||||
private static void RefreshLogWarn(IntPtr msg)
|
||||
{
|
||||
LogWarn(UTF8_ToManaged(msg));
|
||||
}
|
||||
private static void LogErrorDefault(string str)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.Write("ERROR: ");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine(str);
|
||||
}
|
||||
|
||||
private static void RefreshLogError(IntPtr msg)
|
||||
{
|
||||
LogError(UTF8_ToManaged(msg));
|
||||
}
|
||||
private static void RefreshLogInfo(IntPtr msg)
|
||||
{
|
||||
LogInfo(UTF8_ToManaged(msg));
|
||||
}
|
||||
|
||||
private unsafe static string UTF8_ToManaged(IntPtr s)
|
||||
{
|
||||
byte* ptr = (byte*) s;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
ptr += 1;
|
||||
}
|
||||
private static void RefreshLogWarn(IntPtr msg)
|
||||
{
|
||||
LogWarn(UTF8_ToManaged(msg));
|
||||
}
|
||||
|
||||
string result = System.Text.Encoding.UTF8.GetString(
|
||||
(byte*) s,
|
||||
(int) (ptr - (byte*) s)
|
||||
);
|
||||
private static void RefreshLogError(IntPtr msg)
|
||||
{
|
||||
LogError(UTF8_ToManaged(msg));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
private unsafe static string UTF8_ToManaged(IntPtr s)
|
||||
{
|
||||
byte* ptr = (byte*) s;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
ptr += 1;
|
||||
}
|
||||
|
||||
string result = System.Text.Encoding.UTF8.GetString(
|
||||
(byte*) s,
|
||||
(int) (ptr - (byte*) s)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,886 @@
|
|||
using MoonWorks.Math.Fixed;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MoonWorks.Math
|
||||
{
|
||||
public static class Easing
|
||||
{
|
||||
private const float C1 = 1.70158f;
|
||||
private const float C2 = C1 * 1.525f;
|
||||
private const float C3 = C1 + 1;
|
||||
private const float C4 = (2 * System.MathF.PI) / 3;
|
||||
private const float C5 = (2 * System.MathF.PI) / 4.5f;
|
||||
|
||||
private static readonly Fix64 HALF = Fix64.FromFraction(1, 2);
|
||||
private static readonly Fix64 FIXED_C1 = Fix64.FromFraction(170158, 100000);
|
||||
private static readonly Fix64 FIXED_C2 = FIXED_C1 * Fix64.FromFraction(61, 40);
|
||||
private static readonly Fix64 FIXED_C3 = FIXED_C1 + Fix64.One;
|
||||
private static readonly Fix64 FIXED_C4 = Fix64.PiTimes2 / new Fix64(3);
|
||||
private static readonly Fix64 FIXED_C5 = Fix64.PiTimes2 / Fix64.FromFraction(9, 2);
|
||||
|
||||
private static readonly Fix64 FIXED_N1 = Fix64.FromFraction(121, 16);
|
||||
private static readonly Fix64 FIXED_D1 = Fix64.FromFraction(11, 4);
|
||||
|
||||
private static float OutIn(
|
||||
System.Func<float, float> outFunc,
|
||||
System.Func<float, float> inFunc,
|
||||
float t
|
||||
) {
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return outFunc(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
return inFunc(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static Fix64 OutIn(
|
||||
System.Func<Fix64, Fix64> outFunc,
|
||||
System.Func<Fix64, Fix64> inFunc,
|
||||
Fix64 t
|
||||
) {
|
||||
if (t < HALF)
|
||||
{
|
||||
return outFunc(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
return inFunc(t);
|
||||
}
|
||||
}
|
||||
|
||||
/* GENERAL-USE FUNCTIONS */
|
||||
|
||||
public static float AttackHoldRelease(
|
||||
float start,
|
||||
float hold,
|
||||
float end,
|
||||
float time,
|
||||
float attackDuration,
|
||||
Function.Float attackEasingFunction,
|
||||
float holdDuration,
|
||||
float releaseDuration,
|
||||
Function.Float releaseEasingFunction
|
||||
) {
|
||||
if (time < attackDuration)
|
||||
{
|
||||
return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction));
|
||||
}
|
||||
else if (time >= attackDuration && time < attackDuration + holdDuration)
|
||||
{
|
||||
return hold;
|
||||
}
|
||||
else // time >= attackDuration + holdDuration
|
||||
{
|
||||
return Interp(hold, end, time - holdDuration - attackDuration, releaseDuration, Function.Get(releaseEasingFunction));
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 AttackHoldRelease(
|
||||
Fix64 start,
|
||||
Fix64 hold,
|
||||
Fix64 end,
|
||||
Fix64 time,
|
||||
Fix64 attackDuration,
|
||||
Function.Fixed attackEasingFunction,
|
||||
Fix64 holdDuration,
|
||||
Fix64 releaseDuration,
|
||||
Function.Fixed releaseEasingFunction
|
||||
) {
|
||||
if (time < attackDuration)
|
||||
{
|
||||
return Interp(start, hold, time, attackDuration, Function.Get(attackEasingFunction));
|
||||
}
|
||||
else if (time >= attackDuration && time < attackDuration + holdDuration)
|
||||
{
|
||||
return hold;
|
||||
}
|
||||
else // time >= attackDuration + holdDuration
|
||||
{
|
||||
return Interp(hold, end, time - holdDuration - attackDuration, releaseDuration, Function.Get(releaseEasingFunction));
|
||||
}
|
||||
}
|
||||
|
||||
public static float Lerp(float start, float end, float time)
|
||||
{
|
||||
return (start + (end - start) * time);
|
||||
}
|
||||
|
||||
public static float Interp(float start, float end, float time, float duration, System.Func<float, float> easingFunc)
|
||||
{
|
||||
return Lerp(start, end, easingFunc(time / duration));
|
||||
}
|
||||
|
||||
public static float Interp(float start, float end, float time, float duration, MoonWorks.Math.Easing.Function.Float easingFunc)
|
||||
{
|
||||
return Interp(start, end, time, duration, Function.Get(easingFunc));
|
||||
}
|
||||
|
||||
public static Fix64 Lerp(Fix64 start, Fix64 end, Fix64 time)
|
||||
{
|
||||
return (start + (end - start) * time);
|
||||
}
|
||||
|
||||
public static Fix64 Interp(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, System.Func<Fix64, Fix64> easingFunc)
|
||||
{
|
||||
return Lerp(start, end, easingFunc(time / duration));
|
||||
}
|
||||
|
||||
public static Fix64 Interp(Fix64 start, Fix64 end, Fix64 time, Fix64 duration, MoonWorks.Math.Easing.Function.Fixed easingFunc)
|
||||
{
|
||||
return Interp(start, end, time, duration, Function.Get(easingFunc));
|
||||
}
|
||||
|
||||
/* FLOAT EASING FUNCTIONS */
|
||||
|
||||
// LINEAR
|
||||
|
||||
public static float Linear(float t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
// QUADRATIC
|
||||
|
||||
public static float InQuad(float t)
|
||||
{
|
||||
return t * t;
|
||||
}
|
||||
|
||||
public static float OutQuad(float t)
|
||||
{
|
||||
return 1 - (1 - t) * (1 - t);
|
||||
}
|
||||
|
||||
public static float InOutQuad(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return 2 * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = (-2 * t + 2);
|
||||
return 1 - ((x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInQuad(float t) => OutIn(OutQuad, InQuad, t);
|
||||
|
||||
// CUBIC
|
||||
|
||||
public static float InCubic(float t)
|
||||
{
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
public static float OutCubic(float t)
|
||||
{
|
||||
var x = 1 - t;
|
||||
return 1 - (x * x * x);
|
||||
}
|
||||
|
||||
public static float InOutCubic(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return 4 * t * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return 1 - ((x * x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInCubic(float t) => OutIn(OutCubic, InCubic, t);
|
||||
|
||||
// QUARTIC
|
||||
|
||||
public static float InQuart(float t)
|
||||
{
|
||||
return t * t * t * t;
|
||||
}
|
||||
|
||||
public static float OutQuart(float t)
|
||||
{
|
||||
var x = 1 - t;
|
||||
return 1 - (x * x * x * x);
|
||||
}
|
||||
|
||||
public static float InOutQuart(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return 8 * t * t * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return 1 - ((x * x * x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInQuart(float t) => OutIn(OutQuart, InQuart, t);
|
||||
|
||||
// QUINTIC
|
||||
|
||||
public static float InQuint(float t)
|
||||
{
|
||||
return t * t * t * t * t;
|
||||
}
|
||||
|
||||
public static float OutQuint(float t)
|
||||
{
|
||||
var x = 1 - t;
|
||||
return 1 - (x * x * x * x * x);
|
||||
}
|
||||
|
||||
public static float InOutQuint(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return 16 * t * t * t * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return 1 - ((x * x * x * x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInQuint(float t) => OutIn(OutQuint, InQuint, t);
|
||||
|
||||
// SINE
|
||||
|
||||
public static float InSine(float t)
|
||||
{
|
||||
return 1 - System.MathF.Cos((t * System.MathF.PI) / 2);
|
||||
}
|
||||
|
||||
public static float OutSine(float t)
|
||||
{
|
||||
return System.MathF.Sin((t * System.MathF.PI) / 2);
|
||||
}
|
||||
|
||||
public static float InOutSine(float t)
|
||||
{
|
||||
return -(System.MathF.Cos(System.MathF.PI * t) - 1) / 2;
|
||||
}
|
||||
|
||||
public static float OutInSine(float t) => OutIn(OutSine, InSine, t);
|
||||
|
||||
// EXPONENTIAL
|
||||
|
||||
public static float InExpo(float t)
|
||||
{
|
||||
if (t == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return System.MathF.Pow(2, 10 * t - 10);
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutExpo(float t)
|
||||
{
|
||||
if (t == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1 - System.MathF.Pow(2, -10 * t);
|
||||
}
|
||||
}
|
||||
|
||||
public static float InOutExpo(float t)
|
||||
{
|
||||
if (t == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (t == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (t < 0.5f)
|
||||
{
|
||||
return System.MathF.Pow(2, 20 * t - 10) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (2 - System.MathF.Pow(2, -20 * t + 10)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInExpo(float t) => OutIn(OutExpo, InExpo, t);
|
||||
|
||||
// CIRCULAR
|
||||
|
||||
public static float InCirc(float t)
|
||||
{
|
||||
return 1 - System.MathF.Sqrt(1 - (t * t));
|
||||
}
|
||||
|
||||
public static float OutCirc(float t)
|
||||
{
|
||||
return System.MathF.Sqrt(1 - ((t - 1) * (t - 1)));
|
||||
}
|
||||
|
||||
public static float InOutCirc(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return (1 - System.MathF.Sqrt(1 - ((2 * t) * (2 * t)))) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return (System.MathF.Sqrt(1 - (x * x)) + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInCirc(float t) => OutIn(OutCirc, InCirc, t);
|
||||
|
||||
// BACK
|
||||
|
||||
public static float InBack(float t)
|
||||
{
|
||||
return C3 * t * t * t - C1 * t * t;
|
||||
}
|
||||
|
||||
public static float OutBack(float t)
|
||||
{
|
||||
return 1 + C3 * (t - 1) * (t - 1) * (t - 1) + C1 * (t - 1) * (t - 1);
|
||||
}
|
||||
|
||||
public static float InOutBack(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return ((2 * t) * (2 * t) * ((C2 + 1) * 2 * t - C2)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = 2 * t - 2;
|
||||
return ((t * t) * ((C2 + 1) * (x) + C2) + 2) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInBack(float t) => OutIn(OutBack, InBack, t);
|
||||
|
||||
// ELASTIC
|
||||
|
||||
public static float InElastic(float t)
|
||||
{
|
||||
if (t == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (t == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -System.MathF.Pow(2, 10 * t - 10) * System.MathF.Sin((t * 10 - 10.75f) * C4);
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutElastic(float t)
|
||||
{
|
||||
if (t == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (t == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return System.MathF.Pow(2, -10 * t) * System.MathF.Sin((t * 10 - 0.75f) * C4) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static float InOutElastic(float t)
|
||||
{
|
||||
if (t == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (t == 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (t < 0.5f)
|
||||
{
|
||||
return -(System.MathF.Pow(2, 20 * t - 10) * System.MathF.Sin((20 * t - 11.125f) * C5)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (System.MathF.Pow(2, -20 * t + 10) * System.MathF.Sin((20 * t - 11.125f) * C5)) / 2 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInElastic(float t) => OutIn(OutElastic, InElastic, t);
|
||||
|
||||
// BOUNCE
|
||||
|
||||
public static float InBounce(float t)
|
||||
{
|
||||
return 1 - OutBounce(1 - t);
|
||||
}
|
||||
|
||||
public static float OutBounce(float t)
|
||||
{
|
||||
const float N1 = 7.5625f;
|
||||
const float D1 = 2.75f;
|
||||
|
||||
if (t < 1 / D1)
|
||||
{
|
||||
return N1 * t * t;
|
||||
}
|
||||
else if (t < 2 / D1) {
|
||||
return N1 * (t -= 1.5f / D1) * t + 0.75f;
|
||||
}
|
||||
else if (t < 2.5f / D1)
|
||||
{
|
||||
return N1 * (t -= 2.25f / D1) * t + 0.9375f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return N1 * (t -= 2.625f / D1) * t + 0.984375f;
|
||||
}
|
||||
}
|
||||
|
||||
public static float InOutBounce(float t)
|
||||
{
|
||||
if (t < 0.5f)
|
||||
{
|
||||
return (1 - OutBounce(1 - 2 * t)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (1 + OutBounce(2 * t - 1)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static float OutInBounce(float t) => OutIn(OutBounce, InBounce, t);
|
||||
|
||||
/* FIXED EASING FUNCTIONS */
|
||||
|
||||
// LINEAR
|
||||
|
||||
public static Fix64 Linear(Fix64 t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
// QUADRATIC
|
||||
|
||||
public static Fix64 InQuad(Fix64 t)
|
||||
{
|
||||
return t * t;
|
||||
}
|
||||
|
||||
public static Fix64 OutQuad(Fix64 t)
|
||||
{
|
||||
return 1 - (1 - t) * (1 - t);
|
||||
}
|
||||
|
||||
public static Fix64 InOutQuad(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return 2 * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = (-2 * t + 2);
|
||||
return 1 - ((x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInQuad(Fix64 t) => OutIn(OutQuad, InQuad, t);
|
||||
|
||||
// CUBIC
|
||||
|
||||
public static Fix64 InCubic(Fix64 t)
|
||||
{
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
public static Fix64 OutCubic(Fix64 t)
|
||||
{
|
||||
var x = 1 - t;
|
||||
return 1 - (x * x * x);
|
||||
}
|
||||
|
||||
public static Fix64 InOutCubic(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return 4 * t * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return 1 - ((x * x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInCubic(Fix64 t) => OutIn(OutCubic, InCubic, t);
|
||||
|
||||
// QUARTIC
|
||||
|
||||
public static Fix64 InQuart(Fix64 t)
|
||||
{
|
||||
return t * t * t * t;
|
||||
}
|
||||
|
||||
public static Fix64 OutQuart(Fix64 t)
|
||||
{
|
||||
var x = 1 - t;
|
||||
return 1 - (x * x * x * x);
|
||||
}
|
||||
|
||||
public static Fix64 InOutQuart(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return 8 * t * t * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return 1 - ((x * x * x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInQuart(Fix64 t) => OutIn(OutQuart, InQuart, t);
|
||||
|
||||
// QUINTIC
|
||||
|
||||
public static Fix64 InQuint(Fix64 t)
|
||||
{
|
||||
return t * t * t * t * t;
|
||||
}
|
||||
|
||||
public static Fix64 OutQuint(Fix64 t)
|
||||
{
|
||||
var x = 1 - t;
|
||||
return 1 - (x * x * x * x * x);
|
||||
}
|
||||
|
||||
public static Fix64 InOutQuint(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return 16 * t * t * t * t * t;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return 1 - ((x * x * x * x * x) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInQuint(Fix64 t) => OutIn(OutQuint, InQuint, t);
|
||||
|
||||
// SINE
|
||||
|
||||
public static Fix64 InSine(Fix64 t)
|
||||
{
|
||||
return 1 - Fix64.Cos((t * Fix64.Pi) / 2);
|
||||
}
|
||||
|
||||
public static Fix64 OutSine(Fix64 t)
|
||||
{
|
||||
return Fix64.Sin((t * Fix64.Pi) / 2);
|
||||
}
|
||||
|
||||
public static Fix64 InOutSine(Fix64 t)
|
||||
{
|
||||
return -(Fix64.Cos(Fix64.Pi * t) - 1) / 2;
|
||||
}
|
||||
|
||||
public static Fix64 OutInSine(Fix64 t) => OutIn(OutSine, InSine, t);
|
||||
|
||||
// CIRCULAR
|
||||
|
||||
public static Fix64 InCirc(Fix64 t)
|
||||
{
|
||||
return 1 - Fix64.Sqrt(1 - (t * t));
|
||||
}
|
||||
|
||||
public static Fix64 OutCirc(Fix64 t)
|
||||
{
|
||||
return Fix64.Sqrt(1 - ((t - 1) * (t - 1)));
|
||||
}
|
||||
|
||||
public static Fix64 InOutCirc(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return (1 - Fix64.Sqrt(1 - ((2 * t) * (2 * t)))) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = -2 * t + 2;
|
||||
return (Fix64.Sqrt(1 - (x * x)) + 1) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInCirc(Fix64 t) => OutIn(OutCirc, InCirc, t);
|
||||
|
||||
// BACK
|
||||
|
||||
public static Fix64 InBack(Fix64 t)
|
||||
{
|
||||
return FIXED_C3 * t * t * t - FIXED_C1 * t * t;
|
||||
}
|
||||
|
||||
public static Fix64 OutBack(Fix64 t)
|
||||
{
|
||||
return 1 + FIXED_C3 * (t - 1) * (t - 1) * (t - 1) + FIXED_C1 * (t - 1) * (t - 1);
|
||||
}
|
||||
|
||||
public static Fix64 InOutBack(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return ((2 * t) * (2 * t) * ((FIXED_C2 + 1) * 2 * t - FIXED_C2)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = 2 * t - 2;
|
||||
return ((t * t) * ((FIXED_C2 + 1) * (x) + FIXED_C2) + 2) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInBack(Fix64 t) => OutIn(OutBack, InBack, t);
|
||||
|
||||
// BOUNCE
|
||||
|
||||
public static Fix64 InBounce(Fix64 t)
|
||||
{
|
||||
return 1 - OutBounce(1 - t);
|
||||
}
|
||||
|
||||
public static Fix64 OutBounce(Fix64 t)
|
||||
{
|
||||
if (t < 1 / FIXED_D1)
|
||||
{
|
||||
return FIXED_N1 * t * t;
|
||||
}
|
||||
else if (t < 2 / FIXED_D1) {
|
||||
return FIXED_N1 * (t -= Fix64.FromFraction(3, 2) / FIXED_D1) * t + Fix64.FromFraction(3, 4);
|
||||
}
|
||||
else if (t < Fix64.FromFraction(5, 2) / FIXED_D1)
|
||||
{
|
||||
return FIXED_N1 * (t -= Fix64.FromFraction(9, 4) / FIXED_D1) * t + Fix64.FromFraction(15, 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
return FIXED_N1 * (t -= Fix64.FromFraction(181, 80) / FIXED_D1) * t + Fix64.FromFraction(63, 64);
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 InOutBounce(Fix64 t)
|
||||
{
|
||||
if (t < HALF)
|
||||
{
|
||||
return (1 - OutBounce(1 - 2 * t)) / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (1 + OutBounce(2 * t - 1)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static Fix64 OutInBounce(Fix64 t) => OutIn(OutBounce, InBounce, t);
|
||||
|
||||
public static class Function
|
||||
{
|
||||
public enum Float
|
||||
{
|
||||
Linear,
|
||||
InQuad,
|
||||
OutQuad,
|
||||
InOutQuad,
|
||||
OutInQuad,
|
||||
InCubic,
|
||||
OutCubic,
|
||||
InOutCubic,
|
||||
OutInCubic,
|
||||
InQuart,
|
||||
OutQuart,
|
||||
InOutQuart,
|
||||
OutInQuart,
|
||||
InQuint,
|
||||
OutQuint,
|
||||
InOutQuint,
|
||||
OutInQuint,
|
||||
InSine,
|
||||
OutSine,
|
||||
InOutSine,
|
||||
OutInSine,
|
||||
InExpo,
|
||||
OutExpo,
|
||||
InOutExpo,
|
||||
OutInExpo,
|
||||
InCirc,
|
||||
OutCirc,
|
||||
InOutCirc,
|
||||
OutInCirc,
|
||||
InElastic,
|
||||
OutElastic,
|
||||
InOutElastic,
|
||||
OutInElastic,
|
||||
InBack,
|
||||
OutBack,
|
||||
InOutBack,
|
||||
OutInBack,
|
||||
InBounce,
|
||||
OutBounce,
|
||||
InOutBounce,
|
||||
OutInBounce
|
||||
}
|
||||
|
||||
public enum Fixed
|
||||
{
|
||||
Linear,
|
||||
InQuad,
|
||||
OutQuad,
|
||||
InOutQuad,
|
||||
OutInQuad,
|
||||
InCubic,
|
||||
OutCubic,
|
||||
InOutCubic,
|
||||
OutInCubic,
|
||||
InQuart,
|
||||
OutQuart,
|
||||
InOutQuart,
|
||||
OutInQuart,
|
||||
InQuint,
|
||||
OutQuint,
|
||||
InOutQuint,
|
||||
OutInQuint,
|
||||
InSine,
|
||||
OutSine,
|
||||
InOutSine,
|
||||
OutInSine,
|
||||
InCirc,
|
||||
OutCirc,
|
||||
InOutCirc,
|
||||
OutInCirc,
|
||||
InBack,
|
||||
OutBack,
|
||||
InOutBack,
|
||||
OutInBack,
|
||||
InBounce,
|
||||
OutBounce,
|
||||
InOutBounce,
|
||||
OutInBounce
|
||||
}
|
||||
|
||||
private static Dictionary<Float, System.Func<float, float>> FloatLookup = new Dictionary<Float, System.Func<float, float>>
|
||||
{
|
||||
{ Float.Linear, Linear },
|
||||
{ Float.InQuad, InQuad },
|
||||
{ Float.OutQuad, OutQuad },
|
||||
{ Float.InOutQuad, InOutQuad },
|
||||
{ Float.OutInQuad, OutInQuad },
|
||||
{ Float.InCubic, InCubic },
|
||||
{ Float.OutCubic, OutCubic },
|
||||
{ Float.InOutCubic, InOutCubic },
|
||||
{ Float.OutInCubic, OutInCubic },
|
||||
{ Float.InQuart, InQuart },
|
||||
{ Float.OutQuart, OutQuart },
|
||||
{ Float.InOutQuart, InOutQuart },
|
||||
{ Float.OutInQuart, OutInQuart },
|
||||
{ Float.InQuint, InQuint },
|
||||
{ Float.OutQuint, OutQuint },
|
||||
{ Float.InOutQuint, InOutQuint },
|
||||
{ Float.OutInQuint, OutInQuint },
|
||||
{ Float.InSine, InSine },
|
||||
{ Float.OutSine, OutSine },
|
||||
{ Float.InOutSine, InOutSine },
|
||||
{ Float.OutInSine, OutInSine },
|
||||
{ Float.InExpo, InExpo },
|
||||
{ Float.OutExpo, OutExpo },
|
||||
{ Float.InOutExpo, InOutExpo },
|
||||
{ Float.OutInExpo, OutInExpo },
|
||||
{ Float.InCirc, InCirc },
|
||||
{ Float.OutCirc, OutCirc },
|
||||
{ Float.InOutCirc, InOutCirc },
|
||||
{ Float.OutInCirc, OutInCirc },
|
||||
{ Float.InElastic, InElastic },
|
||||
{ Float.OutElastic, OutElastic },
|
||||
{ Float.InOutElastic, InOutElastic },
|
||||
{ Float.OutInElastic, OutInElastic },
|
||||
{ Float.InBack, InBack },
|
||||
{ Float.OutBack, OutBack },
|
||||
{ Float.InOutBack, InOutBack },
|
||||
{ Float.OutInBack, OutInBack },
|
||||
{ Float.InBounce, InBounce },
|
||||
{ Float.OutBounce, OutBounce },
|
||||
{ Float.InOutBounce, InOutBounce },
|
||||
{ Float.OutInBounce, OutInBounce }
|
||||
};
|
||||
|
||||
private static Dictionary<Fixed, System.Func<Fix64, Fix64>> FixedLookup = new Dictionary<Fixed, System.Func<Fix64, Fix64>>
|
||||
{
|
||||
{ Fixed.Linear, Linear },
|
||||
{ Fixed.InQuad, InQuad },
|
||||
{ Fixed.OutQuad, OutQuad },
|
||||
{ Fixed.InOutQuad, InOutQuad },
|
||||
{ Fixed.OutInQuad, OutInQuad },
|
||||
{ Fixed.InCubic, InCubic },
|
||||
{ Fixed.OutCubic, OutCubic },
|
||||
{ Fixed.InOutCubic, InOutCubic },
|
||||
{ Fixed.OutInCubic, OutInCubic },
|
||||
{ Fixed.InQuart, InQuart },
|
||||
{ Fixed.OutQuart, OutQuart },
|
||||
{ Fixed.InOutQuart, InOutQuart },
|
||||
{ Fixed.OutInQuart, OutInQuart },
|
||||
{ Fixed.InQuint, InQuint },
|
||||
{ Fixed.OutQuint, OutQuint },
|
||||
{ Fixed.InOutQuint, InOutQuint },
|
||||
{ Fixed.OutInQuint, OutInQuint },
|
||||
{ Fixed.InSine, InSine },
|
||||
{ Fixed.OutSine, OutSine },
|
||||
{ Fixed.InOutSine, InOutSine },
|
||||
{ Fixed.OutInSine, OutInSine },
|
||||
{ Fixed.InCirc, InCirc },
|
||||
{ Fixed.OutCirc, OutCirc },
|
||||
{ Fixed.InOutCirc, InOutCirc },
|
||||
{ Fixed.OutInCirc, OutInCirc },
|
||||
{ Fixed.InBack, InBack },
|
||||
{ Fixed.OutBack, OutBack },
|
||||
{ Fixed.InOutBack, InOutBack },
|
||||
{ Fixed.OutInBack, OutInBack },
|
||||
{ Fixed.InBounce, InBounce },
|
||||
{ Fixed.OutBounce, OutBounce },
|
||||
{ Fixed.InOutBounce, InOutBounce },
|
||||
{ Fixed.OutInBounce, OutInBounce }
|
||||
};
|
||||
|
||||
public static System.Func<float, float> Get(Float functionEnum)
|
||||
{
|
||||
return FloatLookup[functionEnum];
|
||||
}
|
||||
|
||||
public static System.Func<Fix64, Fix64> Get(Fixed functionEnum)
|
||||
{
|
||||
return FixedLookup[functionEnum];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,846 @@
|
|||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2022 Evan Hemsley
|
||||
*/
|
||||
|
||||
/* Derived from code by Microsoft.
|
||||
* Released under the MIT license.
|
||||
* See microsoft.LICENSE for details.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MoonWorks.Math.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure encapsulating a 3x2 fixed point matrix.
|
||||
/// </summary>
|
||||
public struct Matrix3x2 : IEquatable<Matrix3x2>
|
||||
{
|
||||
#region Public Fields
|
||||
/// <summary>
|
||||
/// The first element of the first row
|
||||
/// </summary>
|
||||
public Fix64 M11;
|
||||
/// <summary>
|
||||
/// The second element of the first row
|
||||
/// </summary>
|
||||
public Fix64 M12;
|
||||
/// <summary>
|
||||
/// The first element of the second row
|
||||
/// </summary>
|
||||
public Fix64 M21;
|
||||
/// <summary>
|
||||
/// The second element of the second row
|
||||
/// </summary>
|
||||
public Fix64 M22;
|
||||
/// <summary>
|
||||
/// The first element of the third row
|
||||
/// </summary>
|
||||
public Fix64 M31;
|
||||
/// <summary>
|
||||
/// The second element of the third row
|
||||
/// </summary>
|
||||
public Fix64 M32;
|
||||
#endregion Public Fields
|
||||
|
||||
private static readonly Matrix3x2 _identity = new Matrix3x2
|
||||
(
|
||||
1, 0,
|
||||
0, 1,
|
||||
0, 0
|
||||
);
|
||||
|
||||
private static readonly Fix64 RotationEpsilon = Fix64.FromFraction(1, 1000) * (Fix64.Pi / new Fix64(180));
|
||||
|
||||
/// <summary>
|
||||
/// Returns the multiplicative identity matrix.
|
||||
/// </summary>
|
||||
public static Matrix3x2 Identity
|
||||
{
|
||||
get { return _identity; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the matrix is the identity matrix.
|
||||
/// </summary>
|
||||
public bool IsIdentity
|
||||
{
|
||||
get
|
||||
{
|
||||
return M11 == Fix64.One && M22 == Fix64.One && // Check diagonal element first for early out.
|
||||
M12 == Fix64.Zero &&
|
||||
M21 == Fix64.Zero &&
|
||||
M31 == Fix64.Zero && M32 == Fix64.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the translation component of this matrix.
|
||||
/// </summary>
|
||||
public Vector2 Translation
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2(M31, M32);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
M31 = value.X;
|
||||
M32 = value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a FixMatrix3x2 from the given components.
|
||||
/// </summary>
|
||||
public Matrix3x2(Fix64 m11, Fix64 m12,
|
||||
Fix64 m21, Fix64 m22,
|
||||
Fix64 m31, Fix64 m32)
|
||||
{
|
||||
M11 = m11;
|
||||
M12 = m12;
|
||||
M21 = m21;
|
||||
M22 = m22;
|
||||
M31 = m31;
|
||||
M32 = m32;
|
||||
}
|
||||
|
||||
public Matrix3x2(int m11, int m12, int m21, int m22, int m31, int m32)
|
||||
{
|
||||
M11 = new Fix64(m11);
|
||||
M12 = new Fix64(m12);
|
||||
M21 = new Fix64(m21);
|
||||
M22 = new Fix64(m22);
|
||||
M31 = new Fix64(m31);
|
||||
M32 = new Fix64(m32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation matrix from the given vector.
|
||||
/// </summary>
|
||||
/// <param name="position">The translation position.</param>
|
||||
/// <returns>A translation matrix.</returns>
|
||||
public static Matrix3x2 CreateTranslation(Vector2 position)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = Fix64.One;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = Fix64.One;
|
||||
|
||||
result.M31 = position.X;
|
||||
result.M32 = position.Y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation matrix from the given X and Y components.
|
||||
/// </summary>
|
||||
/// <param name="xPosition">The X position.</param>
|
||||
/// <param name="yPosition">The Y position.</param>
|
||||
/// <returns>A translation matrix.</returns>
|
||||
public static Matrix3x2 CreateTranslation(Fix64 xPosition, Fix64 yPosition)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = Fix64.One;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = Fix64.One;
|
||||
|
||||
result.M31 = xPosition;
|
||||
result.M32 = yPosition;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given X and Y components.
|
||||
/// </summary>
|
||||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
||||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Fix64 xScale, Fix64 yScale)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = xScale;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = yScale;
|
||||
result.M31 = Fix64.Zero;
|
||||
result.M32 = Fix64.Zero;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that is offset by a given center point.
|
||||
/// </summary>
|
||||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
||||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Fix64 xScale, Fix64 yScale, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
Fix64 tx = centerPoint.X * (Fix64.One - xScale);
|
||||
Fix64 ty = centerPoint.Y * (Fix64.One - yScale);
|
||||
|
||||
result.M11 = xScale;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = yScale;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given vector scale.
|
||||
/// </summary>
|
||||
/// <param name="scales">The scale to use.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Vector2 scales)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = scales.X;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = scales.Y;
|
||||
result.M31 = Fix64.Zero;
|
||||
result.M32 = Fix64.Zero;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
|
||||
/// </summary>
|
||||
/// <param name="scales">The scale to use.</param>
|
||||
/// <param name="centerPoint">The center offset.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Vector2 scales, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
Fix64 tx = centerPoint.X * (Fix64.One - scales.X);
|
||||
Fix64 ty = centerPoint.Y * (Fix64.One - scales.Y);
|
||||
|
||||
result.M11 = scales.X;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = scales.Y;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that scales uniformly with the given scale.
|
||||
/// </summary>
|
||||
/// <param name="scale">The uniform scale to use.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Fix64 scale)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = scale;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = scale;
|
||||
result.M31 = Fix64.Zero;
|
||||
result.M32 = Fix64.Zero;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
|
||||
/// </summary>
|
||||
/// <param name="scale">The uniform scale to use.</param>
|
||||
/// <param name="centerPoint">The center offset.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Fix64 scale, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
Fix64 tx = centerPoint.X * (Fix64.One - scale);
|
||||
Fix64 ty = centerPoint.Y * (Fix64.One - scale);
|
||||
|
||||
result.M11 = scale;
|
||||
result.M12 = Fix64.Zero;
|
||||
result.M21 = Fix64.Zero;
|
||||
result.M22 = scale;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix from the given angles in radians.
|
||||
/// </summary>
|
||||
/// <param name="radiansX">The X angle, in radians.</param>
|
||||
/// <param name="radiansY">The Y angle, in radians.</param>
|
||||
/// <returns>A skew matrix.</returns>
|
||||
public static Matrix3x2 CreateSkew(Fix64 radiansX, Fix64 radiansY)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
Fix64 xTan = (Fix64) Fix64.Tan(radiansX);
|
||||
Fix64 yTan = (Fix64) Fix64.Tan(radiansY);
|
||||
|
||||
result.M11 = Fix64.One;
|
||||
result.M12 = yTan;
|
||||
result.M21 = xTan;
|
||||
result.M22 = Fix64.One;
|
||||
result.M31 = Fix64.Zero;
|
||||
result.M32 = Fix64.Zero;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix from the given angles in radians and a center point.
|
||||
/// </summary>
|
||||
/// <param name="radiansX">The X angle, in radians.</param>
|
||||
/// <param name="radiansY">The Y angle, in radians.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A skew matrix.</returns>
|
||||
public static Matrix3x2 CreateSkew(Fix64 radiansX, Fix64 radiansY, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
Fix64 xTan = (Fix64) Fix64.Tan(radiansX);
|
||||
Fix64 yTan = (Fix64) Fix64.Tan(radiansY);
|
||||
|
||||
Fix64 tx = -centerPoint.Y * xTan;
|
||||
Fix64 ty = -centerPoint.X * yTan;
|
||||
|
||||
result.M11 = Fix64.One;
|
||||
result.M12 = yTan;
|
||||
result.M21 = xTan;
|
||||
result.M22 = Fix64.One;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rotation matrix using the given rotation in radians.
|
||||
/// </summary>
|
||||
/// <param name="radians">The amount of rotation, in radians.</param>
|
||||
/// <returns>A rotation matrix.</returns>
|
||||
public static Matrix3x2 CreateRotation(Fix64 radians)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
radians = Fix64.IEEERemainder(radians, Fix64.PiTimes2);
|
||||
|
||||
Fix64 c, s;
|
||||
|
||||
if (radians > -RotationEpsilon && radians < RotationEpsilon)
|
||||
{
|
||||
// Exact case for zero rotation.
|
||||
c = Fix64.One;
|
||||
s = Fix64.Zero;
|
||||
}
|
||||
else if (radians > Fix64.PiOver2 - RotationEpsilon && radians < Fix64.PiOver2 + RotationEpsilon)
|
||||
{
|
||||
// Exact case for 90 degree rotation.
|
||||
c = Fix64.Zero;
|
||||
s = Fix64.One;
|
||||
}
|
||||
else if (radians < -Fix64.Pi + RotationEpsilon || radians > Fix64.Pi - RotationEpsilon)
|
||||
{
|
||||
// Exact case for 180 degree rotation.
|
||||
c = -Fix64.One;
|
||||
s = Fix64.Zero;
|
||||
}
|
||||
else if (radians > -Fix64.PiOver2 - RotationEpsilon && radians < -Fix64.PiOver2 + RotationEpsilon)
|
||||
{
|
||||
// Exact case for 270 degree rotation.
|
||||
c = Fix64.Zero;
|
||||
s = -Fix64.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arbitrary rotation.
|
||||
c = Fix64.Cos(radians);
|
||||
s = Fix64.Sin(radians);
|
||||
}
|
||||
|
||||
// [ c s ]
|
||||
// [ -s c ]
|
||||
// [ 0 0 ]
|
||||
result.M11 = c;
|
||||
result.M12 = s;
|
||||
result.M21 = -s;
|
||||
result.M22 = c;
|
||||
result.M31 = Fix64.Zero;
|
||||
result.M32 = Fix64.Zero;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rotation matrix using the given rotation in radians and a center point.
|
||||
/// </summary>
|
||||
/// <param name="radians">The amount of rotation, in radians.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A rotation matrix.</returns>
|
||||
public static Matrix3x2 CreateRotation(Fix64 radians, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
radians = Fix64.IEEERemainder(radians, Fix64.PiTimes2);
|
||||
|
||||
Fix64 c, s;
|
||||
|
||||
if (radians > -RotationEpsilon && radians < RotationEpsilon)
|
||||
{
|
||||
// Exact case for zero rotation.
|
||||
c = Fix64.One;
|
||||
s = Fix64.Zero;
|
||||
}
|
||||
else if (radians > Fix64.PiOver2 - RotationEpsilon && radians < Fix64.PiOver2 + RotationEpsilon)
|
||||
{
|
||||
// Exact case for 90 degree rotation.
|
||||
c = Fix64.Zero;
|
||||
s = Fix64.One;
|
||||
}
|
||||
else if (radians < -Fix64.Pi + RotationEpsilon || radians > Fix64.Pi - RotationEpsilon)
|
||||
{
|
||||
// Exact case for 180 degree rotation.
|
||||
c = -Fix64.One;
|
||||
s = Fix64.Zero;
|
||||
}
|
||||
else if (radians > -Fix64.PiOver2 - RotationEpsilon && radians < -Fix64.PiOver2 + RotationEpsilon)
|
||||
{
|
||||
// Exact case for 270 degree rotation.
|
||||
c = Fix64.Zero;
|
||||
s = -Fix64.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arbitrary rotation.
|
||||
c = (Fix64) Fix64.Cos(radians);
|
||||
s = (Fix64) Fix64.Sin(radians);
|
||||
}
|
||||
|
||||
Fix64 x = centerPoint.X * (Fix64.One - c) + centerPoint.Y * s;
|
||||
Fix64 y = centerPoint.Y * (Fix64.One - c) - centerPoint.X * s;
|
||||
|
||||
// [ c s ]
|
||||
// [ -s c ]
|
||||
// [ x y ]
|
||||
result.M11 = c;
|
||||
result.M12 = s;
|
||||
result.M21 = -s;
|
||||
result.M22 = c;
|
||||
result.M31 = x;
|
||||
result.M32 = y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the determinant for this matrix.
|
||||
/// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1).
|
||||
/// </summary>
|
||||
/// <returns>The determinant.</returns>
|
||||
public Fix64 GetDeterminant()
|
||||
{
|
||||
// There isn't actually any such thing as a determinant for a non-square matrix,
|
||||
// but this 3x2 type is really just an optimization of a 3x3 where we happen to
|
||||
// know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
|
||||
//
|
||||
// [ M11, M12, 0 ]
|
||||
// [ M21, M22, 0 ]
|
||||
// [ M31, M32, 1 ]
|
||||
//
|
||||
// Sum the diagonal products:
|
||||
// (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
|
||||
//
|
||||
// Subtract the opposite diagonal products:
|
||||
// (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
|
||||
//
|
||||
// Collapse out the constants and oh look, this is just a 2x2 determinant!
|
||||
|
||||
return (M11 * M22) - (M21 * M12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to invert the given matrix. If the operation succeeds, the inverted matrix is stored in the result parameter.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The source matrix.</param>
|
||||
/// <param name="result">The output matrix.</param>
|
||||
/// <returns>True if the operation succeeded, False otherwise.</returns>
|
||||
public static bool Invert(Matrix3x2 matrix, out Matrix3x2 result)
|
||||
{
|
||||
Fix64 det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12);
|
||||
|
||||
if (Fix64.Abs(det) == Fix64.Zero)
|
||||
{
|
||||
result = new Matrix3x2(Fix64.Zero, Fix64.Zero, Fix64.Zero, Fix64.Zero, Fix64.Zero, Fix64.Zero);
|
||||
return false;
|
||||
}
|
||||
|
||||
Fix64 invDet = Fix64.One / det;
|
||||
|
||||
result.M11 = matrix.M22 * invDet;
|
||||
result.M12 = -matrix.M12 * invDet;
|
||||
result.M21 = -matrix.M21 * invDet;
|
||||
result.M22 = matrix.M11 * invDet;
|
||||
result.M31 = (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet;
|
||||
result.M32 = (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates from matrix1 to matrix2, based on the third parameter.
|
||||
/// </summary>
|
||||
/// <param name="matrix1">The first source matrix.</param>
|
||||
/// <param name="matrix2">The second source matrix.</param>
|
||||
/// <param name="amount">The relative weighting of matrix2.</param>
|
||||
/// <returns>The interpolated matrix.</returns>
|
||||
public static Matrix3x2 Lerp(Matrix3x2 matrix1, Matrix3x2 matrix2, Fix64 amount)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
// First row
|
||||
result.M11 = matrix1.M11 + (matrix2.M11 - matrix1.M11) * amount;
|
||||
result.M12 = matrix1.M12 + (matrix2.M12 - matrix1.M12) * amount;
|
||||
|
||||
// Second row
|
||||
result.M21 = matrix1.M21 + (matrix2.M21 - matrix1.M21) * amount;
|
||||
result.M22 = matrix1.M22 + (matrix2.M22 - matrix1.M22) * amount;
|
||||
|
||||
// Third row
|
||||
result.M31 = matrix1.M31 + (matrix2.M31 - matrix1.M31) * amount;
|
||||
result.M32 = matrix1.M32 + (matrix2.M32 - matrix1.M32) * amount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the given matrix by multiplying all values by -1.
|
||||
/// </summary>
|
||||
/// <param name="value">The source matrix.</param>
|
||||
/// <returns>The negated matrix.</returns>
|
||||
public static Matrix3x2 Negate(Matrix3x2 value)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = -value.M11;
|
||||
result.M12 = -value.M12;
|
||||
result.M21 = -value.M21;
|
||||
result.M22 = -value.M22;
|
||||
result.M31 = -value.M31;
|
||||
result.M32 = -value.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds each matrix element in value1 with its corresponding element in value2.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the summed values.</returns>
|
||||
public static Matrix3x2 Add(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 + value2.M11;
|
||||
result.M12 = value1.M12 + value2.M12;
|
||||
result.M21 = value1.M21 + value2.M21;
|
||||
result.M22 = value1.M22 + value2.M22;
|
||||
result.M31 = value1.M31 + value2.M31;
|
||||
result.M32 = value1.M32 + value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts each matrix element in value2 from its corresponding element in value1.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the resulting values.</returns>
|
||||
public static Matrix3x2 Subtract(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 - value2.M11;
|
||||
result.M12 = value1.M12 - value2.M12;
|
||||
result.M21 = value1.M21 - value2.M21;
|
||||
result.M22 = value1.M22 - value2.M22;
|
||||
result.M31 = value1.M31 - value2.M31;
|
||||
result.M32 = value1.M32 - value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two matrices together and returns the resulting matrix.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The product matrix.</returns>
|
||||
public static Matrix3x2 Multiply(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
// First row
|
||||
result.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
result.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
result.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
result.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
// Third row
|
||||
result.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
|
||||
result.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Matrix4x4 ToMatrix4x4()
|
||||
{
|
||||
return new Matrix4x4(
|
||||
M11, M12, Fix64.Zero, Fix64.Zero,
|
||||
M21, M22, Fix64.Zero, Fix64.Zero,
|
||||
Fix64.Zero, Fix64.Zero, Fix64.One, Fix64.Zero,
|
||||
M31, M32, Fix64.Zero, Fix64.One
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales all elements in a matrix by the given scalar factor.
|
||||
/// </summary>
|
||||
/// <param name="value1">The source matrix.</param>
|
||||
/// <param name="value2">The scaling value to use.</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
public static Matrix3x2 Multiply(Matrix3x2 value1, Fix64 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 * value2;
|
||||
result.M12 = value1.M12 * value2;
|
||||
result.M21 = value1.M21 * value2;
|
||||
result.M22 = value1.M22 * value2;
|
||||
result.M31 = value1.M31 * value2;
|
||||
result.M32 = value1.M32 * value2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the given matrix by multiplying all values by -1.
|
||||
/// </summary>
|
||||
/// <param name="value">The source matrix.</param>
|
||||
/// <returns>The negated matrix.</returns>
|
||||
public static Matrix3x2 operator -(Matrix3x2 value)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = -value.M11;
|
||||
m.M12 = -value.M12;
|
||||
m.M21 = -value.M21;
|
||||
m.M22 = -value.M22;
|
||||
m.M31 = -value.M31;
|
||||
m.M32 = -value.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds each matrix element in value1 with its corresponding element in value2.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the summed values.</returns>
|
||||
public static Matrix3x2 operator +(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 + value2.M11;
|
||||
m.M12 = value1.M12 + value2.M12;
|
||||
m.M21 = value1.M21 + value2.M21;
|
||||
m.M22 = value1.M22 + value2.M22;
|
||||
m.M31 = value1.M31 + value2.M31;
|
||||
m.M32 = value1.M32 + value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts each matrix element in value2 from its corresponding element in value1.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the resulting values.</returns>
|
||||
public static Matrix3x2 operator -(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 - value2.M11;
|
||||
m.M12 = value1.M12 - value2.M12;
|
||||
m.M21 = value1.M21 - value2.M21;
|
||||
m.M22 = value1.M22 - value2.M22;
|
||||
m.M31 = value1.M31 - value2.M31;
|
||||
m.M32 = value1.M32 - value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two matrices together and returns the resulting matrix.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The product matrix.</returns>
|
||||
public static Matrix3x2 operator *(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
// First row
|
||||
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
// Third row
|
||||
m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
|
||||
m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales all elements in a matrix by the given scalar factor.
|
||||
/// </summary>
|
||||
/// <param name="value1">The source matrix.</param>
|
||||
/// <param name="value2">The scaling value to use.</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
public static Matrix3x2 operator *(Matrix3x2 value1, Fix64 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 * value2;
|
||||
m.M12 = value1.M12 * value2;
|
||||
m.M21 = value1.M21 * value2;
|
||||
m.M22 = value1.M22 * value2;
|
||||
m.M31 = value1.M31 * value2;
|
||||
m.M32 = value1.M32 * value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given matrices are equal.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>True if the matrices are equal; False otherwise.</returns>
|
||||
public static bool operator ==(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && // Check diagonal element first for early out.
|
||||
value1.M12 == value2.M12 &&
|
||||
value1.M21 == value2.M21 &&
|
||||
value1.M31 == value2.M31 && value1.M32 == value2.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given matrices are not equal.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>True if the matrices are not equal; False if they are equal.</returns>
|
||||
public static bool operator !=(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
return (value1.M11 != value2.M11 || value1.M12 != value2.M12 ||
|
||||
value1.M21 != value2.M21 || value1.M22 != value2.M22 ||
|
||||
value1.M31 != value2.M31 || value1.M32 != value2.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts to floating point Matrix3x2.
|
||||
/// </summary>
|
||||
public static explicit operator Math.Float.Matrix3x2(Matrix3x2 matrix)
|
||||
{
|
||||
return new Math.Float.Matrix3x2(
|
||||
(float) matrix.M11, (float) matrix.M12,
|
||||
(float) matrix.M21, (float) matrix.M22,
|
||||
(float) matrix.M31, (float) matrix.M32
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
|
||||
/// </summary>
|
||||
/// <param name="other">The other matrix to test equality against.</param>
|
||||
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
|
||||
public bool Equals(Matrix3x2 other)
|
||||
{
|
||||
return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out.
|
||||
M12 == other.M12 &&
|
||||
M21 == other.M21 &&
|
||||
M31 == other.M31 && M32 == other.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given Object is equal to this matrix instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The Object to compare against.</param>
|
||||
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Matrix3x2)
|
||||
{
|
||||
return Equals((Matrix3x2) obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a String representing this matrix instance.
|
||||
/// </summary>
|
||||
/// <returns>The string representation.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
CultureInfo ci = CultureInfo.CurrentCulture;
|
||||
return String.Format(ci, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}",
|
||||
M11.ToString(ci), M12.ToString(ci),
|
||||
M21.ToString(ci), M22.ToString(ci),
|
||||
M31.ToString(ci), M32.ToString(ci));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>The hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return M11.GetHashCode() + M12.GetHashCode() +
|
||||
M21.GetHashCode() + M22.GetHashCode() +
|
||||
M31.GetHashCode() + M32.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,876 @@
|
|||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2022 Evan Hemsley
|
||||
*/
|
||||
|
||||
/* Derived from code by Ethan Lee (Copyright 2009-2021).
|
||||
* Released under the Microsoft Public License.
|
||||
* See fna.LICENSE for details.
|
||||
|
||||
* Derived from code by the Mono.Xna Team (Copyright 2006).
|
||||
* Released under the MIT License. See monoxna.LICENSE for details.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// An efficient mathematical representation for three dimensional fixed point rotations.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
||||
public struct Quaternion : IEquatable<Quaternion>
|
||||
{
|
||||
#region Public Static Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns a quaternion representing no rotation.
|
||||
/// </summary>
|
||||
public static Quaternion Identity
|
||||
{
|
||||
get
|
||||
{
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Properties
|
||||
|
||||
internal string DebugDisplayString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this == Quaternion.Identity)
|
||||
{
|
||||
return "Identity";
|
||||
}
|
||||
|
||||
return string.Concat(
|
||||
X.ToString(), " ",
|
||||
Y.ToString(), " ",
|
||||
Z.ToString(), " ",
|
||||
W.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// The x coordinate of this <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
public Fix64 X;
|
||||
|
||||
/// <summary>
|
||||
/// The y coordinate of this <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
public Fix64 Y;
|
||||
|
||||
/// <summary>
|
||||
/// The z coordinate of this <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
public Fix64 Z;
|
||||
|
||||
/// <summary>
|
||||
/// The rotation component of this <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
public Fix64 W;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Static Variables
|
||||
|
||||
private static readonly Quaternion identity = new Quaternion(0, 0, 0, 1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a quaternion with X, Y, Z and W from four values.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate in 3d-space.</param>
|
||||
/// <param name="y">The y coordinate in 3d-space.</param>
|
||||
/// <param name="z">The z coordinate in 3d-space.</param>
|
||||
/// <param name="w">The rotation component.</param>
|
||||
public Quaternion(int x, int y, int z, int w)
|
||||
{
|
||||
X = new Fix64(x);
|
||||
Y = new Fix64(y);
|
||||
Z = new Fix64(z);
|
||||
W = new Fix64(w);
|
||||
}
|
||||
|
||||
public Quaternion(Fix64 x, Fix64 y, Fix64 z, Fix64 w)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
W = w;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a quaternion with X, Y, Z from <see cref="Vector3"/> and rotation component from a scalar.
|
||||
/// </summary>
|
||||
/// <param name="value">The x, y, z coordinates in 3d-space.</param>
|
||||
/// <param name="w">The rotation component.</param>
|
||||
public Quaternion(Vector3 vectorPart, Fix64 scalarPart)
|
||||
{
|
||||
X = vectorPart.X;
|
||||
Y = vectorPart.Y;
|
||||
Z = vectorPart.Z;
|
||||
W = scalarPart;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Transforms this quaternion into its conjugated version.
|
||||
/// </summary>
|
||||
public void Conjugate()
|
||||
{
|
||||
X = -X;
|
||||
Y = -Y;
|
||||
Z = -Z;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether current instance is equal to specified <see cref="Object"/>.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="Object"/> to compare.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is Quaternion) && Equals((Quaternion) obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether current instance is equal to specified <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="Quaternion"/> to compare.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Quaternion other)
|
||||
{
|
||||
return (X == other.X &&
|
||||
Y == other.Y &&
|
||||
Z == other.Z &&
|
||||
W == other.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash code of this <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <returns>Hash code of this <see cref="Quaternion"/>.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (
|
||||
this.X.GetHashCode() +
|
||||
this.Y.GetHashCode() +
|
||||
this.Z.GetHashCode() +
|
||||
this.W.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the magnitude of the quaternion components.
|
||||
/// </summary>
|
||||
/// <returns>The magnitude of the quaternion components.</returns>
|
||||
public Fix64 Length()
|
||||
{
|
||||
Fix64 num = (
|
||||
(this.X * this.X) +
|
||||
(this.Y * this.Y) +
|
||||
(this.Z * this.Z) +
|
||||
(this.W * this.W)
|
||||
);
|
||||
return (Fix64) Fix64.Sqrt(num);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the squared magnitude of the quaternion components.
|
||||
/// </summary>
|
||||
/// <returns>The squared magnitude of the quaternion components.</returns>
|
||||
public Fix64 LengthSquared()
|
||||
{
|
||||
return (
|
||||
(this.X * this.X) +
|
||||
(this.Y * this.Y) +
|
||||
(this.Z * this.Z) +
|
||||
(this.W * this.W)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="String"/> representation of this <see cref="Quaternion"/> in the format:
|
||||
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>] W:[<see cref="W"/>]}
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="String"/> representation of this <see cref="Quaternion"/>.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return (
|
||||
"{X:" + X.ToString() +
|
||||
" Y:" + Y.ToString() +
|
||||
" Z:" + Z.ToString() +
|
||||
" W:" + W.ToString() +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Static Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains the sum of two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The result of the quaternion addition.</returns>
|
||||
public static Quaternion Add(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Add(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains the sum of two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The result of the quaternion addition as an output parameter.</param>
|
||||
public static void Add(
|
||||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
result.X = quaternion1.X + quaternion2.X;
|
||||
result.Y = quaternion1.Y + quaternion2.Y;
|
||||
result.Z = quaternion1.Z + quaternion2.Z;
|
||||
result.W = quaternion1.W + quaternion2.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains concatenation between two quaternion.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first <see cref="Quaternion"/> to concatenate.</param>
|
||||
/// <param name="value2">The second <see cref="Quaternion"/> to concatenate.</param>
|
||||
/// <returns>The result of rotation of <paramref name="value1"/> followed by <paramref name="value2"/> rotation.</returns>
|
||||
public static Quaternion Concatenate(Quaternion value1, Quaternion value2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Concatenate(ref value1, ref value2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains concatenation between two quaternion.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first <see cref="Quaternion"/> to concatenate.</param>
|
||||
/// <param name="value2">The second <see cref="Quaternion"/> to concatenate.</param>
|
||||
/// <param name="result">The result of rotation of <paramref name="value1"/> followed by <paramref name="value2"/> rotation as an output parameter.</param>
|
||||
public static void Concatenate(
|
||||
ref Quaternion value1,
|
||||
ref Quaternion value2,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
Fix64 x1 = value1.X;
|
||||
Fix64 y1 = value1.Y;
|
||||
Fix64 z1 = value1.Z;
|
||||
Fix64 w1 = value1.W;
|
||||
|
||||
Fix64 x2 = value2.X;
|
||||
Fix64 y2 = value2.Y;
|
||||
Fix64 z2 = value2.Z;
|
||||
Fix64 w2 = value2.W;
|
||||
|
||||
result.X = ((x2 * w1) + (x1 * w2)) + ((y2 * z1) - (z2 * y1));
|
||||
result.Y = ((y2 * w1) + (y1 * w2)) + ((z2 * x1) - (x2 * z1));
|
||||
result.Z = ((z2 * w1) + (z1 * w2)) + ((x2 * y1) - (y2 * x1));
|
||||
result.W = (w2 * w1) - (((x2 * x1) + (y2 * y1)) + (z2 * z1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains conjugated version of the specified quaternion.
|
||||
/// </summary>
|
||||
/// <param name="value">The quaternion which values will be used to create the conjugated version.</param>
|
||||
/// <returns>The conjugate version of the specified quaternion.</returns>
|
||||
public static Quaternion Conjugate(Quaternion value)
|
||||
{
|
||||
return new Quaternion(-value.X, -value.Y, -value.Z, value.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains conjugated version of the specified quaternion.
|
||||
/// </summary>
|
||||
/// <param name="value">The quaternion which values will be used to create the conjugated version.</param>
|
||||
/// <param name="result">The conjugated version of the specified quaternion as an output parameter.</param>
|
||||
public static void Conjugate(ref Quaternion value, out Quaternion result)
|
||||
{
|
||||
result.X = -value.X;
|
||||
result.Y = -value.Y;
|
||||
result.Z = -value.Z;
|
||||
result.W = value.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> from the specified axis and angle.
|
||||
/// </summary>
|
||||
/// <param name="axis">The axis of rotation.</param>
|
||||
/// <param name="angle">The angle in radians.</param>
|
||||
/// <returns>The new quaternion builded from axis and angle.</returns>
|
||||
public static Quaternion CreateFromAxisAngle(Vector3 axis, Fix64 angle)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
CreateFromAxisAngle(ref axis, angle, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> from the specified axis and angle.
|
||||
/// </summary>
|
||||
/// <param name="axis">The axis of rotation.</param>
|
||||
/// <param name="angle">The angle in radians.</param>
|
||||
/// <param name="result">The new quaternion builded from axis and angle as an output parameter.</param>
|
||||
public static void CreateFromAxisAngle(
|
||||
ref Vector3 axis,
|
||||
Fix64 angle,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
Fix64 half = angle / new Fix64(2);
|
||||
Fix64 sin = Fix64.Sin(half);
|
||||
Fix64 cos = Fix64.Cos(half);
|
||||
result.X = axis.X * sin;
|
||||
result.Y = axis.Y * sin;
|
||||
result.Z = axis.Z * sin;
|
||||
result.W = cos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> from the specified <see cref="Matrix4x4"/>.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The rotation matrix.</param>
|
||||
/// <returns>A quaternion composed from the rotation part of the matrix.</returns>
|
||||
public static Quaternion CreateFromRotationMatrix(Matrix4x4 matrix)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
CreateFromRotationMatrix(ref matrix, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> from the specified <see cref="Matrix4x4"/>.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The rotation matrix.</param>
|
||||
/// <param name="result">A quaternion composed from the rotation part of the matrix as an output parameter.</param>
|
||||
public static void CreateFromRotationMatrix(ref Matrix4x4 matrix, out Quaternion result)
|
||||
{
|
||||
Fix64 sqrt;
|
||||
Fix64 half;
|
||||
Fix64 scale = matrix.M11 + matrix.M22 + matrix.M33;
|
||||
Fix64 two = new Fix64(2);
|
||||
|
||||
if (scale > Fix64.Zero)
|
||||
{
|
||||
sqrt = Fix64.Sqrt(scale + Fix64.One);
|
||||
result.W = sqrt / two;
|
||||
sqrt = Fix64.One / (sqrt * two);
|
||||
|
||||
result.X = (matrix.M23 - matrix.M32) * sqrt;
|
||||
result.Y = (matrix.M31 - matrix.M13) * sqrt;
|
||||
result.Z = (matrix.M12 - matrix.M21) * sqrt;
|
||||
}
|
||||
else if ((matrix.M11 >= matrix.M22) && (matrix.M11 >= matrix.M33))
|
||||
{
|
||||
sqrt = Fix64.Sqrt(Fix64.One + matrix.M11 - matrix.M22 - matrix.M33);
|
||||
half = Fix64.One / (sqrt * two);
|
||||
|
||||
result.X = sqrt / two;
|
||||
result.Y = (matrix.M12 + matrix.M21) * half;
|
||||
result.Z = (matrix.M13 + matrix.M31) * half;
|
||||
result.W = (matrix.M23 - matrix.M32) * half;
|
||||
}
|
||||
else if (matrix.M22 > matrix.M33)
|
||||
{
|
||||
sqrt = Fix64.Sqrt(Fix64.One + matrix.M22 - matrix.M11 - matrix.M33);
|
||||
half = Fix64.One / (sqrt * two);
|
||||
|
||||
result.X = (matrix.M21 + matrix.M12) * half;
|
||||
result.Y = sqrt / two;
|
||||
result.Z = (matrix.M32 + matrix.M23) * half;
|
||||
result.W = (matrix.M31 - matrix.M13) * half;
|
||||
}
|
||||
else
|
||||
{
|
||||
sqrt = Fix64.Sqrt(Fix64.One + matrix.M33 - matrix.M11 - matrix.M22);
|
||||
half = Fix64.One / (sqrt * two);
|
||||
|
||||
result.X = (matrix.M31 + matrix.M13) * half;
|
||||
result.Y = (matrix.M32 + matrix.M23) * half;
|
||||
result.Z = sqrt / two;
|
||||
result.W = (matrix.M12 - matrix.M21) * half;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> from the specified yaw, pitch and roll angles.
|
||||
/// </summary>
|
||||
/// <param name="yaw">Yaw around the y axis in radians.</param>
|
||||
/// <param name="pitch">Pitch around the x axis in radians.</param>
|
||||
/// <param name="roll">Roll around the z axis in radians.</param>
|
||||
/// <returns>A new quaternion from the concatenated yaw, pitch, and roll angles.</returns>
|
||||
public static Quaternion CreateFromYawPitchRoll(Fix64 yaw, Fix64 pitch, Fix64 roll)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> from the specified yaw, pitch and roll angles.
|
||||
/// </summary>
|
||||
/// <param name="yaw">Yaw around the y axis in radians.</param>
|
||||
/// <param name="pitch">Pitch around the x axis in radians.</param>
|
||||
/// <param name="roll">Roll around the z axis in radians.</param>
|
||||
/// <param name="result">A new quaternion from the concatenated yaw, pitch, and roll angles as an output parameter.</param>
|
||||
public static void CreateFromYawPitchRoll(
|
||||
Fix64 yaw,
|
||||
Fix64 pitch,
|
||||
Fix64 roll,
|
||||
out Quaternion result)
|
||||
{
|
||||
Fix64 two = new Fix64(2);
|
||||
Fix64 halfRoll = roll / two;;
|
||||
Fix64 sinRoll = Fix64.Sin(halfRoll);
|
||||
Fix64 cosRoll = Fix64.Cos(halfRoll);
|
||||
Fix64 halfPitch = pitch / two;
|
||||
Fix64 sinPitch = Fix64.Sin(halfPitch);
|
||||
Fix64 cosPitch = Fix64.Cos(halfPitch);
|
||||
Fix64 halfYaw = yaw / two;
|
||||
Fix64 sinYaw = Fix64.Sin(halfYaw);
|
||||
Fix64 cosYaw = Fix64.Cos(halfYaw);
|
||||
result.X = ((cosYaw * sinPitch) * cosRoll) + ((sinYaw * cosPitch) * sinRoll);
|
||||
result.Y = ((sinYaw * cosPitch) * cosRoll) - ((cosYaw * sinPitch) * sinRoll);
|
||||
result.Z = ((cosYaw * cosPitch) * sinRoll) - ((sinYaw * sinPitch) * cosRoll);
|
||||
result.W = ((cosYaw * cosPitch) * cosRoll) + ((sinYaw * sinPitch) * sinRoll);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides a <see cref="Quaternion"/> by the other <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Divisor <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The result of dividing the quaternions.</returns>
|
||||
public static Quaternion Divide(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Divide(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides a <see cref="Quaternion"/> by the other <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Divisor <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The result of dividing the quaternions as an output parameter.</param>
|
||||
public static void Divide(
|
||||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
Fix64 x = quaternion1.X;
|
||||
Fix64 y = quaternion1.Y;
|
||||
Fix64 z = quaternion1.Z;
|
||||
Fix64 w = quaternion1.W;
|
||||
Fix64 num14 = (
|
||||
(quaternion2.X * quaternion2.X) +
|
||||
(quaternion2.Y * quaternion2.Y) +
|
||||
(quaternion2.Z * quaternion2.Z) +
|
||||
(quaternion2.W * quaternion2.W)
|
||||
);
|
||||
Fix64 num5 = Fix64.One / num14;
|
||||
Fix64 num4 = -quaternion2.X * num5;
|
||||
Fix64 num3 = -quaternion2.Y * num5;
|
||||
Fix64 num2 = -quaternion2.Z * num5;
|
||||
Fix64 num = quaternion2.W * num5;
|
||||
Fix64 num13 = (y * num2) - (z * num3);
|
||||
Fix64 num12 = (z * num4) - (x * num2);
|
||||
Fix64 num11 = (x * num3) - (y * num4);
|
||||
Fix64 num10 = ((x * num4) + (y * num3)) + (z * num2);
|
||||
result.X = ((x * num) + (num4 * w)) + num13;
|
||||
result.Y = ((y * num) + (num3 * w)) + num12;
|
||||
result.Z = ((z * num) + (num2 * w)) + num11;
|
||||
result.W = (w * num) - num10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dot product of two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">The first quaternion.</param>
|
||||
/// <param name="quaternion2">The second quaternion.</param>
|
||||
/// <returns>The dot product of two quaternions.</returns>
|
||||
public static Fix64 Dot(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
return (
|
||||
(quaternion1.X * quaternion2.X) +
|
||||
(quaternion1.Y * quaternion2.Y) +
|
||||
(quaternion1.Z * quaternion2.Z) +
|
||||
(quaternion1.W * quaternion2.W)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dot product of two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">The first quaternion.</param>
|
||||
/// <param name="quaternion2">The second quaternion.</param>
|
||||
/// <param name="result">The dot product of two quaternions as an output parameter.</param>
|
||||
public static void Dot(
|
||||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Fix64 result
|
||||
)
|
||||
{
|
||||
result = (
|
||||
(quaternion1.X * quaternion2.X) +
|
||||
(quaternion1.Y * quaternion2.Y) +
|
||||
(quaternion1.Z * quaternion2.Z) +
|
||||
(quaternion1.W * quaternion2.W)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the inverse quaternion which represents the opposite rotation.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The inverse quaternion.</returns>
|
||||
public static Quaternion Inverse(Quaternion quaternion)
|
||||
{
|
||||
Quaternion inverse;
|
||||
Inverse(ref quaternion, out inverse);
|
||||
return inverse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the inverse quaternion which represents the opposite rotation.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The inverse quaternion as an output parameter.</param>
|
||||
public static void Inverse(ref Quaternion quaternion, out Quaternion result)
|
||||
{
|
||||
Fix64 num2 = (
|
||||
(quaternion.X * quaternion.X) +
|
||||
(quaternion.Y * quaternion.Y) +
|
||||
(quaternion.Z * quaternion.Z) +
|
||||
(quaternion.W * quaternion.W)
|
||||
);
|
||||
Fix64 num = Fix64.One / num2;
|
||||
result.X = -quaternion.X * num;
|
||||
result.Y = -quaternion.Y * num;
|
||||
result.Z = -quaternion.Z * num;
|
||||
result.W = quaternion.W * num;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains subtraction of one <see cref="Quaternion"/> from another.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The result of the quaternion subtraction.</returns>
|
||||
public static Quaternion Subtract(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Subtract(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains subtraction of one <see cref="Quaternion"/> from another.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The result of the quaternion subtraction as an output parameter.</param>
|
||||
public static void Subtract(
|
||||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
result.X = quaternion1.X - quaternion2.X;
|
||||
result.Y = quaternion1.Y - quaternion2.Y;
|
||||
result.Z = quaternion1.Z - quaternion2.Z;
|
||||
result.W = quaternion1.W - quaternion2.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The result of the quaternion multiplication.</returns>
|
||||
public static Quaternion Multiply(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Multiply(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of <see cref="Quaternion"/> and a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="scaleFactor">Scalar value.</param>
|
||||
/// <returns>The result of the quaternion multiplication with a scalar.</returns>
|
||||
public static Quaternion Multiply(Quaternion quaternion1, Fix64 scaleFactor)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Multiply(ref quaternion1, scaleFactor, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The result of the quaternion multiplication as an output parameter.</param>
|
||||
public static void Multiply(
|
||||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
Fix64 x = quaternion1.X;
|
||||
Fix64 y = quaternion1.Y;
|
||||
Fix64 z = quaternion1.Z;
|
||||
Fix64 w = quaternion1.W;
|
||||
Fix64 num4 = quaternion2.X;
|
||||
Fix64 num3 = quaternion2.Y;
|
||||
Fix64 num2 = quaternion2.Z;
|
||||
Fix64 num = quaternion2.W;
|
||||
Fix64 num12 = (y * num2) - (z * num3);
|
||||
Fix64 num11 = (z * num4) - (x * num2);
|
||||
Fix64 num10 = (x * num3) - (y * num4);
|
||||
Fix64 num9 = ((x * num4) + (y * num3)) + (z * num2);
|
||||
result.X = ((x * num) + (num4 * w)) + num12;
|
||||
result.Y = ((y * num) + (num3 * w)) + num11;
|
||||
result.Z = ((z * num) + (num2 * w)) + num10;
|
||||
result.W = (w * num) - num9;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Quaternion"/> that contains a multiplication of <see cref="Quaternion"/> and a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="scaleFactor">Scalar value.</param>
|
||||
/// <param name="result">The result of the quaternion multiplication with a scalar as an output parameter.</param>
|
||||
public static void Multiply(
|
||||
ref Quaternion quaternion1,
|
||||
Fix64 scaleFactor,
|
||||
out Quaternion result
|
||||
)
|
||||
{
|
||||
result.X = quaternion1.X * scaleFactor;
|
||||
result.Y = quaternion1.Y * scaleFactor;
|
||||
result.Z = quaternion1.Z * scaleFactor;
|
||||
result.W = quaternion1.W * scaleFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the sign of the all the quaternion components.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The result of the quaternion negation.</returns>
|
||||
public static Quaternion Negate(Quaternion quaternion)
|
||||
{
|
||||
return new Quaternion(
|
||||
-quaternion.X,
|
||||
-quaternion.Y,
|
||||
-quaternion.Z,
|
||||
-quaternion.W
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the sign of the all the quaternion components.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The result of the quaternion negation as an output parameter.</param>
|
||||
public static void Negate(ref Quaternion quaternion, out Quaternion result)
|
||||
{
|
||||
result.X = -quaternion.X;
|
||||
result.Y = -quaternion.Y;
|
||||
result.Z = -quaternion.Z;
|
||||
result.W = -quaternion.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the quaternion magnitude to unit length.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
|
||||
/// <returns>The unit length quaternion.</returns>
|
||||
public static Quaternion Normalize(Quaternion quaternion)
|
||||
{
|
||||
Quaternion quaternion2;
|
||||
Normalize(ref quaternion, out quaternion2);
|
||||
return quaternion2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the quaternion magnitude to unit length.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/>.</param>
|
||||
/// <param name="result">The unit length quaternion an output parameter.</param>
|
||||
public static void Normalize(ref Quaternion quaternion, out Quaternion result)
|
||||
{
|
||||
Fix64 lengthSquared = (quaternion.X * quaternion.X) + (quaternion.Y * quaternion.Y) +
|
||||
(quaternion.Z * quaternion.Z) + (quaternion.W * quaternion.W);
|
||||
|
||||
if (lengthSquared == Fix64.Zero)
|
||||
{
|
||||
result = Identity;
|
||||
return;
|
||||
}
|
||||
|
||||
Fix64 num = Fix64.One / Fix64.Sqrt(lengthSquared);
|
||||
result.X = quaternion.X * num;
|
||||
result.Y = quaternion.Y * num;
|
||||
result.Z = quaternion.Z * num;
|
||||
result.W = quaternion.W * num;
|
||||
}
|
||||
|
||||
public static Quaternion LookAt(in Vector3 forward, in Vector3 up)
|
||||
{
|
||||
Matrix4x4 orientation = Matrix4x4.Identity;
|
||||
orientation.Forward = forward;
|
||||
orientation.Right = Vector3.Normalize(Vector3.Cross(forward, up));
|
||||
orientation.Up = Vector3.Cross(orientation.Right, forward);
|
||||
|
||||
return Quaternion.CreateFromRotationMatrix(orientation);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Static Operator Overloads
|
||||
|
||||
/// <summary>
|
||||
/// Adds two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/> on the left of the add sign.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/> on the right of the add sign.</param>
|
||||
/// <returns>Sum of the vectors.</returns>
|
||||
public static Quaternion operator +(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Add(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides a <see cref="Quaternion"/> by the other <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/> on the left of the div sign.</param>
|
||||
/// <param name="quaternion2">Divisor <see cref="Quaternion"/> on the right of the div sign.</param>
|
||||
/// <returns>The result of dividing the quaternions.</returns>
|
||||
public static Quaternion operator /(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Divide(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether two <see cref="Quaternion"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1"><see cref="Quaternion"/> instance on the left of the equal sign.</param>
|
||||
/// <param name="quaternion2"><see cref="Quaternion"/> instance on the right of the equal sign.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator ==(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
return quaternion1.Equals(quaternion2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether two <see cref="Quaternion"/> instances are not equal.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1"><see cref="Quaternion"/> instance on the left of the not equal sign.</param>
|
||||
/// <param name="quaternion2"><see cref="Quaternion"/> instance on the right of the not equal sign.</param>
|
||||
/// <returns><c>true</c> if the instances are not equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator !=(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
return !quaternion1.Equals(quaternion2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two quaternions.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Quaternion"/> on the left of the mul sign.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Quaternion"/> on the right of the mul sign.</param>
|
||||
/// <returns>Result of the quaternions multiplication.</returns>
|
||||
public static Quaternion operator *(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Multiply(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies the components of quaternion by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Vector3"/> on the left of the mul sign.</param>
|
||||
/// <param name="scaleFactor">Scalar value on the right of the mul sign.</param>
|
||||
/// <returns>Result of the quaternion multiplication with a scalar.</returns>
|
||||
public static Quaternion operator *(Quaternion quaternion1, Fix64 scaleFactor)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Multiply(ref quaternion1, scaleFactor, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a <see cref="Quaternion"/> from a <see cref="Quaternion"/>.
|
||||
/// </summary>
|
||||
/// <param name="quaternion1">Source <see cref="Vector3"/> on the left of the sub sign.</param>
|
||||
/// <param name="quaternion2">Source <see cref="Vector3"/> on the right of the sub sign.</param>
|
||||
/// <returns>Result of the quaternion subtraction.</returns>
|
||||
public static Quaternion operator -(Quaternion quaternion1, Quaternion quaternion2)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Subtract(ref quaternion1, ref quaternion2, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the sign of the all the quaternion components.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">Source <see cref="Quaternion"/> on the right of the sub sign.</param>
|
||||
/// <returns>The result of the quaternion negation.</returns>
|
||||
public static Quaternion operator -(Quaternion quaternion)
|
||||
{
|
||||
Quaternion quaternion2;
|
||||
Negate(ref quaternion, out quaternion2);
|
||||
return quaternion2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,835 @@
|
|||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2022 Evan Hemsley
|
||||
*/
|
||||
|
||||
/* Derived from code by Ethan Lee (Copyright 2009-2021).
|
||||
* Released under the Microsoft Public License.
|
||||
* See fna.LICENSE for details.
|
||||
|
||||
* Derived from code by the Mono.Xna Team (Copyright 2006).
|
||||
* Released under the MIT License. See monoxna.LICENSE for details.
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math.Fixed
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a fixed point 2D-vector.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Vector2 : IEquatable<Vector2>
|
||||
{
|
||||
#region Public Static Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Vector2"/> with components 0, 0.
|
||||
/// </summary>
|
||||
public static Vector2 Zero
|
||||
{
|
||||
get
|
||||
{
|
||||
return zeroVector;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Vector2"/> with components 1, 1.
|
||||
/// </summary>
|
||||
public static Vector2 One
|
||||
{
|
||||
get
|
||||
{
|
||||
return unitVector;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Vector2"/> with components 1, 0.
|
||||
/// </summary>
|
||||
public static Vector2 UnitX
|
||||
{
|
||||
get
|
||||
{
|
||||
return unitXVector;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Vector2"/> with components 0, 1.
|
||||
/// </summary>
|
||||
public static Vector2 UnitY
|
||||
{
|
||||
get
|
||||
{
|
||||
return unitYVector;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Properties
|
||||
|
||||
internal string DebugDisplayString
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Concat(
|
||||
X.ToString(), " ",
|
||||
Y.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// The x coordinate of this <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public Fix64 X;
|
||||
|
||||
/// <summary>
|
||||
/// The y coordinate of this <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
[FieldOffset(8)]
|
||||
public Fix64 Y;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Static Fields
|
||||
|
||||
private static readonly Vector2 zeroVector = new Vector2(0, 0);
|
||||
private static readonly Vector2 unitVector = new Vector2(1, 1);
|
||||
private static readonly Vector2 unitXVector = new Vector2(1, 0);
|
||||
private static readonly Vector2 unitYVector = new Vector2(0, 1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a 2d vector with X and Y from two values.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate in 2d-space.</param>
|
||||
/// <param name="y">The y coordinate in 2d-space.</param>
|
||||
public Vector2(Fix64 x, Fix64 y)
|
||||
{
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a 2d vector with X and Y set to the same value.
|
||||
/// </summary>
|
||||
/// <param name="value">The x and y coordinates in 2d-space.</param>
|
||||
public Vector2(Fix64 value)
|
||||
{
|
||||
this.X = value;
|
||||
this.Y = value;
|
||||
}
|
||||
|
||||
public Vector2(int x, int y)
|
||||
{
|
||||
this.X = new Fix64(x);
|
||||
this.Y = new Fix64(y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether current instance is equal to specified <see cref="Object"/>.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="Object"/> to compare.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Vector2 fixVector && Equals(fixVector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether current instance is equal to specified <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="Vector2"/> to compare.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Vector2 other)
|
||||
{
|
||||
return (X == other.X &&
|
||||
Y == other.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash code of this <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <returns>Hash code of this <see cref="Vector2"/>.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return X.GetHashCode() + Y.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the length of this <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <returns>The length of this <see cref="Vector2"/>.</returns>
|
||||
public Fix64 Length()
|
||||
{
|
||||
return Fix64.Sqrt((X * X) + (Y * Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the squared length of this <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <returns>The squared length of this <see cref="Vector2"/>.</returns>
|
||||
public Fix64 LengthSquared()
|
||||
{
|
||||
return (X * X) + (Y * Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
||||
/// </summary>
|
||||
public Fix64 Angle()
|
||||
{
|
||||
return Fix64.Atan2(Y, X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns this Vector2 with the fractional components cut off.
|
||||
/// </summary>
|
||||
public Vector2 Truncated()
|
||||
{
|
||||
return new Vector2((int) X, (int) Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains linear interpolation of the specified vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector.</param>
|
||||
/// <param name="value2">The second vector.</param>
|
||||
/// <param name="amount">Weighting value(between 0.0 and 1.0).</param>
|
||||
/// <returns>The result of linear interpolation of the specified vectors.</returns>
|
||||
public static Vector2 Lerp(Vector2 value1, Vector2 value2, Fix64 amount)
|
||||
{
|
||||
return new Vector2(
|
||||
Fix64.Lerp(value1.X, value2.X, amount),
|
||||
Fix64.Lerp(value1.Y, value2.Y, amount)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="String"/> representation of this <see cref="Vector2"/> in the format:
|
||||
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>]}
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="String"/> representation of this <see cref="Vector2"/>.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return (
|
||||
"{X:" + X.ToString() +
|
||||
" Y:" + Y.ToString() +
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Static Methods
|
||||
|
||||
/// <summary>
|
||||
/// Performs vector addition on <paramref name="value1"/> and <paramref name="value2"/>.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector to add.</param>
|
||||
/// <param name="value2">The second vector to add.</param>
|
||||
/// <returns>The result of the vector addition.</returns>
|
||||
public static Vector2 Add(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X += value2.X;
|
||||
value1.Y += value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the specified value within a range.
|
||||
/// </summary>
|
||||
/// <param name="value1">The value to clamp.</param>
|
||||
/// <param name="min">The min value.</param>
|
||||
/// <param name="max">The max value.</param>
|
||||
/// <returns>The clamped value.</returns>
|
||||
public static Vector2 Clamp(Vector2 value1, Vector2 min, Vector2 max)
|
||||
{
|
||||
return new Vector2(
|
||||
Fix64.Clamp(value1.X, min.X, max.X),
|
||||
Fix64.Clamp(value1.Y, min.Y, max.Y)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the distance between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector.</param>
|
||||
/// <param name="value2">The second vector.</param>
|
||||
/// <returns>The distance between two vectors.</returns>
|
||||
public static Fix64 Distance(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
Fix64 v1 = value1.X - value2.X, v2 = value1.Y - value2.Y;
|
||||
return Fix64.Sqrt((v1 * v1) + (v2 * v2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the squared distance between two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector.</param>
|
||||
/// <param name="value2">The second vector.</param>
|
||||
/// <returns>The squared distance between two vectors.</returns>
|
||||
public static Fix64 DistanceSquared(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
Fix64 v1 = value1.X - value2.X, v2 = value1.Y - value2.Y;
|
||||
return (v1 * v1) + (v2 * v2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides the components of a <see cref="Vector2"/> by the components of another <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="value2">Divisor <see cref="Vector2"/>.</param>
|
||||
/// <returns>The result of dividing the vectors.</returns>
|
||||
public static Vector2 Divide(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X /= value2.X;
|
||||
value1.Y /= value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides the components of a <see cref="Vector2"/> by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="divider">Divisor scalar.</param>
|
||||
/// <returns>The result of dividing a vector by a scalar.</returns>
|
||||
public static Vector2 Divide(Vector2 value1, Fix64 divider)
|
||||
{
|
||||
Fix64 factor = Fix64.One / divider;
|
||||
value1.X *= factor;
|
||||
value1.Y *= factor;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dot product of two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector.</param>
|
||||
/// <param name="value2">The second vector.</param>
|
||||
/// <returns>The dot product of two vectors.</returns>
|
||||
public static Fix64 Dot(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
return (value1.X * value2.X) + (value1.Y * value2.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a maximal values from the two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector.</param>
|
||||
/// <param name="value2">The second vector.</param>
|
||||
/// <returns>The <see cref="Vector2"/> with maximal values from the two vectors.</returns>
|
||||
public static Vector2 Max(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
return new Vector2(
|
||||
value1.X > value2.X ? value1.X : value2.X,
|
||||
value1.Y > value2.Y ? value1.Y : value2.Y
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a minimal values from the two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first vector.</param>
|
||||
/// <param name="value2">The second vector.</param>
|
||||
/// <returns>The <see cref="Vector2"/> with minimal values from the two vectors.</returns>
|
||||
public static Vector2 Min(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
return new Vector2(
|
||||
value1.X < value2.X ? value1.X : value2.X,
|
||||
value1.Y < value2.Y ? value1.Y : value2.Y
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a multiplication of two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="value2">Source <see cref="Vector2"/>.</param>
|
||||
/// <returns>The result of the vector multiplication.</returns>
|
||||
public static Vector2 Multiply(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X *= value2.X;
|
||||
value1.Y *= value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a multiplication of <see cref="Vector2"/> and a scalar.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="scaleFactor">Scalar value.</param>
|
||||
/// <returns>The result of the vector multiplication with a scalar.</returns>
|
||||
public static Vector2 Multiply(Vector2 value1, Fix64 scaleFactor)
|
||||
{
|
||||
value1.X *= scaleFactor;
|
||||
value1.Y *= scaleFactor;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains the specified vector inversion.
|
||||
/// direction of <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector2"/>.</param>
|
||||
/// <returns>The result of the vector inversion.</returns>
|
||||
public static Vector2 Negate(Vector2 value)
|
||||
{
|
||||
value.X = -value.X;
|
||||
value.Y = -value.Y;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a normalized values from another vector.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector2"/>.</param>
|
||||
/// <returns>Unit vector.</returns>
|
||||
public static Vector2 Normalize(Vector2 value)
|
||||
{
|
||||
Fix64 lengthSquared = (value.X * value.X) + (value.Y * value.Y);
|
||||
|
||||
if (lengthSquared == Fix64.Zero)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
|
||||
Fix64 val = Fix64.One / Fix64.Sqrt(lengthSquared);
|
||||
value.X *= val;
|
||||
value.Y *= val;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains reflect vector of the given vector and normal.
|
||||
/// </summary>
|
||||
/// <param name="vector">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="normal">Reflection normal.</param>
|
||||
/// <returns>Reflected vector.</returns>
|
||||
public static Vector2 Reflect(Vector2 vector, Vector2 normal)
|
||||
{
|
||||
Vector2 result;
|
||||
Fix64 val = new Fix64(2) * ((vector.X * normal.X) + (vector.Y * normal.Y));
|
||||
result.X = vector.X - (normal.X * val);
|
||||
result.Y = vector.Y - (normal.Y * val);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains subtraction of on <see cref="Vector2"/> from a another.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="value2">Source <see cref="Vector2"/>.</param>
|
||||
/// <returns>The result of the vector subtraction.</returns>
|
||||
public static Vector2 Subtract(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X -= value2.X;
|
||||
value1.Y -= value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Matrix4x4"/>.
|
||||
/// </summary>
|
||||
/// <param name="position">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
|
||||
/// <returns>Transformed <see cref="Vector2"/>.</returns>
|
||||
public static Vector2 Transform(Vector2 position, Matrix4x4 matrix)
|
||||
{
|
||||
return new Vector2(
|
||||
(position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M41,
|
||||
(position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M42
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Quaternion"/>, representing the rotation.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
|
||||
/// <returns>Transformed <see cref="Vector2"/>.</returns>
|
||||
public static Vector2 Transform(Vector2 value, Quaternion rotation)
|
||||
{
|
||||
Transform(ref value, ref rotation, out value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Quaternion"/>, representing the rotation.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
|
||||
/// <param name="result">Transformed <see cref="Vector2"/> as an output parameter.</param>
|
||||
public static void Transform(
|
||||
ref Vector2 value,
|
||||
ref Quaternion rotation,
|
||||
out Vector2 result
|
||||
)
|
||||
{
|
||||
Fix64 two = new Fix64(2);
|
||||
Fix64 x = two * -(rotation.Z * value.Y);
|
||||
Fix64 y = two * (rotation.Z * value.X);
|
||||
Fix64 z = two * (rotation.X * value.Y - rotation.Y * value.X);
|
||||
|
||||
result.X = value.X + x * rotation.W + (rotation.Y * z - rotation.Z * y);
|
||||
result.Y = value.Y + y * rotation.W + (rotation.Z * x - rotation.X * z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Math.Matrix3x2"/>.
|
||||
/// </summary>
|
||||
/// <param name="position">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Math.Matrix3x2"/>.</param>
|
||||
/// <returns>Transformed <see cref="Vector2"/>.</returns>
|
||||
public static Vector2 Transform(Vector2 position, Matrix3x2 matrix)
|
||||
{
|
||||
return new Vector2(
|
||||
(position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M31,
|
||||
(position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M32
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on all vectors within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Source array.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
|
||||
/// <param name="destinationArray">Destination array.</param>
|
||||
public static void Transform(
|
||||
Vector2[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector2[] destinationArray
|
||||
)
|
||||
{
|
||||
Transform(sourceArray, 0, ref matrix, destinationArray, 0, sourceArray.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on vectors within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Source array.</param>
|
||||
/// <param name="sourceIndex">The starting index of transformation in the source array.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
|
||||
/// <param name="destinationArray">Destination array.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination array, where the first <see cref="Vector2"/> should be written.</param>
|
||||
/// <param name="length">The number of vectors to be transformed.</param>
|
||||
public static void Transform(
|
||||
Vector2[] sourceArray,
|
||||
int sourceIndex,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector2[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
)
|
||||
{
|
||||
for (int x = 0; x < length; x += 1)
|
||||
{
|
||||
Vector2 position = sourceArray[sourceIndex + x];
|
||||
Vector2 destination = destinationArray[destinationIndex + x];
|
||||
destination.X = (position.X * matrix.M11) + (position.Y * matrix.M21)
|
||||
+ matrix.M41;
|
||||
destination.Y = (position.X * matrix.M12) + (position.Y * matrix.M22)
|
||||
+ matrix.M42;
|
||||
destinationArray[destinationIndex + x] = destination;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on all vectors within array of <see cref="Vector2"/> by the specified <see cref="Quaternion"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Source array.</param>
|
||||
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
|
||||
/// <param name="destinationArray">Destination array.</param>
|
||||
public static void Transform(
|
||||
Vector2[] sourceArray,
|
||||
ref Quaternion rotation,
|
||||
Vector2[] destinationArray
|
||||
)
|
||||
{
|
||||
Transform(
|
||||
sourceArray,
|
||||
0,
|
||||
ref rotation,
|
||||
destinationArray,
|
||||
0,
|
||||
sourceArray.Length
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on vectors within array of <see cref="Vector2"/> by the specified <see cref="Quaternion"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Source array.</param>
|
||||
/// <param name="sourceIndex">The starting index of transformation in the source array.</param>
|
||||
/// <param name="rotation">The <see cref="Quaternion"/> which contains rotation transformation.</param>
|
||||
/// <param name="destinationArray">Destination array.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination array, where the first <see cref="Vector2"/> should be written.</param>
|
||||
/// <param name="length">The number of vectors to be transformed.</param>
|
||||
public static void Transform(
|
||||
Vector2[] sourceArray,
|
||||
int sourceIndex,
|
||||
ref Quaternion rotation,
|
||||
Vector2[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < length; i += 1)
|
||||
{
|
||||
Vector2 position = sourceArray[sourceIndex + i];
|
||||
Vector2 v;
|
||||
Transform(ref position, ref rotation, out v);
|
||||
destinationArray[destinationIndex + i] = v;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix4x4"/>.
|
||||
/// </summary>
|
||||
/// <param name="normal">Source <see cref="Vector2"/> which represents a normal vector.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
|
||||
/// <returns>Transformed normal.</returns>
|
||||
public static Vector2 TransformNormal(Vector2 normal, Matrix4x4 matrix)
|
||||
{
|
||||
return new Vector2(
|
||||
(normal.X * matrix.M11) + (normal.Y * matrix.M21),
|
||||
(normal.X * matrix.M12) + (normal.Y * matrix.M22)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Math.Matrix3x2"/>.
|
||||
/// </summary>
|
||||
/// <param name="normal">Source <see cref="Vector2"/> which represents a normal vector.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Math.Matrix3x2"/>.</param>
|
||||
/// <returns>Transformed normal.</returns>
|
||||
public static Vector2 TransformNormal(Vector2 normal, Matrix3x2 matrix)
|
||||
{
|
||||
return new Vector2(
|
||||
normal.X * matrix.M11 + normal.Y * matrix.M21,
|
||||
normal.X * matrix.M12 + normal.Y * matrix.M22);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on all normals within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Source array.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
|
||||
/// <param name="destinationArray">Destination array.</param>
|
||||
public static void TransformNormal(
|
||||
Vector2[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector2[] destinationArray
|
||||
)
|
||||
{
|
||||
TransformNormal(
|
||||
sourceArray,
|
||||
0,
|
||||
ref matrix,
|
||||
destinationArray,
|
||||
0,
|
||||
sourceArray.Length
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on normals within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Source array.</param>
|
||||
/// <param name="sourceIndex">The starting index of transformation in the source array.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix4x4"/>.</param>
|
||||
/// <param name="destinationArray">Destination array.</param>
|
||||
/// <param name="destinationIndex">The starting index in the destination array, where the first <see cref="Vector2"/> should be written.</param>
|
||||
/// <param name="length">The number of normals to be transformed.</param>
|
||||
public static void TransformNormal(
|
||||
Vector2[] sourceArray,
|
||||
int sourceIndex,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector2[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < length; i += 1)
|
||||
{
|
||||
Vector2 position = sourceArray[sourceIndex + i];
|
||||
Vector2 result;
|
||||
result.X = (position.X * matrix.M11) + (position.Y * matrix.M21);
|
||||
result.Y = (position.X * matrix.M12) + (position.Y * matrix.M22);
|
||||
destinationArray[destinationIndex + i] = result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a Vector2 by an angle.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to rotate.</param>
|
||||
/// <param name="angle">The angle in radians.</param>
|
||||
public static Vector2 Rotate(Vector2 vector, Fix64 angle)
|
||||
{
|
||||
return new Vector2(
|
||||
vector.X * Fix64.Cos(angle) - vector.Y * Fix64.Sin(angle),
|
||||
vector.X * Fix64.Sin(angle) + vector.Y * Fix64.Cos(angle)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Static Operators
|
||||
|
||||
/// <summary>
|
||||
/// Inverts values in the specified <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector2"/> on the right of the sub sign.</param>
|
||||
/// <returns>Result of the inversion.</returns>
|
||||
public static Vector2 operator -(Vector2 value)
|
||||
{
|
||||
value.X = -value.X;
|
||||
value.Y = -value.Y;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether two <see cref="Vector2"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="value1"><see cref="Vector2"/> instance on the left of the equal sign.</param>
|
||||
/// <param name="value2"><see cref="Vector2"/> instance on the right of the equal sign.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator ==(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
return (value1.X == value2.X &&
|
||||
value1.Y == value2.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether two <see cref="Vector2"/> instances are equal.
|
||||
/// </summary>
|
||||
/// <param name="value1"><see cref="Vector2"/> instance on the left of the equal sign.</param>
|
||||
/// <param name="value2"><see cref="Vector2"/> instance on the right of the equal sign.</param>
|
||||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator !=(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
return !(value1 == value2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two vectors.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/> on the left of the add sign.</param>
|
||||
/// <param name="value2">Source <see cref="Vector2"/> on the right of the add sign.</param>
|
||||
/// <returns>Sum of the vectors.</returns>
|
||||
public static Vector2 operator +(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X += value2.X;
|
||||
value1.Y += value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a <see cref="Vector2"/> from a <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/> on the left of the sub sign.</param>
|
||||
/// <param name="value2">Source <see cref="Vector2"/> on the right of the sub sign.</param>
|
||||
/// <returns>Result of the vector subtraction.</returns>
|
||||
public static Vector2 operator -(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X -= value2.X;
|
||||
value1.Y -= value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies the components of two vectors by each other.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/> on the left of the mul sign.</param>
|
||||
/// <param name="value2">Source <see cref="Vector2"/> on the right of the mul sign.</param>
|
||||
/// <returns>Result of the vector multiplication.</returns>
|
||||
public static Vector2 operator *(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X *= value2.X;
|
||||
value1.Y *= value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies the components of vector by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector2"/> on the left of the mul sign.</param>
|
||||
/// <param name="scaleFactor">Scalar value on the right of the mul sign.</param>
|
||||
/// <returns>Result of the vector multiplication with a scalar.</returns>
|
||||
public static Vector2 operator *(Vector2 value, Fix64 scaleFactor)
|
||||
{
|
||||
value.X *= scaleFactor;
|
||||
value.Y *= scaleFactor;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies the components of vector by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="scaleFactor">Scalar value on the left of the mul sign.</param>
|
||||
/// <param name="value">Source <see cref="Vector2"/> on the right of the mul sign.</param>
|
||||
/// <returns>Result of the vector multiplication with a scalar.</returns>
|
||||
public static Vector2 operator *(Fix64 scaleFactor, Vector2 value)
|
||||
{
|
||||
value.X *= scaleFactor;
|
||||
value.Y *= scaleFactor;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides the components of a <see cref="Vector2"/> by the components of another <see cref="Vector2"/>.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/> on the left of the div sign.</param>
|
||||
/// <param name="value2">Divisor <see cref="Vector2"/> on the right of the div sign.</param>
|
||||
/// <returns>The result of dividing the vectors.</returns>
|
||||
public static Vector2 operator /(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
value1.X /= value2.X;
|
||||
value1.Y /= value2.Y;
|
||||
return value1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Divides the components of a <see cref="Vector2"/> by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="value1">Source <see cref="Vector2"/> on the left of the div sign.</param>
|
||||
/// <param name="divider">Divisor scalar on the right of the div sign.</param>
|
||||
/// <returns>The result of dividing a vector by a scalar.</returns>
|
||||
public static Vector2 operator /(Vector2 value1, Fix64 divider)
|
||||
{
|
||||
Fix64 factor = Fix64.One / divider;
|
||||
value1.X *= factor;
|
||||
value1.Y *= factor;
|
||||
return value1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -21,7 +21,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
||||
|
@ -91,23 +91,23 @@ namespace MoonWorks.Math
|
|||
public ContainmentType Contains(BoundingBox box)
|
||||
{
|
||||
// Test if all corner is in the same side of a face by just checking min and max
|
||||
if ( box.Max.X < Min.X ||
|
||||
if (box.Max.X < Min.X ||
|
||||
box.Min.X > Max.X ||
|
||||
box.Max.Y < Min.Y ||
|
||||
box.Min.Y > Max.Y ||
|
||||
box.Max.Z < Min.Z ||
|
||||
box.Min.Z > Max.Z )
|
||||
box.Min.Z > Max.Z)
|
||||
{
|
||||
return ContainmentType.Disjoint;
|
||||
}
|
||||
|
||||
|
||||
if ( box.Min.X >= Min.X &&
|
||||
if (box.Min.X >= Min.X &&
|
||||
box.Max.X <= Max.X &&
|
||||
box.Min.Y >= Min.Y &&
|
||||
box.Max.Y <= Max.Y &&
|
||||
box.Min.Z >= Min.Z &&
|
||||
box.Max.Z <= Max.Z )
|
||||
box.Max.Z <= Max.Z)
|
||||
{
|
||||
return ContainmentType.Contains;
|
||||
}
|
||||
|
@ -172,12 +172,12 @@ namespace MoonWorks.Math
|
|||
|
||||
public ContainmentType Contains(BoundingSphere sphere)
|
||||
{
|
||||
if ( sphere.Center.X - Min.X >= sphere.Radius &&
|
||||
if (sphere.Center.X - Min.X >= sphere.Radius &&
|
||||
sphere.Center.Y - Min.Y >= sphere.Radius &&
|
||||
sphere.Center.Z - Min.Z >= sphere.Radius &&
|
||||
Max.X - sphere.Center.X >= sphere.Radius &&
|
||||
Max.Y - sphere.Center.Y >= sphere.Radius &&
|
||||
Max.Z - sphere.Center.Z >= sphere.Radius )
|
||||
Max.Z - sphere.Center.Z >= sphere.Radius)
|
||||
{
|
||||
return ContainmentType.Contains;
|
||||
}
|
||||
|
@ -261,12 +261,12 @@ namespace MoonWorks.Math
|
|||
public void Contains(ref Vector3 point, out ContainmentType result)
|
||||
{
|
||||
// Determine if point is outside of this box.
|
||||
if ( point.X < this.Min.X ||
|
||||
if (point.X < this.Min.X ||
|
||||
point.X > this.Max.X ||
|
||||
point.Y < this.Min.Y ||
|
||||
point.Y > this.Max.Y ||
|
||||
point.Z < this.Min.Z ||
|
||||
point.Z > this.Max.Z )
|
||||
point.Z > this.Max.Z)
|
||||
{
|
||||
result = ContainmentType.Disjoint;
|
||||
}
|
||||
|
@ -380,12 +380,12 @@ namespace MoonWorks.Math
|
|||
|
||||
public bool Intersects(BoundingSphere sphere)
|
||||
{
|
||||
if ( sphere.Center.X - Min.X > sphere.Radius &&
|
||||
if (sphere.Center.X - Min.X > sphere.Radius &&
|
||||
sphere.Center.Y - Min.Y > sphere.Radius &&
|
||||
sphere.Center.Z - Min.Z > sphere.Radius &&
|
||||
Max.X - sphere.Center.X > sphere.Radius &&
|
||||
Max.Y - sphere.Center.Y > sphere.Radius &&
|
||||
Max.Z - sphere.Center.Z > sphere.Radius )
|
||||
Max.Z - sphere.Center.Z > sphere.Radius)
|
||||
{
|
||||
return true;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
using System.Text;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a viewing frustum for intersection operations.
|
||||
|
@ -230,12 +230,12 @@ namespace MoonWorks.Math
|
|||
box.Intersects(ref this.planes[i], out planeIntersectionType);
|
||||
switch (planeIntersectionType)
|
||||
{
|
||||
case PlaneIntersectionType.Front:
|
||||
result = ContainmentType.Disjoint;
|
||||
return;
|
||||
case PlaneIntersectionType.Intersecting:
|
||||
intersects = true;
|
||||
break;
|
||||
case PlaneIntersectionType.Front:
|
||||
result = ContainmentType.Disjoint;
|
||||
return;
|
||||
case PlaneIntersectionType.Intersecting:
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = intersects ? ContainmentType.Intersects : ContainmentType.Contains;
|
||||
|
@ -269,12 +269,12 @@ namespace MoonWorks.Math
|
|||
sphere.Intersects(ref this.planes[i], out planeIntersectionType);
|
||||
switch (planeIntersectionType)
|
||||
{
|
||||
case PlaneIntersectionType.Front:
|
||||
result = ContainmentType.Disjoint;
|
||||
return;
|
||||
case PlaneIntersectionType.Intersecting:
|
||||
intersects = true;
|
||||
break;
|
||||
case PlaneIntersectionType.Front:
|
||||
result = ContainmentType.Disjoint;
|
||||
return;
|
||||
case PlaneIntersectionType.Intersecting:
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = intersects ? ContainmentType.Intersects : ContainmentType.Contains;
|
||||
|
@ -597,7 +597,8 @@ namespace MoonWorks.Math
|
|||
ref Plane b,
|
||||
ref Plane c,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
/* Formula used
|
||||
* d1 ( N2 * N3 ) + d2 ( N3 * N1 ) + d3 ( N1 * N2 )
|
||||
* P = -------------------------------------------------------------------
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -21,7 +21,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a sphere in 3D-space for bounding operations.
|
||||
|
@ -297,8 +297,8 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(BoundingSphere other)
|
||||
{
|
||||
return ( Center == other.Center &&
|
||||
Radius == other.Radius );
|
||||
return (Center == other.Center &&
|
||||
Radius == other.Radius);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -474,7 +474,8 @@ namespace MoonWorks.Math
|
|||
ref BoundingSphere original,
|
||||
ref BoundingSphere additional,
|
||||
out BoundingSphere result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector3 ocenterToaCenter = Vector3.Subtract(additional.Center, original.Center);
|
||||
float distance = ocenterToaCenter.Length();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -14,7 +14,7 @@
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines how the bounding volumes intersects or contain one another.
|
|
@ -0,0 +1,826 @@
|
|||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
*/
|
||||
|
||||
/* Derived from code by Microsoft.
|
||||
* Released under the MIT license.
|
||||
* See microsoft.LICENSE for details.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure encapsulating a 3x2 matrix.
|
||||
/// </summary>
|
||||
public struct Matrix3x2 : IEquatable<Matrix3x2>
|
||||
{
|
||||
#region Public Fields
|
||||
/// <summary>
|
||||
/// The first element of the first row
|
||||
/// </summary>
|
||||
public float M11;
|
||||
/// <summary>
|
||||
/// The second element of the first row
|
||||
/// </summary>
|
||||
public float M12;
|
||||
/// <summary>
|
||||
/// The first element of the second row
|
||||
/// </summary>
|
||||
public float M21;
|
||||
/// <summary>
|
||||
/// The second element of the second row
|
||||
/// </summary>
|
||||
public float M22;
|
||||
/// <summary>
|
||||
/// The first element of the third row
|
||||
/// </summary>
|
||||
public float M31;
|
||||
/// <summary>
|
||||
/// The second element of the third row
|
||||
/// </summary>
|
||||
public float M32;
|
||||
#endregion Public Fields
|
||||
|
||||
private static readonly Matrix3x2 _identity = new Matrix3x2
|
||||
(
|
||||
1f, 0f,
|
||||
0f, 1f,
|
||||
0f, 0f
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the multiplicative identity matrix.
|
||||
/// </summary>
|
||||
public static Matrix3x2 Identity
|
||||
{
|
||||
get { return _identity; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the matrix is the identity matrix.
|
||||
/// </summary>
|
||||
public bool IsIdentity
|
||||
{
|
||||
get
|
||||
{
|
||||
return M11 == 1f && M22 == 1f && // Check diagonal element first for early out.
|
||||
M12 == 0f &&
|
||||
M21 == 0f &&
|
||||
M31 == 0f && M32 == 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the translation component of this matrix.
|
||||
/// </summary>
|
||||
public Vector2 Translation
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2(M31, M32);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
M31 = value.X;
|
||||
M32 = value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a Matrix3x2 from the given components.
|
||||
/// </summary>
|
||||
public Matrix3x2(float m11, float m12,
|
||||
float m21, float m22,
|
||||
float m31, float m32)
|
||||
{
|
||||
this.M11 = m11;
|
||||
this.M12 = m12;
|
||||
this.M21 = m21;
|
||||
this.M22 = m22;
|
||||
this.M31 = m31;
|
||||
this.M32 = m32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation matrix from the given vector.
|
||||
/// </summary>
|
||||
/// <param name="position">The translation position.</param>
|
||||
/// <returns>A translation matrix.</returns>
|
||||
public static Matrix3x2 CreateTranslation(Vector2 position)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = 1.0f;
|
||||
|
||||
result.M31 = position.X;
|
||||
result.M32 = position.Y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation matrix from the given X and Y components.
|
||||
/// </summary>
|
||||
/// <param name="xPosition">The X position.</param>
|
||||
/// <param name="yPosition">The Y position.</param>
|
||||
/// <returns>A translation matrix.</returns>
|
||||
public static Matrix3x2 CreateTranslation(float xPosition, float yPosition)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = 1.0f;
|
||||
|
||||
result.M31 = xPosition;
|
||||
result.M32 = yPosition;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given X and Y components.
|
||||
/// </summary>
|
||||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
||||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float xScale, float yScale)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = xScale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = yScale;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that is offset by a given center point.
|
||||
/// </summary>
|
||||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
||||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float xScale, float yScale, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float tx = centerPoint.X * (1 - xScale);
|
||||
float ty = centerPoint.Y * (1 - yScale);
|
||||
|
||||
result.M11 = xScale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = yScale;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given vector scale.
|
||||
/// </summary>
|
||||
/// <param name="scales">The scale to use.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Vector2 scales)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = scales.X;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scales.Y;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
|
||||
/// </summary>
|
||||
/// <param name="scales">The scale to use.</param>
|
||||
/// <param name="centerPoint">The center offset.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Vector2 scales, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float tx = centerPoint.X * (1 - scales.X);
|
||||
float ty = centerPoint.Y * (1 - scales.Y);
|
||||
|
||||
result.M11 = scales.X;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scales.Y;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that scales uniformly with the given scale.
|
||||
/// </summary>
|
||||
/// <param name="scale">The uniform scale to use.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float scale)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = scale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scale;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
|
||||
/// </summary>
|
||||
/// <param name="scale">The uniform scale to use.</param>
|
||||
/// <param name="centerPoint">The center offset.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float scale, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float tx = centerPoint.X * (1 - scale);
|
||||
float ty = centerPoint.Y * (1 - scale);
|
||||
|
||||
result.M11 = scale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scale;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix from the given angles in radians.
|
||||
/// </summary>
|
||||
/// <param name="radiansX">The X angle, in radians.</param>
|
||||
/// <param name="radiansY">The Y angle, in radians.</param>
|
||||
/// <returns>A skew matrix.</returns>
|
||||
public static Matrix3x2 CreateSkew(float radiansX, float radiansY)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float xTan = (float) System.Math.Tan(radiansX);
|
||||
float yTan = (float) System.Math.Tan(radiansY);
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = yTan;
|
||||
result.M21 = xTan;
|
||||
result.M22 = 1.0f;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix from the given angles in radians and a center point.
|
||||
/// </summary>
|
||||
/// <param name="radiansX">The X angle, in radians.</param>
|
||||
/// <param name="radiansY">The Y angle, in radians.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A skew matrix.</returns>
|
||||
public static Matrix3x2 CreateSkew(float radiansX, float radiansY, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float xTan = (float) System.Math.Tan(radiansX);
|
||||
float yTan = (float) System.Math.Tan(radiansY);
|
||||
|
||||
float tx = -centerPoint.Y * xTan;
|
||||
float ty = -centerPoint.X * yTan;
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = yTan;
|
||||
result.M21 = xTan;
|
||||
result.M22 = 1.0f;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rotation matrix using the given rotation in radians.
|
||||
/// </summary>
|
||||
/// <param name="radians">The amount of rotation, in radians.</param>
|
||||
/// <returns>A rotation matrix.</returns>
|
||||
public static Matrix3x2 CreateRotation(float radians)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
radians = (float) System.Math.IEEERemainder(radians, System.Math.PI * 2);
|
||||
|
||||
float c, s;
|
||||
|
||||
const float epsilon = 0.001f * (float) System.Math.PI / 180f; // 0.1% of a degree
|
||||
|
||||
if (radians > -epsilon && radians < epsilon)
|
||||
{
|
||||
// Exact case for zero rotation.
|
||||
c = 1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 90 degree rotation.
|
||||
c = 0;
|
||||
s = 1;
|
||||
}
|
||||
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
|
||||
{
|
||||
// Exact case for 180 degree rotation.
|
||||
c = -1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 270 degree rotation.
|
||||
c = 0;
|
||||
s = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arbitrary rotation.
|
||||
c = (float) System.Math.Cos(radians);
|
||||
s = (float) System.Math.Sin(radians);
|
||||
}
|
||||
|
||||
// [ c s ]
|
||||
// [ -s c ]
|
||||
// [ 0 0 ]
|
||||
result.M11 = c;
|
||||
result.M12 = s;
|
||||
result.M21 = -s;
|
||||
result.M22 = c;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rotation matrix using the given rotation in radians and a center point.
|
||||
/// </summary>
|
||||
/// <param name="radians">The amount of rotation, in radians.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A rotation matrix.</returns>
|
||||
public static Matrix3x2 CreateRotation(float radians, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
radians = (float) System.Math.IEEERemainder(radians, System.Math.PI * 2);
|
||||
|
||||
float c, s;
|
||||
|
||||
const float epsilon = 0.001f * (float) System.Math.PI / 180f; // 0.1% of a degree
|
||||
|
||||
if (radians > -epsilon && radians < epsilon)
|
||||
{
|
||||
// Exact case for zero rotation.
|
||||
c = 1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 90 degree rotation.
|
||||
c = 0;
|
||||
s = 1;
|
||||
}
|
||||
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
|
||||
{
|
||||
// Exact case for 180 degree rotation.
|
||||
c = -1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 270 degree rotation.
|
||||
c = 0;
|
||||
s = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arbitrary rotation.
|
||||
c = (float) System.Math.Cos(radians);
|
||||
s = (float) System.Math.Sin(radians);
|
||||
}
|
||||
|
||||
float x = centerPoint.X * (1 - c) + centerPoint.Y * s;
|
||||
float y = centerPoint.Y * (1 - c) - centerPoint.X * s;
|
||||
|
||||
// [ c s ]
|
||||
// [ -s c ]
|
||||
// [ x y ]
|
||||
result.M11 = c;
|
||||
result.M12 = s;
|
||||
result.M21 = -s;
|
||||
result.M22 = c;
|
||||
result.M31 = x;
|
||||
result.M32 = y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the determinant for this matrix.
|
||||
/// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1).
|
||||
/// </summary>
|
||||
/// <returns>The determinant.</returns>
|
||||
public float GetDeterminant()
|
||||
{
|
||||
// There isn't actually any such thing as a determinant for a non-square matrix,
|
||||
// but this 3x2 type is really just an optimization of a 3x3 where we happen to
|
||||
// know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
|
||||
//
|
||||
// [ M11, M12, 0 ]
|
||||
// [ M21, M22, 0 ]
|
||||
// [ M31, M32, 1 ]
|
||||
//
|
||||
// Sum the diagonal products:
|
||||
// (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
|
||||
//
|
||||
// Subtract the opposite diagonal products:
|
||||
// (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
|
||||
//
|
||||
// Collapse out the constants and oh look, this is just a 2x2 determinant!
|
||||
|
||||
return (M11 * M22) - (M21 * M12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to invert the given matrix. If the operation succeeds, the inverted matrix is stored in the result parameter.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The source matrix.</param>
|
||||
/// <param name="result">The output matrix.</param>
|
||||
/// <returns>True if the operation succeeded, False otherwise.</returns>
|
||||
public static bool Invert(Matrix3x2 matrix, out Matrix3x2 result)
|
||||
{
|
||||
float det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12);
|
||||
|
||||
if (System.Math.Abs(det) < float.Epsilon)
|
||||
{
|
||||
result = new Matrix3x2(float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN);
|
||||
return false;
|
||||
}
|
||||
|
||||
float invDet = 1.0f / det;
|
||||
|
||||
result.M11 = matrix.M22 * invDet;
|
||||
result.M12 = -matrix.M12 * invDet;
|
||||
result.M21 = -matrix.M21 * invDet;
|
||||
result.M22 = matrix.M11 * invDet;
|
||||
result.M31 = (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet;
|
||||
result.M32 = (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates from matrix1 to matrix2, based on the third parameter.
|
||||
/// </summary>
|
||||
/// <param name="matrix1">The first source matrix.</param>
|
||||
/// <param name="matrix2">The second source matrix.</param>
|
||||
/// <param name="amount">The relative weighting of matrix2.</param>
|
||||
/// <returns>The interpolated matrix.</returns>
|
||||
public static Matrix3x2 Lerp(Matrix3x2 matrix1, Matrix3x2 matrix2, float amount)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
// First row
|
||||
result.M11 = matrix1.M11 + (matrix2.M11 - matrix1.M11) * amount;
|
||||
result.M12 = matrix1.M12 + (matrix2.M12 - matrix1.M12) * amount;
|
||||
|
||||
// Second row
|
||||
result.M21 = matrix1.M21 + (matrix2.M21 - matrix1.M21) * amount;
|
||||
result.M22 = matrix1.M22 + (matrix2.M22 - matrix1.M22) * amount;
|
||||
|
||||
// Third row
|
||||
result.M31 = matrix1.M31 + (matrix2.M31 - matrix1.M31) * amount;
|
||||
result.M32 = matrix1.M32 + (matrix2.M32 - matrix1.M32) * amount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the given matrix by multiplying all values by -1.
|
||||
/// </summary>
|
||||
/// <param name="value">The source matrix.</param>
|
||||
/// <returns>The negated matrix.</returns>
|
||||
public static Matrix3x2 Negate(Matrix3x2 value)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = -value.M11;
|
||||
result.M12 = -value.M12;
|
||||
result.M21 = -value.M21;
|
||||
result.M22 = -value.M22;
|
||||
result.M31 = -value.M31;
|
||||
result.M32 = -value.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds each matrix element in value1 with its corresponding element in value2.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the summed values.</returns>
|
||||
public static Matrix3x2 Add(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 + value2.M11;
|
||||
result.M12 = value1.M12 + value2.M12;
|
||||
result.M21 = value1.M21 + value2.M21;
|
||||
result.M22 = value1.M22 + value2.M22;
|
||||
result.M31 = value1.M31 + value2.M31;
|
||||
result.M32 = value1.M32 + value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts each matrix element in value2 from its corresponding element in value1.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the resulting values.</returns>
|
||||
public static Matrix3x2 Subtract(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 - value2.M11;
|
||||
result.M12 = value1.M12 - value2.M12;
|
||||
result.M21 = value1.M21 - value2.M21;
|
||||
result.M22 = value1.M22 - value2.M22;
|
||||
result.M31 = value1.M31 - value2.M31;
|
||||
result.M32 = value1.M32 - value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two matrices together and returns the resulting matrix.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The product matrix.</returns>
|
||||
public static Matrix3x2 Multiply(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
// First row
|
||||
result.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
result.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
result.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
result.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
// Third row
|
||||
result.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
|
||||
result.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Matrix4x4 ToMatrix4x4()
|
||||
{
|
||||
return new Matrix4x4(
|
||||
M11, M12, 0, 0,
|
||||
M21, M22, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
M31, M32, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales all elements in a matrix by the given scalar factor.
|
||||
/// </summary>
|
||||
/// <param name="value1">The source matrix.</param>
|
||||
/// <param name="value2">The scaling value to use.</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
public static Matrix3x2 Multiply(Matrix3x2 value1, float value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 * value2;
|
||||
result.M12 = value1.M12 * value2;
|
||||
result.M21 = value1.M21 * value2;
|
||||
result.M22 = value1.M22 * value2;
|
||||
result.M31 = value1.M31 * value2;
|
||||
result.M32 = value1.M32 * value2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the given matrix by multiplying all values by -1.
|
||||
/// </summary>
|
||||
/// <param name="value">The source matrix.</param>
|
||||
/// <returns>The negated matrix.</returns>
|
||||
public static Matrix3x2 operator -(Matrix3x2 value)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = -value.M11;
|
||||
m.M12 = -value.M12;
|
||||
m.M21 = -value.M21;
|
||||
m.M22 = -value.M22;
|
||||
m.M31 = -value.M31;
|
||||
m.M32 = -value.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds each matrix element in value1 with its corresponding element in value2.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the summed values.</returns>
|
||||
public static Matrix3x2 operator +(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 + value2.M11;
|
||||
m.M12 = value1.M12 + value2.M12;
|
||||
m.M21 = value1.M21 + value2.M21;
|
||||
m.M22 = value1.M22 + value2.M22;
|
||||
m.M31 = value1.M31 + value2.M31;
|
||||
m.M32 = value1.M32 + value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts each matrix element in value2 from its corresponding element in value1.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the resulting values.</returns>
|
||||
public static Matrix3x2 operator -(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 - value2.M11;
|
||||
m.M12 = value1.M12 - value2.M12;
|
||||
m.M21 = value1.M21 - value2.M21;
|
||||
m.M22 = value1.M22 - value2.M22;
|
||||
m.M31 = value1.M31 - value2.M31;
|
||||
m.M32 = value1.M32 - value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two matrices together and returns the resulting matrix.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The product matrix.</returns>
|
||||
public static Matrix3x2 operator *(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
// First row
|
||||
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
// Third row
|
||||
m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
|
||||
m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales all elements in a matrix by the given scalar factor.
|
||||
/// </summary>
|
||||
/// <param name="value1">The source matrix.</param>
|
||||
/// <param name="value2">The scaling value to use.</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
public static Matrix3x2 operator *(Matrix3x2 value1, float value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 * value2;
|
||||
m.M12 = value1.M12 * value2;
|
||||
m.M21 = value1.M21 * value2;
|
||||
m.M22 = value1.M22 * value2;
|
||||
m.M31 = value1.M31 * value2;
|
||||
m.M32 = value1.M32 * value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given matrices are equal.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>True if the matrices are equal; False otherwise.</returns>
|
||||
public static bool operator ==(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && // Check diagonal element first for early out.
|
||||
value1.M12 == value2.M12 &&
|
||||
value1.M21 == value2.M21 &&
|
||||
value1.M31 == value2.M31 && value1.M32 == value2.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given matrices are not equal.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>True if the matrices are not equal; False if they are equal.</returns>
|
||||
public static bool operator !=(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
return (value1.M11 != value2.M11 || value1.M12 != value2.M12 ||
|
||||
value1.M21 != value2.M21 || value1.M22 != value2.M22 ||
|
||||
value1.M31 != value2.M31 || value1.M32 != value2.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
|
||||
/// </summary>
|
||||
/// <param name="other">The other matrix to test equality against.</param>
|
||||
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
|
||||
public bool Equals(Matrix3x2 other)
|
||||
{
|
||||
return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out.
|
||||
M12 == other.M12 &&
|
||||
M21 == other.M21 &&
|
||||
M31 == other.M31 && M32 == other.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given Object is equal to this matrix instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The Object to compare against.</param>
|
||||
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Matrix3x2)
|
||||
{
|
||||
return Equals((Matrix3x2) obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a String representing this matrix instance.
|
||||
/// </summary>
|
||||
/// <returns>The string representation.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
CultureInfo ci = CultureInfo.CurrentCulture;
|
||||
return String.Format(ci, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}",
|
||||
M11.ToString(ci), M12.ToString(ci),
|
||||
M21.ToString(ci), M22.ToString(ci),
|
||||
M31.ToString(ci), M32.ToString(ci));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>The hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return M11.GetHashCode() + M12.GetHashCode() +
|
||||
M21.GetHashCode() + M22.GetHashCode() +
|
||||
M31.GetHashCode() + M32.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the right-handed 4x4 floating point matrix, which can store translation, scale and rotation information.
|
||||
|
@ -316,7 +316,8 @@ namespace MoonWorks.Math
|
|||
float m21, float m22, float m23, float m24,
|
||||
float m31, float m32, float m33, float m34,
|
||||
float m41, float m42, float m43, float m44
|
||||
) {
|
||||
)
|
||||
{
|
||||
M11 = m11;
|
||||
M12 = m12;
|
||||
M13 = m13;
|
||||
|
@ -350,7 +351,8 @@ namespace MoonWorks.Math
|
|||
out Vector3 scale,
|
||||
out Quaternion rotation,
|
||||
out Vector3 translation
|
||||
) {
|
||||
)
|
||||
{
|
||||
translation.X = M41;
|
||||
translation.Y = M42;
|
||||
translation.Z = M43;
|
||||
|
@ -363,9 +365,9 @@ namespace MoonWorks.Math
|
|||
scale.Y = ys * (float) System.Math.Sqrt(M21 * M21 + M22 * M22 + M23 * M23);
|
||||
scale.Z = zs * (float) System.Math.Sqrt(M31 * M31 + M32 * M32 + M33 * M33);
|
||||
|
||||
if ( MathHelper.WithinEpsilon(scale.X, 0.0f) ||
|
||||
if (MathHelper.WithinEpsilon(scale.X, 0.0f) ||
|
||||
MathHelper.WithinEpsilon(scale.Y, 0.0f) ||
|
||||
MathHelper.WithinEpsilon(scale.Z, 0.0f) )
|
||||
MathHelper.WithinEpsilon(scale.Z, 0.0f))
|
||||
{
|
||||
rotation = Quaternion.Identity;
|
||||
return false;
|
||||
|
@ -413,7 +415,7 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Matrix4x4 other)
|
||||
{
|
||||
return ( M11 == other.M11 &&
|
||||
return (M11 == other.M11 &&
|
||||
M12 == other.M12 &&
|
||||
M13 == other.M13 &&
|
||||
M14 == other.M14 &&
|
||||
|
@ -428,7 +430,7 @@ namespace MoonWorks.Math
|
|||
M41 == other.M41 &&
|
||||
M42 == other.M42 &&
|
||||
M43 == other.M43 &&
|
||||
M44 == other.M44 );
|
||||
M44 == other.M44);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -555,7 +557,8 @@ namespace MoonWorks.Math
|
|||
Vector3 cameraPosition,
|
||||
Vector3 cameraUpVector,
|
||||
Nullable<Vector3> cameraForwardVector
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
|
||||
// Delegate to the other overload of the function to do the work
|
||||
|
@ -584,7 +587,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 cameraUpVector,
|
||||
Vector3? cameraForwardVector,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector3 vector;
|
||||
Vector3 vector2;
|
||||
Vector3 vector3;
|
||||
|
@ -607,7 +611,7 @@ namespace MoonWorks.Math
|
|||
);
|
||||
}
|
||||
Vector3.Cross(ref cameraUpVector, ref vector, out vector3);
|
||||
vector3.Normalize();
|
||||
vector3 = Vector3.Normalize(vector3);
|
||||
Vector3.Cross(ref vector, ref vector3, out vector2);
|
||||
result.M11 = vector3.X;
|
||||
result.M12 = vector3.Y;
|
||||
|
@ -642,7 +646,8 @@ namespace MoonWorks.Math
|
|||
Vector3 rotateAxis,
|
||||
Nullable<Vector3> cameraForwardVector,
|
||||
Nullable<Vector3> objectForwardVector
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
CreateConstrainedBillboard(
|
||||
ref objectPosition,
|
||||
|
@ -671,7 +676,8 @@ namespace MoonWorks.Math
|
|||
Vector3? cameraForwardVector,
|
||||
Vector3? objectForwardVector,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float num;
|
||||
Vector3 vector;
|
||||
Vector3 vector2;
|
||||
|
@ -724,16 +730,16 @@ namespace MoonWorks.Math
|
|||
Vector3.Forward;
|
||||
}
|
||||
Vector3.Cross(ref rotateAxis, ref vector, out vector3);
|
||||
vector3.Normalize();
|
||||
vector3 = Vector3.Normalize(vector3);
|
||||
Vector3.Cross(ref vector3, ref rotateAxis, out vector);
|
||||
vector.Normalize();
|
||||
vector = Vector3.Normalize(vector);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector3.Cross(ref rotateAxis, ref vector2, out vector3);
|
||||
vector3.Normalize();
|
||||
vector3 = Vector3.Normalize(vector3);
|
||||
Vector3.Cross(ref vector3, ref vector4, out vector);
|
||||
vector.Normalize();
|
||||
vector = Vector3.Normalize(vector);
|
||||
}
|
||||
|
||||
result.M11 = vector3.X;
|
||||
|
@ -777,7 +783,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 axis,
|
||||
float angle,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = axis.X;
|
||||
float y = axis.Y;
|
||||
float z = axis.Z;
|
||||
|
@ -883,7 +890,8 @@ namespace MoonWorks.Math
|
|||
float pitch,
|
||||
float roll,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll, out quaternion);
|
||||
CreateFromQuaternion(ref quaternion, out result);
|
||||
|
@ -900,7 +908,8 @@ namespace MoonWorks.Math
|
|||
Vector3 cameraPosition,
|
||||
Vector3 cameraTarget,
|
||||
Vector3 cameraUpVector
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 matrix;
|
||||
CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector, out matrix);
|
||||
return matrix;
|
||||
|
@ -918,7 +927,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 cameraTarget,
|
||||
ref Vector3 cameraUpVector,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector3 vectorA = Vector3.Normalize(cameraPosition - cameraTarget);
|
||||
Vector3 vectorB = Vector3.Normalize(Vector3.Cross(cameraUpVector, vectorA));
|
||||
Vector3 vectorC = Vector3.Cross(vectorA, vectorB);
|
||||
|
@ -953,7 +963,8 @@ namespace MoonWorks.Math
|
|||
float height,
|
||||
float zNearPlane,
|
||||
float zFarPlane
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 matrix;
|
||||
CreateOrthographic(width, height, zNearPlane, zFarPlane, out matrix);
|
||||
return matrix;
|
||||
|
@ -973,7 +984,8 @@ namespace MoonWorks.Math
|
|||
float zNearPlane,
|
||||
float zFarPlane,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.M11 = 2f / width;
|
||||
result.M12 = result.M13 = result.M14 = 0f;
|
||||
result.M22 = 2f / height;
|
||||
|
@ -1002,7 +1014,8 @@ namespace MoonWorks.Math
|
|||
float top,
|
||||
float zNearPlane,
|
||||
float zFarPlane
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 matrix;
|
||||
CreateOrthographicOffCenter(
|
||||
left,
|
||||
|
@ -1034,7 +1047,8 @@ namespace MoonWorks.Math
|
|||
float zNearPlane,
|
||||
float zFarPlane,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.M11 = (float) (2.0 / ((double) right - (double) left));
|
||||
result.M12 = 0.0f;
|
||||
result.M13 = 0.0f;
|
||||
|
@ -1075,7 +1089,8 @@ namespace MoonWorks.Math
|
|||
float height,
|
||||
float nearPlaneDistance,
|
||||
float farPlaneDistance
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 matrix;
|
||||
CreatePerspective(width, height, nearPlaneDistance, farPlaneDistance, out matrix);
|
||||
return matrix;
|
||||
|
@ -1095,7 +1110,8 @@ namespace MoonWorks.Math
|
|||
float nearPlaneDistance,
|
||||
float farPlaneDistance,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (nearPlaneDistance <= 0f)
|
||||
{
|
||||
throw new ArgumentException("nearPlaneDistance <= 0");
|
||||
|
@ -1135,7 +1151,8 @@ namespace MoonWorks.Math
|
|||
float aspectRatio,
|
||||
float nearPlaneDistance,
|
||||
float farPlaneDistance
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
CreatePerspectiveFieldOfView(
|
||||
fieldOfView,
|
||||
|
@ -1161,7 +1178,8 @@ namespace MoonWorks.Math
|
|||
float nearPlaneDistance,
|
||||
float farPlaneDistance,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
if ((fieldOfView <= 0f) || (fieldOfView >= 3.141593f))
|
||||
{
|
||||
throw new ArgumentException("fieldOfView <= 0 or >= PI");
|
||||
|
@ -1210,7 +1228,8 @@ namespace MoonWorks.Math
|
|||
float top,
|
||||
float nearPlaneDistance,
|
||||
float farPlaneDistance
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
CreatePerspectiveOffCenter(
|
||||
left,
|
||||
|
@ -1242,7 +1261,8 @@ namespace MoonWorks.Math
|
|||
float nearPlaneDistance,
|
||||
float farPlaneDistance,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (nearPlaneDistance <= 0f)
|
||||
{
|
||||
throw new ArgumentException("nearPlaneDistance <= 0");
|
||||
|
@ -1408,7 +1428,8 @@ namespace MoonWorks.Math
|
|||
float yScale,
|
||||
float zScale,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.M11 = xScale;
|
||||
result.M12 = 0;
|
||||
result.M13 = 0;
|
||||
|
@ -1527,7 +1548,8 @@ namespace MoonWorks.Math
|
|||
float xPosition,
|
||||
float yPosition,
|
||||
float zPosition
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 result;
|
||||
CreateTranslation(xPosition, yPosition, zPosition, out result);
|
||||
return result;
|
||||
|
@ -1582,7 +1604,8 @@ namespace MoonWorks.Math
|
|||
float yPosition,
|
||||
float zPosition,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.M11 = 1;
|
||||
result.M12 = 0;
|
||||
result.M13 = 0;
|
||||
|
@ -1672,13 +1695,14 @@ namespace MoonWorks.Math
|
|||
ref Vector3 forward,
|
||||
ref Vector3 up,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector3 x, y, z;
|
||||
Vector3.Normalize(ref forward, out z);
|
||||
Vector3.Cross(ref forward, ref up, out x);
|
||||
Vector3.Cross(ref x, ref forward, out y);
|
||||
x.Normalize();
|
||||
y.Normalize();
|
||||
x = Vector3.Normalize(x);
|
||||
y = Vector3.Normalize(y);
|
||||
|
||||
result = new Matrix4x4();
|
||||
result.Right = x;
|
||||
|
@ -2069,7 +2093,8 @@ namespace MoonWorks.Math
|
|||
ref Matrix4x4 matrix2,
|
||||
float amount,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.M11 = matrix1.M11 + ((matrix2.M11 - matrix1.M11) * amount);
|
||||
result.M12 = matrix1.M12 + ((matrix2.M12 - matrix1.M12) * amount);
|
||||
result.M13 = matrix1.M13 + ((matrix2.M13 - matrix1.M13) * amount);
|
||||
|
@ -2097,7 +2122,8 @@ namespace MoonWorks.Math
|
|||
public static Matrix4x4 Multiply(
|
||||
Matrix4x4 matrix1,
|
||||
Matrix4x4 matrix2
|
||||
) {
|
||||
)
|
||||
{
|
||||
float m11 = (
|
||||
(matrix1.M11 * matrix2.M11) +
|
||||
(matrix1.M12 * matrix2.M21) +
|
||||
|
@ -2548,7 +2574,8 @@ namespace MoonWorks.Math
|
|||
ref Matrix4x4 value,
|
||||
ref Quaternion rotation,
|
||||
out Matrix4x4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Matrix4x4 rotMatrix = CreateFromQuaternion(rotation);
|
||||
Multiply(ref value, ref rotMatrix, out result);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
||||
|
@ -233,7 +233,8 @@ namespace MoonWorks.Math
|
|||
ref Plane plane,
|
||||
ref Matrix4x4 matrix,
|
||||
out Plane result
|
||||
) {
|
||||
)
|
||||
{
|
||||
/* See "Transforming Normals" in
|
||||
* http://www.glprogramming.com/red/appendixf.html
|
||||
* for an explanation of how this works.
|
||||
|
@ -277,7 +278,8 @@ namespace MoonWorks.Math
|
|||
ref Plane plane,
|
||||
ref Quaternion rotation,
|
||||
out Plane result
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector3.Transform(
|
||||
ref plane.Normal,
|
||||
ref rotation,
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -14,7 +14,7 @@
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the intersection between a <see cref="Plane"/> and a bounding volume.
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 2D-point.
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// An efficient mathematical representation for three dimensional rotations.
|
||||
|
@ -157,10 +157,10 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Quaternion other)
|
||||
{
|
||||
return ( X == other.X &&
|
||||
return (X == other.X &&
|
||||
Y == other.Y &&
|
||||
Z == other.Z &&
|
||||
W == other.W );
|
||||
W == other.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -266,7 +266,8 @@ namespace MoonWorks.Math
|
|||
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;
|
||||
|
@ -296,7 +297,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion value1,
|
||||
ref Quaternion value2,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x1 = value1.X;
|
||||
float y1 = value1.Y;
|
||||
float z1 = value1.Z;
|
||||
|
@ -359,7 +361,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 axis,
|
||||
float angle,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float half = angle * 0.5f;
|
||||
float sin = (float) System.Math.Sin((double) half);
|
||||
float cos = (float) System.Math.Cos((double) half);
|
||||
|
@ -415,12 +418,12 @@ namespace MoonWorks.Math
|
|||
else if (matrix.M22 > matrix.M33)
|
||||
{
|
||||
sqrt = (float) System.Math.Sqrt(1.0f + matrix.M22 - matrix.M11 - matrix.M33);
|
||||
half = 0.5f/sqrt;
|
||||
half = 0.5f / sqrt;
|
||||
|
||||
result.X = (matrix.M21 + matrix.M12)*half;
|
||||
result.Y = 0.5f*sqrt;
|
||||
result.Z = (matrix.M32 + matrix.M23)*half;
|
||||
result.W = (matrix.M31 - matrix.M13)*half;
|
||||
result.X = (matrix.M21 + matrix.M12) * half;
|
||||
result.Y = 0.5f * sqrt;
|
||||
result.Z = (matrix.M32 + matrix.M23) * half;
|
||||
result.W = (matrix.M31 - matrix.M13) * half;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -499,7 +502,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = quaternion1.X;
|
||||
float y = quaternion1.Y;
|
||||
float z = quaternion1.Z;
|
||||
|
@ -551,7 +555,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out float result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result = (
|
||||
(quaternion1.X * quaternion2.X) +
|
||||
(quaternion1.Y * quaternion2.Y) +
|
||||
|
@ -603,7 +608,8 @@ namespace MoonWorks.Math
|
|||
Quaternion quaternion1,
|
||||
Quaternion quaternion2,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Lerp(ref quaternion1, ref quaternion2, amount, out quaternion);
|
||||
return quaternion;
|
||||
|
@ -621,7 +627,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion quaternion2,
|
||||
float amount,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float num = amount;
|
||||
float num2 = 1f - num;
|
||||
float num5 = (
|
||||
|
@ -668,7 +675,8 @@ namespace MoonWorks.Math
|
|||
Quaternion quaternion1,
|
||||
Quaternion quaternion2,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
Quaternion quaternion;
|
||||
Slerp(ref quaternion1, ref quaternion2, amount, out quaternion);
|
||||
return quaternion;
|
||||
|
@ -686,7 +694,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion quaternion2,
|
||||
float amount,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float num2;
|
||||
float num3;
|
||||
float num = amount;
|
||||
|
@ -743,7 +752,8 @@ namespace MoonWorks.Math
|
|||
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;
|
||||
|
@ -786,7 +796,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion quaternion1,
|
||||
ref Quaternion quaternion2,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = quaternion1.X;
|
||||
float y = quaternion1.Y;
|
||||
float z = quaternion1.Z;
|
||||
|
@ -815,7 +826,8 @@ namespace MoonWorks.Math
|
|||
ref Quaternion quaternion1,
|
||||
float scaleFactor,
|
||||
out Quaternion result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = quaternion1.X * scaleFactor;
|
||||
result.Y = quaternion1.Y * scaleFactor;
|
||||
result.Z = quaternion1.Z * scaleFactor;
|
||||
|
@ -881,15 +893,15 @@ namespace MoonWorks.Math
|
|||
result.W = quaternion.W * num;
|
||||
}
|
||||
|
||||
public static Quaternion LookAt(in Vector3 forward, in Vector3 up)
|
||||
{
|
||||
Matrix4x4 orientation = Matrix4x4.Identity;
|
||||
orientation.Forward = forward;
|
||||
orientation.Right = Vector3.Normalize(Vector3.Cross(forward, up));
|
||||
orientation.Up = Vector3.Cross(orientation.Right, forward);
|
||||
public static Quaternion LookAt(in Vector3 forward, in Vector3 up)
|
||||
{
|
||||
Matrix4x4 orientation = Matrix4x4.Identity;
|
||||
orientation.Forward = forward;
|
||||
orientation.Right = Vector3.Normalize(Vector3.Cross(forward, up));
|
||||
orientation.Up = Vector3.Cross(orientation.Right, forward);
|
||||
|
||||
return Quaternion.CreateFromRotationMatrix(orientation);
|
||||
}
|
||||
return Quaternion.CreateFromRotationMatrix(orientation);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
[Serializable]
|
||||
[DebuggerDisplay("{DebugDisplayString,nq}")]
|
||||
|
@ -70,8 +70,8 @@ namespace MoonWorks.Math
|
|||
|
||||
public bool Equals(Ray other)
|
||||
{
|
||||
return ( this.Position.Equals(other.Position) &&
|
||||
this.Direction.Equals(other.Direction) );
|
||||
return (this.Position.Equals(other.Position) &&
|
||||
this.Direction.Equals(other.Direction));
|
||||
}
|
||||
|
||||
|
||||
|
@ -124,8 +124,8 @@ namespace MoonWorks.Math
|
|||
tMaxY = temp;
|
||||
}
|
||||
|
||||
if ( (tMin.HasValue && tMin > tMaxY) ||
|
||||
(tMax.HasValue && tMinY > tMax) )
|
||||
if ((tMin.HasValue && tMin > tMaxY) ||
|
||||
(tMax.HasValue && tMinY > tMax))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -153,8 +153,8 @@ namespace MoonWorks.Math
|
|||
tMaxZ = temp;
|
||||
}
|
||||
|
||||
if ( (tMin.HasValue && tMin > tMaxZ) ||
|
||||
(tMax.HasValue && tMinZ > tMax) )
|
||||
if ((tMin.HasValue && tMin > tMaxZ) ||
|
||||
(tMax.HasValue && tMinZ > tMax))
|
||||
{
|
||||
return null;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -20,7 +20,7 @@ using System.Diagnostics;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 2D-rectangle.
|
||||
|
@ -117,10 +117,10 @@ namespace MoonWorks.Math
|
|||
{
|
||||
get
|
||||
{
|
||||
return ( (Width == 0) &&
|
||||
return ((Width == 0) &&
|
||||
(Height == 0) &&
|
||||
(X == 0) &&
|
||||
(Y == 0) );
|
||||
(Y == 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,10 +218,10 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the provided coordinates lie inside this <see cref="Rectangle"/>. <c>false</c> otherwise.</returns>
|
||||
public bool Contains(int x, int y)
|
||||
{
|
||||
return ( (this.X <= x) &&
|
||||
return ((this.X <= x) &&
|
||||
(x < (this.X + this.Width)) &&
|
||||
(this.Y <= y) &&
|
||||
(y < (this.Y + this.Height)) );
|
||||
(y < (this.Y + this.Height)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -231,10 +231,10 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the provided <see cref="Point"/> lies inside this <see cref="Rectangle"/>. <c>false</c> otherwise.</returns>
|
||||
public bool Contains(Point value)
|
||||
{
|
||||
return ( (this.X <= value.X) &&
|
||||
return ((this.X <= value.X) &&
|
||||
(value.X < (this.X + this.Width)) &&
|
||||
(this.Y <= value.Y) &&
|
||||
(value.Y < (this.Y + this.Height)) );
|
||||
(value.Y < (this.Y + this.Height)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -244,26 +244,26 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the provided <see cref="Rectangle"/>'s bounds lie entirely inside this <see cref="Rectangle"/>. <c>false</c> otherwise.</returns>
|
||||
public bool Contains(Rectangle value)
|
||||
{
|
||||
return ( (this.X <= value.X) &&
|
||||
return ((this.X <= value.X) &&
|
||||
((value.X + value.Width) <= (this.X + this.Width)) &&
|
||||
(this.Y <= value.Y) &&
|
||||
((value.Y + value.Height) <= (this.Y + this.Height)) );
|
||||
((value.Y + value.Height) <= (this.Y + this.Height)));
|
||||
}
|
||||
|
||||
public void Contains(ref Point value, out bool result)
|
||||
{
|
||||
result = ( (this.X <= value.X) &&
|
||||
result = ((this.X <= value.X) &&
|
||||
(value.X < (this.X + this.Width)) &&
|
||||
(this.Y <= value.Y) &&
|
||||
(value.Y < (this.Y + this.Height)) );
|
||||
(value.Y < (this.Y + this.Height)));
|
||||
}
|
||||
|
||||
public void Contains(ref Rectangle value, out bool result)
|
||||
{
|
||||
result = ( (this.X <= value.X) &&
|
||||
result = ((this.X <= value.X) &&
|
||||
((value.X + value.Width) <= (this.X + this.Width)) &&
|
||||
(this.Y <= value.Y) &&
|
||||
((value.Y + value.Height) <= (this.Y + this.Height)) );
|
||||
((value.Y + value.Height) <= (this.Y + this.Height)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -349,10 +349,10 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if other <see cref="Rectangle"/> intersects with this rectangle; <c>false</c> otherwise.</returns>
|
||||
public bool Intersects(Rectangle value)
|
||||
{
|
||||
return ( value.Left < Right &&
|
||||
return (value.Left < Right &&
|
||||
Left < value.Right &&
|
||||
value.Top < Bottom &&
|
||||
Top < value.Bottom );
|
||||
Top < value.Bottom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -362,10 +362,10 @@ namespace MoonWorks.Math
|
|||
/// <param name="result"><c>true</c> if other <see cref="Rectangle"/> intersects with this rectangle; <c>false</c> otherwise. As an output parameter.</param>
|
||||
public void Intersects(ref Rectangle value, out bool result)
|
||||
{
|
||||
result = ( value.Left < Right &&
|
||||
result = (value.Left < Right &&
|
||||
Left < value.Right &&
|
||||
value.Top < Bottom &&
|
||||
Top < value.Bottom );
|
||||
Top < value.Bottom);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -374,10 +374,10 @@ namespace MoonWorks.Math
|
|||
|
||||
public static bool operator ==(Rectangle a, Rectangle b)
|
||||
{
|
||||
return ( (a.X == b.X) &&
|
||||
return ((a.X == b.X) &&
|
||||
(a.Y == b.Y) &&
|
||||
(a.Width == b.Width) &&
|
||||
(a.Height == b.Height) );
|
||||
(a.Height == b.Height));
|
||||
}
|
||||
|
||||
public static bool operator !=(Rectangle a, Rectangle b)
|
||||
|
@ -396,7 +396,8 @@ namespace MoonWorks.Math
|
|||
ref Rectangle value1,
|
||||
ref Rectangle value2,
|
||||
out Rectangle result
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (value1.Intersects(value2))
|
||||
{
|
||||
int right_side = System.Math.Min(
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 2D-vector.
|
||||
|
@ -163,8 +163,8 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Vector2 other)
|
||||
{
|
||||
return ( X == other.X &&
|
||||
Y == other.Y );
|
||||
return (X == other.X &&
|
||||
Y == other.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -195,13 +195,11 @@ namespace MoonWorks.Math
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns this <see cref="Vector2"/> to a unit vector with the same direction.
|
||||
/// Turns this <see cref="Vector2"/> to an angle in radians.
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
public float Angle()
|
||||
{
|
||||
float val = 1.0f / (float) System.Math.Sqrt((X * X) + (Y * Y));
|
||||
X *= val;
|
||||
Y *= val;
|
||||
return MathF.Atan2(Y, X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -264,7 +262,8 @@ namespace MoonWorks.Math
|
|||
Vector2 value3,
|
||||
float amount1,
|
||||
float amount2
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector2(
|
||||
MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2),
|
||||
MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2)
|
||||
|
@ -287,7 +286,8 @@ namespace MoonWorks.Math
|
|||
float amount1,
|
||||
float amount2,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2);
|
||||
result.Y = MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2);
|
||||
}
|
||||
|
@ -307,7 +307,8 @@ namespace MoonWorks.Math
|
|||
Vector2 value3,
|
||||
Vector2 value4,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector2(
|
||||
MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount),
|
||||
MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount)
|
||||
|
@ -330,7 +331,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 value4,
|
||||
float amount,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount);
|
||||
result.Y = MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount);
|
||||
}
|
||||
|
@ -362,7 +364,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 min,
|
||||
ref Vector2 max,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Clamp(value1.X, min.X, max.X);
|
||||
result.Y = MathHelper.Clamp(value1.Y, min.Y, max.Y);
|
||||
}
|
||||
|
@ -413,7 +416,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 value1,
|
||||
ref Vector2 value2,
|
||||
out float result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float v1 = value1.X - value2.X, v2 = value1.Y - value2.Y;
|
||||
result = (v1 * v1) + (v2 * v2);
|
||||
}
|
||||
|
@ -507,7 +511,8 @@ namespace MoonWorks.Math
|
|||
Vector2 value2,
|
||||
Vector2 tangent2,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector2 result = new Vector2();
|
||||
Hermite(ref value1, ref tangent1, ref value2, ref tangent2, amount, out result);
|
||||
return result;
|
||||
|
@ -529,7 +534,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 tangent2,
|
||||
float amount,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount);
|
||||
result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount);
|
||||
}
|
||||
|
@ -561,7 +567,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 value2,
|
||||
float amount,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Lerp(value1.X, value2.X, amount);
|
||||
result.Y = MathHelper.Lerp(value1.Y, value2.Y, amount);
|
||||
}
|
||||
|
@ -700,7 +707,14 @@ namespace MoonWorks.Math
|
|||
/// <returns>Unit vector.</returns>
|
||||
public static Vector2 Normalize(Vector2 value)
|
||||
{
|
||||
float val = 1.0f / (float) System.Math.Sqrt((value.X * value.X) + (value.Y * value.Y));
|
||||
float lengthSquared = (value.X * value.X) + (value.Y * value.Y);
|
||||
|
||||
if (lengthSquared == 0)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
|
||||
float val = 1.0f / System.MathF.Sqrt(lengthSquared);
|
||||
value.X *= val;
|
||||
value.Y *= val;
|
||||
return value;
|
||||
|
@ -773,7 +787,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 value2,
|
||||
float amount,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.SmoothStep(value1.X, value2.X, amount);
|
||||
result.Y = MathHelper.SmoothStep(value1.Y, value2.Y, amount);
|
||||
}
|
||||
|
@ -827,7 +842,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 position,
|
||||
ref Matrix4x4 matrix,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = (position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M41;
|
||||
float y = (position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M42;
|
||||
result.X = x;
|
||||
|
@ -856,7 +872,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 value,
|
||||
ref Quaternion rotation,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = 2 * -(rotation.Z * value.Y);
|
||||
float y = 2 * (rotation.Z * value.X);
|
||||
float z = 2 * (rotation.X * value.Y - rotation.Y * value.X);
|
||||
|
@ -865,6 +882,20 @@ namespace MoonWorks.Math
|
|||
result.Y = value.Y + y * rotation.W + (rotation.Z * x - rotation.X * z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of 2d-vector by the specified <see cref="Matrix3x2"/>.
|
||||
/// </summary>
|
||||
/// <param name="position">Source <see cref="Vector2"/>.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix3x2"/>.</param>
|
||||
/// <returns>Transformed <see cref="Vector2"/>.</returns>
|
||||
public static Vector2 Transform(Vector2 position, Matrix3x2 matrix)
|
||||
{
|
||||
return new Vector2(
|
||||
(position.X * matrix.M11) + (position.Y * matrix.M21) + matrix.M31,
|
||||
(position.X * matrix.M12) + (position.Y * matrix.M22) + matrix.M32
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply transformation on all vectors within array of <see cref="Vector2"/> by the specified <see cref="Matrix4x4"/> and places the results in an another array.
|
||||
/// </summary>
|
||||
|
@ -875,7 +906,8 @@ namespace MoonWorks.Math
|
|||
Vector2[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector2[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
Transform(sourceArray, 0, ref matrix, destinationArray, 0, sourceArray.Length);
|
||||
}
|
||||
|
||||
|
@ -895,7 +927,8 @@ namespace MoonWorks.Math
|
|||
Vector2[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
for (int x = 0; x < length; x += 1)
|
||||
{
|
||||
Vector2 position = sourceArray[sourceIndex + x];
|
||||
|
@ -918,7 +951,8 @@ namespace MoonWorks.Math
|
|||
Vector2[] sourceArray,
|
||||
ref Quaternion rotation,
|
||||
Vector2[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
Transform(
|
||||
sourceArray,
|
||||
0,
|
||||
|
@ -945,7 +979,8 @@ namespace MoonWorks.Math
|
|||
Vector2[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < length; i += 1)
|
||||
{
|
||||
Vector2 position = sourceArray[sourceIndex + i];
|
||||
|
@ -969,6 +1004,19 @@ namespace MoonWorks.Math
|
|||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix3x2"/>.
|
||||
/// </summary>
|
||||
/// <param name="normal">Source <see cref="Vector2"/> which represents a normal vector.</param>
|
||||
/// <param name="matrix">The transformation <see cref="Matrix3x2"/>.</param>
|
||||
/// <returns>Transformed normal.</returns>
|
||||
public static Vector2 TransformNormal(Vector2 normal, Matrix3x2 matrix)
|
||||
{
|
||||
return new Vector2(
|
||||
normal.X * matrix.M11 + normal.Y * matrix.M21,
|
||||
normal.X * matrix.M12 + normal.Y * matrix.M22);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Vector2"/> that contains a transformation of the specified normal by the specified <see cref="Matrix4x4"/>.
|
||||
/// </summary>
|
||||
|
@ -979,7 +1027,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 normal,
|
||||
ref Matrix4x4 matrix,
|
||||
out Vector2 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = (normal.X * matrix.M11) + (normal.Y * matrix.M21);
|
||||
float y = (normal.X * matrix.M12) + (normal.Y * matrix.M22);
|
||||
result.X = x;
|
||||
|
@ -996,7 +1045,8 @@ namespace MoonWorks.Math
|
|||
Vector2[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector2[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
TransformNormal(
|
||||
sourceArray,
|
||||
0,
|
||||
|
@ -1023,7 +1073,8 @@ namespace MoonWorks.Math
|
|||
Vector2[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
for (int i = 0; i < length; i += 1)
|
||||
{
|
||||
Vector2 position = sourceArray[sourceIndex + i];
|
||||
|
@ -1034,6 +1085,19 @@ namespace MoonWorks.Math
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a Vector2 by an angle.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector to rotate.</param>
|
||||
/// <param name="angle">The angle in radians.</param>
|
||||
public static Vector2 Rotate(Vector2 vector, float angle)
|
||||
{
|
||||
return new Vector2(
|
||||
vector.X * (float) System.Math.Cos(angle) - vector.Y * (float) System.Math.Sin(angle),
|
||||
vector.X * (float) System.Math.Sin(angle) + vector.Y * (float) System.Math.Cos(angle)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Static Operators
|
||||
|
@ -1058,8 +1122,8 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator ==(Vector2 value1, Vector2 value2)
|
||||
{
|
||||
return ( value1.X == value2.X &&
|
||||
value1.Y == value2.Y );
|
||||
return (value1.X == value2.X &&
|
||||
value1.Y == value2.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -22,7 +22,7 @@ using System.Text;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 3D-vector.
|
||||
|
@ -270,9 +270,9 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Vector3 other)
|
||||
{
|
||||
return ( X == other.X &&
|
||||
return (X == other.X &&
|
||||
Y == other.Y &&
|
||||
Z == other.Z );
|
||||
Z == other.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -302,21 +302,6 @@ namespace MoonWorks.Math
|
|||
return (X * X) + (Y * Y) + (Z * Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns this <see cref="Vector3"/> to a unit vector with the same direction.
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
{
|
||||
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||
(X * X) +
|
||||
(Y * Y) +
|
||||
(Z * Z)
|
||||
);
|
||||
X *= factor;
|
||||
Y *= factor;
|
||||
Z *= factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="String"/> representation of this <see cref="Vector3"/> in the format:
|
||||
/// {X:[<see cref="X"/>] Y:[<see cref="Y"/>] Z:[<see cref="Z"/>]}
|
||||
|
@ -383,7 +368,8 @@ namespace MoonWorks.Math
|
|||
Vector3 value3,
|
||||
float amount1,
|
||||
float amount2
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector3(
|
||||
MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2),
|
||||
MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2),
|
||||
|
@ -407,7 +393,8 @@ namespace MoonWorks.Math
|
|||
float amount1,
|
||||
float amount2,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2);
|
||||
result.Y = MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2);
|
||||
result.Z = MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2);
|
||||
|
@ -428,7 +415,8 @@ namespace MoonWorks.Math
|
|||
Vector3 value3,
|
||||
Vector3 value4,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector3(
|
||||
MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount),
|
||||
MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount),
|
||||
|
@ -452,7 +440,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 value4,
|
||||
float amount,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount);
|
||||
result.Y = MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount);
|
||||
result.Z = MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount);
|
||||
|
@ -486,24 +475,26 @@ namespace MoonWorks.Math
|
|||
ref Vector3 min,
|
||||
ref Vector3 max,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Clamp(value1.X, min.X, max.X);
|
||||
result.Y = MathHelper.Clamp(value1.Y, min.Y, max.Y);
|
||||
result.Z = MathHelper.Clamp(value1.Z, min.Z, max.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the magnitude of the specified vector.
|
||||
/// </summary>
|
||||
/// <param name="value">The vector to clamp.</param>
|
||||
/// <param name="maxLength">The maximum length of the vector.</param>
|
||||
/// <returns></returns>
|
||||
/// Clamps the magnitude of the specified vector.
|
||||
/// </summary>
|
||||
/// <param name="value">The vector to clamp.</param>
|
||||
/// <param name="maxLength">The maximum length of the vector.</param>
|
||||
/// <returns></returns>
|
||||
public static Vector3 ClampMagnitude(
|
||||
Vector3 value,
|
||||
float maxLength
|
||||
) {
|
||||
return (value.LengthSquared() > maxLength * maxLength) ? (Vector3.Normalize(value) * maxLength) : value;
|
||||
}
|
||||
)
|
||||
{
|
||||
return (value.LengthSquared() > maxLength * maxLength) ? (Vector3.Normalize(value) * maxLength) : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the cross product of two vectors.
|
||||
|
@ -583,7 +574,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 value1,
|
||||
ref Vector3 value2,
|
||||
out float result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result = (
|
||||
(value1.X - value2.X) * (value1.X - value2.X) +
|
||||
(value1.Y - value2.Y) * (value1.Y - value2.Y) +
|
||||
|
@ -688,7 +680,8 @@ namespace MoonWorks.Math
|
|||
Vector3 value2,
|
||||
Vector3 tangent2,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
Vector3 result = new Vector3();
|
||||
Hermite(ref value1, ref tangent1, ref value2, ref tangent2, amount, out result);
|
||||
return result;
|
||||
|
@ -710,7 +703,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 tangent2,
|
||||
float amount,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount);
|
||||
result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount);
|
||||
result.Z = MathHelper.Hermite(value1.Z, tangent1.Z, value2.Z, tangent2.Z, amount);
|
||||
|
@ -744,7 +738,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 value2,
|
||||
float amount,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Lerp(value1.X, value2.X, amount);
|
||||
result.Y = MathHelper.Lerp(value1.Y, value2.Y, amount);
|
||||
result.Z = MathHelper.Lerp(value1.Z, value2.Z, amount);
|
||||
|
@ -890,11 +885,14 @@ namespace MoonWorks.Math
|
|||
/// <returns>Unit vector.</returns>
|
||||
public static Vector3 Normalize(Vector3 value)
|
||||
{
|
||||
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||
(value.X * value.X) +
|
||||
(value.Y * value.Y) +
|
||||
(value.Z * value.Z)
|
||||
);
|
||||
float lengthSquared = (value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z);
|
||||
|
||||
if (lengthSquared == 0f)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
|
||||
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
|
||||
return new Vector3(
|
||||
value.X * factor,
|
||||
value.Y * factor,
|
||||
|
@ -992,7 +990,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 value2,
|
||||
float amount,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.SmoothStep(value1.X, value2.X, amount);
|
||||
result.Y = MathHelper.SmoothStep(value1.Y, value2.Y, amount);
|
||||
result.Z = MathHelper.SmoothStep(value1.Z, value2.Z, amount);
|
||||
|
@ -1047,7 +1046,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 position,
|
||||
ref Matrix4x4 matrix,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = (
|
||||
(position.X * matrix.M11) +
|
||||
(position.Y * matrix.M21) +
|
||||
|
@ -1081,7 +1081,8 @@ namespace MoonWorks.Math
|
|||
Vector3[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector3[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
Debug.Assert(
|
||||
destinationArray.Length >= sourceArray.Length,
|
||||
"The destination array is smaller than the source array."
|
||||
|
@ -1121,7 +1122,8 @@ namespace MoonWorks.Math
|
|||
Vector3[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
Debug.Assert(
|
||||
sourceArray.Length - sourceIndex >= length,
|
||||
"The source array is too small for the given sourceIndex and length."
|
||||
|
@ -1173,7 +1175,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 value,
|
||||
ref Quaternion rotation,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = 2 * (rotation.Y * value.Z - rotation.Z * value.Y);
|
||||
float y = 2 * (rotation.Z * value.X - rotation.X * value.Z);
|
||||
float z = 2 * (rotation.X * value.Y - rotation.Y * value.X);
|
||||
|
@ -1193,7 +1196,8 @@ namespace MoonWorks.Math
|
|||
Vector3[] sourceArray,
|
||||
ref Quaternion rotation,
|
||||
Vector3[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
Debug.Assert(
|
||||
destinationArray.Length >= sourceArray.Length,
|
||||
"The destination array is smaller than the source array."
|
||||
|
@ -1236,7 +1240,8 @@ namespace MoonWorks.Math
|
|||
Vector3[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
Debug.Assert(
|
||||
sourceArray.Length - sourceIndex >= length,
|
||||
"The source array is too small for the given sourceIndex and length."
|
||||
|
@ -1289,7 +1294,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 normal,
|
||||
ref Matrix4x4 matrix,
|
||||
out Vector3 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
float x = (normal.X * matrix.M11) + (normal.Y * matrix.M21) + (normal.Z * matrix.M31);
|
||||
float y = (normal.X * matrix.M12) + (normal.Y * matrix.M22) + (normal.Z * matrix.M32);
|
||||
float z = (normal.X * matrix.M13) + (normal.Y * matrix.M23) + (normal.Z * matrix.M33);
|
||||
|
@ -1308,7 +1314,8 @@ namespace MoonWorks.Math
|
|||
Vector3[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector3[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
Debug.Assert(
|
||||
destinationArray.Length >= sourceArray.Length,
|
||||
"The destination array is smaller than the source array."
|
||||
|
@ -1339,7 +1346,8 @@ namespace MoonWorks.Math
|
|||
Vector3[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (sourceArray == null)
|
||||
{
|
||||
throw new ArgumentNullException("sourceArray");
|
||||
|
@ -1396,9 +1404,9 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public static bool operator ==(Vector3 value1, Vector3 value2)
|
||||
{
|
||||
return ( value1.X == value2.X &&
|
||||
return (value1.X == value2.X &&
|
||||
value1.Y == value2.Y &&
|
||||
value1.Z == value2.Z );
|
||||
value1.Z == value2.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks.Math
|
||||
namespace MoonWorks.Math.Float
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a 4D-vector.
|
||||
|
@ -234,10 +234,10 @@ namespace MoonWorks.Math
|
|||
/// <returns><c>true</c> if the instances are equal; <c>false</c> otherwise.</returns>
|
||||
public bool Equals(Vector4 other)
|
||||
{
|
||||
return ( X == other.X &&
|
||||
return (X == other.X &&
|
||||
Y == other.Y &&
|
||||
Z == other.Z &&
|
||||
W == other.W );
|
||||
W == other.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -267,23 +267,6 @@ namespace MoonWorks.Math
|
|||
return (X * X) + (Y * Y) + (Z * Z) + (W * W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns this <see cref="Vector4"/> to a unit vector with the same direction.
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
{
|
||||
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||
(X * X) +
|
||||
(Y * Y) +
|
||||
(Z * Z) +
|
||||
(W * W)
|
||||
);
|
||||
X *= factor;
|
||||
Y *= factor;
|
||||
Z *= factor;
|
||||
W *= factor;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (
|
||||
|
@ -344,7 +327,8 @@ namespace MoonWorks.Math
|
|||
Vector4 value3,
|
||||
float amount1,
|
||||
float amount2
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector4(
|
||||
MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2),
|
||||
MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2),
|
||||
|
@ -369,7 +353,8 @@ namespace MoonWorks.Math
|
|||
float amount1,
|
||||
float amount2,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Barycentric(value1.X, value2.X, value3.X, amount1, amount2);
|
||||
result.Y = MathHelper.Barycentric(value1.Y, value2.Y, value3.Y, amount1, amount2);
|
||||
result.Z = MathHelper.Barycentric(value1.Z, value2.Z, value3.Z, amount1, amount2);
|
||||
|
@ -391,7 +376,8 @@ namespace MoonWorks.Math
|
|||
Vector4 value3,
|
||||
Vector4 value4,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector4(
|
||||
MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount),
|
||||
MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount),
|
||||
|
@ -416,7 +402,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 value4,
|
||||
float amount,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.CatmullRom(value1.X, value2.X, value3.X, value4.X, amount);
|
||||
result.Y = MathHelper.CatmullRom(value1.Y, value2.Y, value3.Y, value4.Y, amount);
|
||||
result.Z = MathHelper.CatmullRom(value1.Z, value2.Z, value3.Z, value4.Z, amount);
|
||||
|
@ -452,7 +439,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 min,
|
||||
ref Vector4 max,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Clamp(value1.X, min.X, max.X);
|
||||
result.Y = MathHelper.Clamp(value1.Y, min.Y, max.Y);
|
||||
result.Z = MathHelper.Clamp(value1.Z, min.Z, max.Z);
|
||||
|
@ -507,7 +495,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 value1,
|
||||
ref Vector4 value2,
|
||||
out float result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result = (
|
||||
(value1.W - value2.W) * (value1.W - value2.W) +
|
||||
(value1.X - value2.X) * (value1.X - value2.X) +
|
||||
|
@ -572,7 +561,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 value1,
|
||||
ref Vector4 value2,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.W = value1.W / value2.W;
|
||||
result.X = value1.X / value2.X;
|
||||
result.Y = value1.Y / value2.Y;
|
||||
|
@ -626,7 +616,8 @@ namespace MoonWorks.Math
|
|||
Vector4 value2,
|
||||
Vector4 tangent2,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
return new Vector4(
|
||||
MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount),
|
||||
MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount),
|
||||
|
@ -651,7 +642,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 tangent2,
|
||||
float amount,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.W = MathHelper.Hermite(value1.W, tangent1.W, value2.W, tangent2.W, amount);
|
||||
result.X = MathHelper.Hermite(value1.X, tangent1.X, value2.X, tangent2.X, amount);
|
||||
result.Y = MathHelper.Hermite(value1.Y, tangent1.Y, value2.Y, tangent2.Y, amount);
|
||||
|
@ -687,7 +679,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 value2,
|
||||
float amount,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.Lerp(value1.X, value2.X, amount);
|
||||
result.Y = MathHelper.Lerp(value1.Y, value2.Y, amount);
|
||||
result.Z = MathHelper.Lerp(value1.Z, value2.Z, amount);
|
||||
|
@ -843,12 +836,15 @@ namespace MoonWorks.Math
|
|||
/// <returns>Unit vector.</returns>
|
||||
public static Vector4 Normalize(Vector4 vector)
|
||||
{
|
||||
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||
(vector.X * vector.X) +
|
||||
(vector.Y * vector.Y) +
|
||||
(vector.Z * vector.Z) +
|
||||
(vector.W * vector.W)
|
||||
);
|
||||
var lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
|
||||
(vector.Z * vector.Z) + (vector.W * vector.W);
|
||||
|
||||
if (lengthSquared == 0)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
|
||||
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
|
||||
return new Vector4(
|
||||
vector.X * factor,
|
||||
vector.Y * factor,
|
||||
|
@ -860,16 +856,20 @@ namespace MoonWorks.Math
|
|||
/// <summary>
|
||||
/// Creates a new <see cref="Vector4"/> that contains a normalized values from another vector.
|
||||
/// </summary>
|
||||
/// <param name="value">Source <see cref="Vector4"/>.</param>
|
||||
/// <param name="vector">Source <see cref="Vector4"/>.</param>
|
||||
/// <param name="result">Unit vector as an output parameter.</param>
|
||||
public static void Normalize(ref Vector4 vector, out Vector4 result)
|
||||
{
|
||||
float factor = 1.0f / (float) System.Math.Sqrt(
|
||||
(vector.X * vector.X) +
|
||||
(vector.Y * vector.Y) +
|
||||
(vector.Z * vector.Z) +
|
||||
(vector.W * vector.W)
|
||||
);
|
||||
float lengthSquared = (vector.X * vector.X) + (vector.Y * vector.Y) +
|
||||
(vector.Z * vector.Z) + (vector.W * vector.W);
|
||||
|
||||
if (lengthSquared == 0)
|
||||
{
|
||||
result = Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
float factor = 1.0f / System.MathF.Sqrt(lengthSquared);
|
||||
result.X = vector.X * factor;
|
||||
result.Y = vector.Y * factor;
|
||||
result.Z = vector.Z * factor;
|
||||
|
@ -905,7 +905,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 value2,
|
||||
float amount,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
result.X = MathHelper.SmoothStep(value1.X, value2.X, amount);
|
||||
result.Y = MathHelper.SmoothStep(value1.Y, value2.Y, amount);
|
||||
result.Z = MathHelper.SmoothStep(value1.Z, value2.Z, amount);
|
||||
|
@ -1081,7 +1082,8 @@ namespace MoonWorks.Math
|
|||
Vector4[] sourceArray,
|
||||
ref Matrix4x4 matrix,
|
||||
Vector4[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (sourceArray == null)
|
||||
{
|
||||
throw new ArgumentNullException("sourceArray");
|
||||
|
@ -1122,7 +1124,8 @@ namespace MoonWorks.Math
|
|||
Vector4[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (sourceArray == null)
|
||||
{
|
||||
throw new ArgumentNullException("sourceArray");
|
||||
|
@ -1202,7 +1205,8 @@ namespace MoonWorks.Math
|
|||
ref Vector2 value,
|
||||
ref Quaternion rotation,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
double xx = rotation.X + rotation.X;
|
||||
double yy = rotation.Y + rotation.Y;
|
||||
double zz = rotation.Z + rotation.Z;
|
||||
|
@ -1240,7 +1244,8 @@ namespace MoonWorks.Math
|
|||
ref Vector3 value,
|
||||
ref Quaternion rotation,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
double xx = rotation.X + rotation.X;
|
||||
double yy = rotation.Y + rotation.Y;
|
||||
double zz = rotation.Z + rotation.Z;
|
||||
|
@ -1281,7 +1286,8 @@ namespace MoonWorks.Math
|
|||
ref Vector4 value,
|
||||
ref Quaternion rotation,
|
||||
out Vector4 result
|
||||
) {
|
||||
)
|
||||
{
|
||||
double xx = rotation.X + rotation.X;
|
||||
double yy = rotation.Y + rotation.Y;
|
||||
double zz = rotation.Z + rotation.Z;
|
||||
|
@ -1322,7 +1328,8 @@ namespace MoonWorks.Math
|
|||
Vector4[] sourceArray,
|
||||
ref Quaternion rotation,
|
||||
Vector4[] destinationArray
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (sourceArray == null)
|
||||
{
|
||||
throw new ArgumentException("sourceArray");
|
||||
|
@ -1363,7 +1370,8 @@ namespace MoonWorks.Math
|
|||
Vector4[] destinationArray,
|
||||
int destinationIndex,
|
||||
int length
|
||||
) {
|
||||
)
|
||||
{
|
||||
if (sourceArray == null)
|
||||
{
|
||||
throw new ArgumentException("sourceArray");
|
||||
|
@ -1405,10 +1413,10 @@ namespace MoonWorks.Math
|
|||
|
||||
public static bool operator ==(Vector4 value1, Vector4 value2)
|
||||
{
|
||||
return ( value1.X == value2.X &&
|
||||
return (value1.X == value2.X &&
|
||||
value1.Y == value2.Y &&
|
||||
value1.Z == value2.Z &&
|
||||
value1.W == value2.W );
|
||||
value1.W == value2.W);
|
||||
}
|
||||
|
||||
public static bool operator !=(Vector4 value1, Vector4 value2)
|
|
@ -1,4 +1,4 @@
|
|||
#region License
|
||||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
|
@ -98,7 +98,8 @@ namespace MoonWorks.Math
|
|||
float value3,
|
||||
float amount1,
|
||||
float amount2
|
||||
) {
|
||||
)
|
||||
{
|
||||
return value1 + (value2 - value1) * amount1 + (value3 - value1) * amount2;
|
||||
}
|
||||
|
||||
|
@ -117,7 +118,8 @@ namespace MoonWorks.Math
|
|||
float value3,
|
||||
float value4,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
/* Using formula from http://www.mvps.org/directx/articles/catmull/
|
||||
* Internally using doubles not to lose precision.
|
||||
*/
|
||||
|
@ -158,6 +160,26 @@ namespace MoonWorks.Math
|
|||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restricts a value to be within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to clamp.</param>
|
||||
/// <param name="min">
|
||||
/// The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c>
|
||||
/// will be returned.
|
||||
/// </param>
|
||||
/// <param name="max">
|
||||
/// The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c>
|
||||
/// will be returned.
|
||||
/// </param>
|
||||
/// <returns>The clamped value.</returns>
|
||||
public static int Clamp(int value, int min, int max)
|
||||
{
|
||||
value = (value > max) ? max : value;
|
||||
value = (value < min) ? min : value;
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the absolute value of the difference of two values.
|
||||
/// </summary>
|
||||
|
@ -184,7 +206,8 @@ namespace MoonWorks.Math
|
|||
float value2,
|
||||
float tangent2,
|
||||
float amount
|
||||
) {
|
||||
)
|
||||
{
|
||||
/* All transformed to double not to lose precision
|
||||
* Otherwise, for high numbers of param:amount the result is NaN instead
|
||||
* of Infinity.
|
||||
|
@ -277,6 +300,16 @@ namespace MoonWorks.Math
|
|||
return result;
|
||||
}
|
||||
|
||||
public static float Quantize(float value, float step)
|
||||
{
|
||||
return System.MathF.Round(value / step) * step;
|
||||
}
|
||||
|
||||
public static Fixed.Fix64 Quantize(Fixed.Fix64 value, Fixed.Fix64 step)
|
||||
{
|
||||
return Fixed.Fix64.Round(value / step) * step;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
|
@ -328,31 +361,65 @@ namespace MoonWorks.Math
|
|||
return angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rescales a value within a given range to a new range.
|
||||
/// </summary>
|
||||
public static float Normalize(short value, short min, short max, short newMin, short newMax)
|
||||
{
|
||||
return ((float) (value - min) * (newMax - newMin)) / (max - min) + newMin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rescales a value within a given range to a new range.
|
||||
/// </summary>
|
||||
public static float Normalize(float value, float min, float max, float newMin, float newMax)
|
||||
{
|
||||
return ((value - min) * (newMax - newMin)) / (max - min) + newMin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Step from start towards end by change.
|
||||
/// </summary>
|
||||
/// <param name="start">Start value.</param>
|
||||
/// <param name="end">End value.</param>
|
||||
/// <param name="change">Change value.</param>
|
||||
public static float Approach(float start, float end, float change)
|
||||
{
|
||||
return start < end ?
|
||||
System.Math.Min(start + change, end) :
|
||||
System.Math.Max(start - change, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Step from start towards end by change.
|
||||
/// </summary>
|
||||
/// <param name="start">Start value.</param>
|
||||
/// <param name="end">End value.</param>
|
||||
/// <param name="change">Change value.</param>
|
||||
public static int Approach(int start, int end, int change)
|
||||
{
|
||||
return start < end ?
|
||||
System.Math.Min(start + change, end) :
|
||||
System.Math.Max(start - change, end);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Step from start towards end by change.
|
||||
/// </summary>
|
||||
/// <param name="start">Start value.</param>
|
||||
/// <param name="end">End value.</param>
|
||||
/// <param name="change">Change value.</param>
|
||||
public static Fixed.Fix64 Approach(Fixed.Fix64 start, Fixed.Fix64 end, Fixed.Fix64 change)
|
||||
{
|
||||
return start < end ?
|
||||
Fixed.Fix64.Min(start + change, end) :
|
||||
Fixed.Fix64.Max(start - change, end);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Static Methods
|
||||
|
||||
// FIXME: This could be an extension! ClampIntEXT? -flibit
|
||||
/// <summary>
|
||||
/// Restricts a value to be within a specified range.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to clamp.</param>
|
||||
/// <param name="min">
|
||||
/// The minimum value. If <c>value</c> is less than <c>min</c>, <c>min</c>
|
||||
/// will be returned.
|
||||
/// </param>
|
||||
/// <param name="max">
|
||||
/// The maximum value. If <c>value</c> is greater than <c>max</c>, <c>max</c>
|
||||
/// will be returned.
|
||||
/// </param>
|
||||
/// <returns>The clamped value.</returns>
|
||||
internal static int Clamp(int value, int min, int max)
|
||||
{
|
||||
value = (value > max) ? max : value;
|
||||
value = (value < min) ? min : value;
|
||||
return value;
|
||||
}
|
||||
|
||||
internal static bool WithinEpsilon(float floatA, float floatB)
|
||||
{
|
||||
return System.Math.Abs(floatA - floatB) < MachineEpsilonFloat;
|
||||
|
|
|
@ -1,826 +0,0 @@
|
|||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
*/
|
||||
|
||||
/* Derived from code by Microsoft.
|
||||
* Released under the MIT license.
|
||||
* See microsoft.LICENSE for details.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MoonWorks.Math
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure encapsulating a 3x2 matrix.
|
||||
/// </summary>
|
||||
public struct Matrix3x2 : IEquatable<Matrix3x2>
|
||||
{
|
||||
#region Public Fields
|
||||
/// <summary>
|
||||
/// The first element of the first row
|
||||
/// </summary>
|
||||
public float M11;
|
||||
/// <summary>
|
||||
/// The second element of the first row
|
||||
/// </summary>
|
||||
public float M12;
|
||||
/// <summary>
|
||||
/// The first element of the second row
|
||||
/// </summary>
|
||||
public float M21;
|
||||
/// <summary>
|
||||
/// The second element of the second row
|
||||
/// </summary>
|
||||
public float M22;
|
||||
/// <summary>
|
||||
/// The first element of the third row
|
||||
/// </summary>
|
||||
public float M31;
|
||||
/// <summary>
|
||||
/// The second element of the third row
|
||||
/// </summary>
|
||||
public float M32;
|
||||
#endregion Public Fields
|
||||
|
||||
private static readonly Matrix3x2 _identity = new Matrix3x2
|
||||
(
|
||||
1f, 0f,
|
||||
0f, 1f,
|
||||
0f, 0f
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the multiplicative identity matrix.
|
||||
/// </summary>
|
||||
public static Matrix3x2 Identity
|
||||
{
|
||||
get { return _identity; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the matrix is the identity matrix.
|
||||
/// </summary>
|
||||
public bool IsIdentity
|
||||
{
|
||||
get
|
||||
{
|
||||
return M11 == 1f && M22 == 1f && // Check diagonal element first for early out.
|
||||
M12 == 0f &&
|
||||
M21 == 0f &&
|
||||
M31 == 0f && M32 == 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the translation component of this matrix.
|
||||
/// </summary>
|
||||
public Vector2 Translation
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2(M31, M32);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
M31 = value.X;
|
||||
M32 = value.Y;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a Matrix3x2 from the given components.
|
||||
/// </summary>
|
||||
public Matrix3x2(float m11, float m12,
|
||||
float m21, float m22,
|
||||
float m31, float m32)
|
||||
{
|
||||
this.M11 = m11;
|
||||
this.M12 = m12;
|
||||
this.M21 = m21;
|
||||
this.M22 = m22;
|
||||
this.M31 = m31;
|
||||
this.M32 = m32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation matrix from the given vector.
|
||||
/// </summary>
|
||||
/// <param name="position">The translation position.</param>
|
||||
/// <returns>A translation matrix.</returns>
|
||||
public static Matrix3x2 CreateTranslation(Vector2 position)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = 1.0f;
|
||||
|
||||
result.M31 = position.X;
|
||||
result.M32 = position.Y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a translation matrix from the given X and Y components.
|
||||
/// </summary>
|
||||
/// <param name="xPosition">The X position.</param>
|
||||
/// <param name="yPosition">The Y position.</param>
|
||||
/// <returns>A translation matrix.</returns>
|
||||
public static Matrix3x2 CreateTranslation(float xPosition, float yPosition)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = 1.0f;
|
||||
|
||||
result.M31 = xPosition;
|
||||
result.M32 = yPosition;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given X and Y components.
|
||||
/// </summary>
|
||||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
||||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float xScale, float yScale)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = xScale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = yScale;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that is offset by a given center point.
|
||||
/// </summary>
|
||||
/// <param name="xScale">Value to scale by on the X-axis.</param>
|
||||
/// <param name="yScale">Value to scale by on the Y-axis.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float xScale, float yScale, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float tx = centerPoint.X * (1 - xScale);
|
||||
float ty = centerPoint.Y * (1 - yScale);
|
||||
|
||||
result.M11 = xScale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = yScale;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given vector scale.
|
||||
/// </summary>
|
||||
/// <param name="scales">The scale to use.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Vector2 scales)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = scales.X;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scales.Y;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix from the given vector scale with an offset from the given center point.
|
||||
/// </summary>
|
||||
/// <param name="scales">The scale to use.</param>
|
||||
/// <param name="centerPoint">The center offset.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(Vector2 scales, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float tx = centerPoint.X * (1 - scales.X);
|
||||
float ty = centerPoint.Y * (1 - scales.Y);
|
||||
|
||||
result.M11 = scales.X;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scales.Y;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that scales uniformly with the given scale.
|
||||
/// </summary>
|
||||
/// <param name="scale">The uniform scale to use.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float scale)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = scale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scale;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a scale matrix that scales uniformly with the given scale with an offset from the given center.
|
||||
/// </summary>
|
||||
/// <param name="scale">The uniform scale to use.</param>
|
||||
/// <param name="centerPoint">The center offset.</param>
|
||||
/// <returns>A scaling matrix.</returns>
|
||||
public static Matrix3x2 CreateScale(float scale, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float tx = centerPoint.X * (1 - scale);
|
||||
float ty = centerPoint.Y * (1 - scale);
|
||||
|
||||
result.M11 = scale;
|
||||
result.M12 = 0.0f;
|
||||
result.M21 = 0.0f;
|
||||
result.M22 = scale;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix from the given angles in radians.
|
||||
/// </summary>
|
||||
/// <param name="radiansX">The X angle, in radians.</param>
|
||||
/// <param name="radiansY">The Y angle, in radians.</param>
|
||||
/// <returns>A skew matrix.</returns>
|
||||
public static Matrix3x2 CreateSkew(float radiansX, float radiansY)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float xTan = (float)System.Math.Tan(radiansX);
|
||||
float yTan = (float)System.Math.Tan(radiansY);
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = yTan;
|
||||
result.M21 = xTan;
|
||||
result.M22 = 1.0f;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a skew matrix from the given angles in radians and a center point.
|
||||
/// </summary>
|
||||
/// <param name="radiansX">The X angle, in radians.</param>
|
||||
/// <param name="radiansY">The Y angle, in radians.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A skew matrix.</returns>
|
||||
public static Matrix3x2 CreateSkew(float radiansX, float radiansY, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
float xTan = (float)System.Math.Tan(radiansX);
|
||||
float yTan = (float)System.Math.Tan(radiansY);
|
||||
|
||||
float tx = -centerPoint.Y * xTan;
|
||||
float ty = -centerPoint.X * yTan;
|
||||
|
||||
result.M11 = 1.0f;
|
||||
result.M12 = yTan;
|
||||
result.M21 = xTan;
|
||||
result.M22 = 1.0f;
|
||||
result.M31 = tx;
|
||||
result.M32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rotation matrix using the given rotation in radians.
|
||||
/// </summary>
|
||||
/// <param name="radians">The amount of rotation, in radians.</param>
|
||||
/// <returns>A rotation matrix.</returns>
|
||||
public static Matrix3x2 CreateRotation(float radians)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
radians = (float)System.Math.IEEERemainder(radians, System.Math.PI * 2);
|
||||
|
||||
float c, s;
|
||||
|
||||
const float epsilon = 0.001f * (float)System.Math.PI / 180f; // 0.1% of a degree
|
||||
|
||||
if (radians > -epsilon && radians < epsilon)
|
||||
{
|
||||
// Exact case for zero rotation.
|
||||
c = 1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 90 degree rotation.
|
||||
c = 0;
|
||||
s = 1;
|
||||
}
|
||||
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
|
||||
{
|
||||
// Exact case for 180 degree rotation.
|
||||
c = -1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 270 degree rotation.
|
||||
c = 0;
|
||||
s = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arbitrary rotation.
|
||||
c = (float)System.Math.Cos(radians);
|
||||
s = (float)System.Math.Sin(radians);
|
||||
}
|
||||
|
||||
// [ c s ]
|
||||
// [ -s c ]
|
||||
// [ 0 0 ]
|
||||
result.M11 = c;
|
||||
result.M12 = s;
|
||||
result.M21 = -s;
|
||||
result.M22 = c;
|
||||
result.M31 = 0.0f;
|
||||
result.M32 = 0.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a rotation matrix using the given rotation in radians and a center point.
|
||||
/// </summary>
|
||||
/// <param name="radians">The amount of rotation, in radians.</param>
|
||||
/// <param name="centerPoint">The center point.</param>
|
||||
/// <returns>A rotation matrix.</returns>
|
||||
public static Matrix3x2 CreateRotation(float radians, Vector2 centerPoint)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
radians = (float)System.Math.IEEERemainder(radians, System.Math.PI * 2);
|
||||
|
||||
float c, s;
|
||||
|
||||
const float epsilon = 0.001f * (float)System.Math.PI / 180f; // 0.1% of a degree
|
||||
|
||||
if (radians > -epsilon && radians < epsilon)
|
||||
{
|
||||
// Exact case for zero rotation.
|
||||
c = 1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > System.Math.PI / 2 - epsilon && radians < System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 90 degree rotation.
|
||||
c = 0;
|
||||
s = 1;
|
||||
}
|
||||
else if (radians < -System.Math.PI + epsilon || radians > System.Math.PI - epsilon)
|
||||
{
|
||||
// Exact case for 180 degree rotation.
|
||||
c = -1;
|
||||
s = 0;
|
||||
}
|
||||
else if (radians > -System.Math.PI / 2 - epsilon && radians < -System.Math.PI / 2 + epsilon)
|
||||
{
|
||||
// Exact case for 270 degree rotation.
|
||||
c = 0;
|
||||
s = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Arbitrary rotation.
|
||||
c = (float)System.Math.Cos(radians);
|
||||
s = (float)System.Math.Sin(radians);
|
||||
}
|
||||
|
||||
float x = centerPoint.X * (1 - c) + centerPoint.Y * s;
|
||||
float y = centerPoint.Y * (1 - c) - centerPoint.X * s;
|
||||
|
||||
// [ c s ]
|
||||
// [ -s c ]
|
||||
// [ x y ]
|
||||
result.M11 = c;
|
||||
result.M12 = s;
|
||||
result.M21 = -s;
|
||||
result.M22 = c;
|
||||
result.M31 = x;
|
||||
result.M32 = y;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the determinant for this matrix.
|
||||
/// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1).
|
||||
/// </summary>
|
||||
/// <returns>The determinant.</returns>
|
||||
public float GetDeterminant()
|
||||
{
|
||||
// There isn't actually any such thing as a determinant for a non-square matrix,
|
||||
// but this 3x2 type is really just an optimization of a 3x3 where we happen to
|
||||
// know the rightmost column is always (0, 0, 1). So we expand to 3x3 format:
|
||||
//
|
||||
// [ M11, M12, 0 ]
|
||||
// [ M21, M22, 0 ]
|
||||
// [ M31, M32, 1 ]
|
||||
//
|
||||
// Sum the diagonal products:
|
||||
// (M11 * M22 * 1) + (M12 * 0 * M31) + (0 * M21 * M32)
|
||||
//
|
||||
// Subtract the opposite diagonal products:
|
||||
// (M31 * M22 * 0) + (M32 * 0 * M11) + (1 * M21 * M12)
|
||||
//
|
||||
// Collapse out the constants and oh look, this is just a 2x2 determinant!
|
||||
|
||||
return (M11 * M22) - (M21 * M12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to invert the given matrix. If the operation succeeds, the inverted matrix is stored in the result parameter.
|
||||
/// </summary>
|
||||
/// <param name="matrix">The source matrix.</param>
|
||||
/// <param name="result">The output matrix.</param>
|
||||
/// <returns>True if the operation succeeded, False otherwise.</returns>
|
||||
public static bool Invert(Matrix3x2 matrix, out Matrix3x2 result)
|
||||
{
|
||||
float det = (matrix.M11 * matrix.M22) - (matrix.M21 * matrix.M12);
|
||||
|
||||
if (System.Math.Abs(det) < float.Epsilon)
|
||||
{
|
||||
result = new Matrix3x2(float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN);
|
||||
return false;
|
||||
}
|
||||
|
||||
float invDet = 1.0f / det;
|
||||
|
||||
result.M11 = matrix.M22 * invDet;
|
||||
result.M12 = -matrix.M12 * invDet;
|
||||
result.M21 = -matrix.M21 * invDet;
|
||||
result.M22 = matrix.M11 * invDet;
|
||||
result.M31 = (matrix.M21 * matrix.M32 - matrix.M31 * matrix.M22) * invDet;
|
||||
result.M32 = (matrix.M31 * matrix.M12 - matrix.M11 * matrix.M32) * invDet;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates from matrix1 to matrix2, based on the third parameter.
|
||||
/// </summary>
|
||||
/// <param name="matrix1">The first source matrix.</param>
|
||||
/// <param name="matrix2">The second source matrix.</param>
|
||||
/// <param name="amount">The relative weighting of matrix2.</param>
|
||||
/// <returns>The interpolated matrix.</returns>
|
||||
public static Matrix3x2 Lerp(Matrix3x2 matrix1, Matrix3x2 matrix2, float amount)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
// First row
|
||||
result.M11 = matrix1.M11 + (matrix2.M11 - matrix1.M11) * amount;
|
||||
result.M12 = matrix1.M12 + (matrix2.M12 - matrix1.M12) * amount;
|
||||
|
||||
// Second row
|
||||
result.M21 = matrix1.M21 + (matrix2.M21 - matrix1.M21) * amount;
|
||||
result.M22 = matrix1.M22 + (matrix2.M22 - matrix1.M22) * amount;
|
||||
|
||||
// Third row
|
||||
result.M31 = matrix1.M31 + (matrix2.M31 - matrix1.M31) * amount;
|
||||
result.M32 = matrix1.M32 + (matrix2.M32 - matrix1.M32) * amount;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the given matrix by multiplying all values by -1.
|
||||
/// </summary>
|
||||
/// <param name="value">The source matrix.</param>
|
||||
/// <returns>The negated matrix.</returns>
|
||||
public static Matrix3x2 Negate(Matrix3x2 value)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = -value.M11;
|
||||
result.M12 = -value.M12;
|
||||
result.M21 = -value.M21;
|
||||
result.M22 = -value.M22;
|
||||
result.M31 = -value.M31;
|
||||
result.M32 = -value.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds each matrix element in value1 with its corresponding element in value2.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the summed values.</returns>
|
||||
public static Matrix3x2 Add(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 + value2.M11;
|
||||
result.M12 = value1.M12 + value2.M12;
|
||||
result.M21 = value1.M21 + value2.M21;
|
||||
result.M22 = value1.M22 + value2.M22;
|
||||
result.M31 = value1.M31 + value2.M31;
|
||||
result.M32 = value1.M32 + value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts each matrix element in value2 from its corresponding element in value1.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the resulting values.</returns>
|
||||
public static Matrix3x2 Subtract(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 - value2.M11;
|
||||
result.M12 = value1.M12 - value2.M12;
|
||||
result.M21 = value1.M21 - value2.M21;
|
||||
result.M22 = value1.M22 - value2.M22;
|
||||
result.M31 = value1.M31 - value2.M31;
|
||||
result.M32 = value1.M32 - value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two matrices together and returns the resulting matrix.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The product matrix.</returns>
|
||||
public static Matrix3x2 Multiply(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
// First row
|
||||
result.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
result.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
result.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
result.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
// Third row
|
||||
result.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
|
||||
result.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Matrix4x4 ToMatrix4x4()
|
||||
{
|
||||
return new Matrix4x4(
|
||||
M11, M12, 0, 0,
|
||||
M21, M22, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
M31, M32, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales all elements in a matrix by the given scalar factor.
|
||||
/// </summary>
|
||||
/// <param name="value1">The source matrix.</param>
|
||||
/// <param name="value2">The scaling value to use.</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
public static Matrix3x2 Multiply(Matrix3x2 value1, float value2)
|
||||
{
|
||||
Matrix3x2 result;
|
||||
|
||||
result.M11 = value1.M11 * value2;
|
||||
result.M12 = value1.M12 * value2;
|
||||
result.M21 = value1.M21 * value2;
|
||||
result.M22 = value1.M22 * value2;
|
||||
result.M31 = value1.M31 * value2;
|
||||
result.M32 = value1.M32 * value2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the given matrix by multiplying all values by -1.
|
||||
/// </summary>
|
||||
/// <param name="value">The source matrix.</param>
|
||||
/// <returns>The negated matrix.</returns>
|
||||
public static Matrix3x2 operator -(Matrix3x2 value)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = -value.M11;
|
||||
m.M12 = -value.M12;
|
||||
m.M21 = -value.M21;
|
||||
m.M22 = -value.M22;
|
||||
m.M31 = -value.M31;
|
||||
m.M32 = -value.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds each matrix element in value1 with its corresponding element in value2.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the summed values.</returns>
|
||||
public static Matrix3x2 operator +(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 + value2.M11;
|
||||
m.M12 = value1.M12 + value2.M12;
|
||||
m.M21 = value1.M21 + value2.M21;
|
||||
m.M22 = value1.M22 + value2.M22;
|
||||
m.M31 = value1.M31 + value2.M31;
|
||||
m.M32 = value1.M32 + value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts each matrix element in value2 from its corresponding element in value1.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The matrix containing the resulting values.</returns>
|
||||
public static Matrix3x2 operator -(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 - value2.M11;
|
||||
m.M12 = value1.M12 - value2.M12;
|
||||
m.M21 = value1.M21 - value2.M21;
|
||||
m.M22 = value1.M22 - value2.M22;
|
||||
m.M31 = value1.M31 - value2.M31;
|
||||
m.M32 = value1.M32 - value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two matrices together and returns the resulting matrix.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>The product matrix.</returns>
|
||||
public static Matrix3x2 operator *(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
// First row
|
||||
m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21;
|
||||
m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22;
|
||||
|
||||
// Second row
|
||||
m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21;
|
||||
m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22;
|
||||
|
||||
// Third row
|
||||
m.M31 = value1.M31 * value2.M11 + value1.M32 * value2.M21 + value2.M31;
|
||||
m.M32 = value1.M31 * value2.M12 + value1.M32 * value2.M22 + value2.M32;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales all elements in a matrix by the given scalar factor.
|
||||
/// </summary>
|
||||
/// <param name="value1">The source matrix.</param>
|
||||
/// <param name="value2">The scaling value to use.</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
public static Matrix3x2 operator *(Matrix3x2 value1, float value2)
|
||||
{
|
||||
Matrix3x2 m;
|
||||
|
||||
m.M11 = value1.M11 * value2;
|
||||
m.M12 = value1.M12 * value2;
|
||||
m.M21 = value1.M21 * value2;
|
||||
m.M22 = value1.M22 * value2;
|
||||
m.M31 = value1.M31 * value2;
|
||||
m.M32 = value1.M32 * value2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given matrices are equal.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>True if the matrices are equal; False otherwise.</returns>
|
||||
public static bool operator ==(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && // Check diagonal element first for early out.
|
||||
value1.M12 == value2.M12 &&
|
||||
value1.M21 == value2.M21 &&
|
||||
value1.M31 == value2.M31 && value1.M32 == value2.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given matrices are not equal.
|
||||
/// </summary>
|
||||
/// <param name="value1">The first source matrix.</param>
|
||||
/// <param name="value2">The second source matrix.</param>
|
||||
/// <returns>True if the matrices are not equal; False if they are equal.</returns>
|
||||
public static bool operator !=(Matrix3x2 value1, Matrix3x2 value2)
|
||||
{
|
||||
return (value1.M11 != value2.M11 || value1.M12 != value2.M12 ||
|
||||
value1.M21 != value2.M21 || value1.M22 != value2.M22 ||
|
||||
value1.M31 != value2.M31 || value1.M32 != value2.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the matrix is equal to the other given matrix.
|
||||
/// </summary>
|
||||
/// <param name="other">The other matrix to test equality against.</param>
|
||||
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
|
||||
public bool Equals(Matrix3x2 other)
|
||||
{
|
||||
return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out.
|
||||
M12 == other.M12 &&
|
||||
M21 == other.M21 &&
|
||||
M31 == other.M31 && M32 == other.M32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a boolean indicating whether the given Object is equal to this matrix instance.
|
||||
/// </summary>
|
||||
/// <param name="obj">The Object to compare against.</param>
|
||||
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Matrix3x2)
|
||||
{
|
||||
return Equals((Matrix3x2)obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a String representing this matrix instance.
|
||||
/// </summary>
|
||||
/// <returns>The string representation.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
CultureInfo ci = CultureInfo.CurrentCulture;
|
||||
return String.Format(ci, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}",
|
||||
M11.ToString(ci), M12.ToString(ci),
|
||||
M21.ToString(ci), M22.ToString(ci),
|
||||
M31.ToString(ci), M32.ToString(ci));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>The hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return M11.GetHashCode() + M12.GetHashCode() +
|
||||
M21.GetHashCode() + M22.GetHashCode() +
|
||||
M31.GetHashCode() + M32.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
#region License
|
||||
|
||||
/* MoonWorks - Game Development Framework
|
||||
* Copyright 2021 Evan Hemsley
|
||||
*/
|
||||
|
||||
/* Derived from code by Ethan Lee (Copyright 2009-2021).
|
||||
* Released under the Microsoft Public License.
|
||||
* See fna.LICENSE for details.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#region Using Statements
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml;
|
||||
#endregion
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
internal static class MoonWorksDllMap
|
||||
{
|
||||
#region Private Static Variables
|
||||
|
||||
private static Dictionary<string, string> mapDictionary
|
||||
= new Dictionary<string, string>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Static Methods
|
||||
|
||||
private static string GetPlatformName()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return "windows";
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return "osx";
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return "linux";
|
||||
}
|
||||
else if (OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
return "freebsd";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Maybe this platform statically links?
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DllImportResolver Callback Method
|
||||
|
||||
private static IntPtr MapAndLoad(
|
||||
string libraryName,
|
||||
Assembly assembly,
|
||||
DllImportSearchPath? dllImportSearchPath
|
||||
)
|
||||
{
|
||||
string mappedName;
|
||||
if (!mapDictionary.TryGetValue(libraryName, out mappedName))
|
||||
{
|
||||
mappedName = libraryName;
|
||||
}
|
||||
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Module Initializer
|
||||
|
||||
[ModuleInitializer]
|
||||
public static void Init()
|
||||
{
|
||||
// Ignore NativeAOT platforms since they don't perform dynamic loading.
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the platform and architecture
|
||||
string os = GetPlatformName();
|
||||
string cpu = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
|
||||
string wordsize = (IntPtr.Size * 8).ToString();
|
||||
|
||||
// Get the path to the assembly
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyPath = System.AppContext.BaseDirectory;
|
||||
|
||||
// Locate the config file
|
||||
string xmlPath = Path.Combine(
|
||||
assemblyPath,
|
||||
"MoonWorks.dll.config"
|
||||
);
|
||||
if (!File.Exists(xmlPath))
|
||||
{
|
||||
// Let's hope for the best...
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the XML
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.Load(xmlPath);
|
||||
|
||||
// The NativeLibrary API cannot remap function names. :(
|
||||
if (xmlDoc.GetElementsByTagName("dllentry").Count > 0)
|
||||
{
|
||||
string msg = "Function remapping is not supported by .NET Core. Ignoring dllentry elements...";
|
||||
Console.WriteLine(msg);
|
||||
|
||||
// Log it in the debugger for non-console apps.
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
Debug.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the XML into a mapping dictionary
|
||||
foreach (XmlNode node in xmlDoc.GetElementsByTagName("dllmap"))
|
||||
{
|
||||
XmlAttribute attribute;
|
||||
|
||||
// Check the OS
|
||||
attribute = node.Attributes["os"];
|
||||
if (attribute != null)
|
||||
{
|
||||
bool containsOS = attribute.Value.Contains(os);
|
||||
bool invert = attribute.Value.StartsWith("!");
|
||||
if ((!containsOS && !invert) || (containsOS && invert))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the CPU
|
||||
attribute = node.Attributes["cpu"];
|
||||
if (attribute != null)
|
||||
{
|
||||
bool containsCPU = attribute.Value.Contains(cpu);
|
||||
bool invert = attribute.Value.StartsWith("!");
|
||||
if ((!containsCPU && !invert) || (containsCPU && invert))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the word size
|
||||
attribute = node.Attributes["wordsize"];
|
||||
if (attribute != null)
|
||||
{
|
||||
bool containsWordsize = attribute.Value.Contains(wordsize);
|
||||
bool invert = attribute.Value.StartsWith("!");
|
||||
if ((!containsWordsize && !invert) || (containsWordsize && invert))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the existence of 'dll' and 'target' attributes
|
||||
XmlAttribute dllAttribute = node.Attributes["dll"];
|
||||
XmlAttribute targetAttribute = node.Attributes["target"];
|
||||
if (dllAttribute == null || targetAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the actual library names
|
||||
string oldLib = dllAttribute.Value;
|
||||
string newLib = targetAttribute.Value;
|
||||
if (string.IsNullOrWhiteSpace(oldLib) || string.IsNullOrWhiteSpace(newLib))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't allow duplicates
|
||||
if (mapDictionary.ContainsKey(oldLib))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mapDictionary.Add(oldLib, newLib);
|
||||
}
|
||||
|
||||
// Set the resolver callback for our native assemblies
|
||||
NativeLibrary.SetDllImportResolver(assembly, MapAndLoad);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace MoonWorks
|
||||
{
|
||||
public enum ScreenMode
|
||||
{
|
||||
Fullscreen,
|
||||
BorderlessFullscreen,
|
||||
Windowed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// This class takes in a filename for AV1 data in .obu (open bitstream unit) format
|
||||
/// </summary>
|
||||
public unsafe class VideoAV1 : GraphicsResource
|
||||
{
|
||||
public string Filename { get; }
|
||||
|
||||
// "double buffering" so we can loop without a stutter
|
||||
internal VideoAV1Stream StreamA { get; }
|
||||
internal VideoAV1Stream StreamB { get; }
|
||||
|
||||
public int Width => width;
|
||||
public int Height => height;
|
||||
public double FramesPerSecond { get; set; }
|
||||
public Dav1dfile.PixelLayout PixelLayout => pixelLayout;
|
||||
public int UVWidth { get; }
|
||||
public int UVHeight { get; }
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private Dav1dfile.PixelLayout pixelLayout;
|
||||
|
||||
/// <summary>
|
||||
/// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate.
|
||||
/// </summary>
|
||||
public VideoAV1(GraphicsDevice device, string filename, double framesPerSecond) : base(device)
|
||||
{
|
||||
if (!File.Exists(filename))
|
||||
{
|
||||
throw new ArgumentException("Video file not found!");
|
||||
}
|
||||
|
||||
if (Dav1dfile.df_fopen(filename, out var handle) == 0)
|
||||
{
|
||||
throw new Exception("Failed to open video file!");
|
||||
}
|
||||
|
||||
Dav1dfile.df_videoinfo(handle, out width, out height, out pixelLayout);
|
||||
Dav1dfile.df_close(handle);
|
||||
|
||||
if (pixelLayout == Dav1dfile.PixelLayout.I420)
|
||||
{
|
||||
UVWidth = Width / 2;
|
||||
UVHeight = Height / 2;
|
||||
}
|
||||
else if (pixelLayout == Dav1dfile.PixelLayout.I422)
|
||||
{
|
||||
UVWidth = Width / 2;
|
||||
UVHeight = Height;
|
||||
}
|
||||
else if (pixelLayout == Dav1dfile.PixelLayout.I444)
|
||||
{
|
||||
UVWidth = width;
|
||||
UVHeight = height;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unrecognized YUV format!");
|
||||
}
|
||||
|
||||
FramesPerSecond = framesPerSecond;
|
||||
|
||||
Filename = filename;
|
||||
|
||||
StreamA = new VideoAV1Stream(device, this);
|
||||
StreamB = new VideoAV1Stream(device, this);
|
||||
}
|
||||
|
||||
// NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
StreamA.Dispose();
|
||||
StreamB.Dispose();
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
internal class VideoAV1Stream : GraphicsResource
|
||||
{
|
||||
public IntPtr Handle => handle;
|
||||
IntPtr handle;
|
||||
|
||||
public bool Ended => Dav1dfile.df_eos(Handle) == 1;
|
||||
|
||||
public IntPtr yDataHandle;
|
||||
public IntPtr uDataHandle;
|
||||
public IntPtr vDataHandle;
|
||||
public uint yDataLength;
|
||||
public uint uvDataLength;
|
||||
public uint yStride;
|
||||
public uint uvStride;
|
||||
|
||||
public bool FrameDataUpdated { get; set; }
|
||||
|
||||
public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device)
|
||||
{
|
||||
if (Dav1dfile.df_fopen(video.Filename, out handle) == 0)
|
||||
{
|
||||
throw new Exception("Failed to open video file!");
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
Dav1dfile.df_reset(Handle);
|
||||
ReadNextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadNextFrame()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (!Ended)
|
||||
{
|
||||
if (Dav1dfile.df_readvideo(
|
||||
Handle,
|
||||
1,
|
||||
out var yDataHandle,
|
||||
out var uDataHandle,
|
||||
out var vDataHandle,
|
||||
out var yDataLength,
|
||||
out var uvDataLength,
|
||||
out var yStride,
|
||||
out var uvStride) == 1
|
||||
) {
|
||||
this.yDataHandle = yDataHandle;
|
||||
this.uDataHandle = uDataHandle;
|
||||
this.vDataHandle = vDataHandle;
|
||||
this.yDataLength = yDataLength;
|
||||
this.uvDataLength = uvDataLength;
|
||||
this.yStride = yStride;
|
||||
this.uvStride = uvStride;
|
||||
|
||||
FrameDataUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
Dav1dfile.df_close(Handle);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using MoonWorks.Graphics;
|
||||
|
||||
namespace MoonWorks.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// A structure for continuous decoding of AV1 videos and rendering them into a texture.
|
||||
/// </summary>
|
||||
public unsafe class VideoPlayer : GraphicsResource
|
||||
{
|
||||
public Texture RenderTexture { get; private set; } = null;
|
||||
public VideoState State { get; private set; } = VideoState.Stopped;
|
||||
public bool Loop { get; set; }
|
||||
public float PlaybackSpeed { get; set; } = 1;
|
||||
|
||||
private VideoAV1 Video = null;
|
||||
private VideoAV1Stream CurrentStream = null;
|
||||
|
||||
private Task ReadNextFrameTask;
|
||||
private Task ResetStreamATask;
|
||||
private Task ResetStreamBTask;
|
||||
|
||||
private GraphicsDevice GraphicsDevice;
|
||||
private Texture yTexture = null;
|
||||
private Texture uTexture = null;
|
||||
private Texture vTexture = null;
|
||||
private Sampler LinearSampler;
|
||||
|
||||
private int currentFrame;
|
||||
|
||||
private Stopwatch timer;
|
||||
private double lastTimestamp;
|
||||
private double timeElapsed;
|
||||
|
||||
public VideoPlayer(GraphicsDevice device) : base(device)
|
||||
{
|
||||
GraphicsDevice = device;
|
||||
|
||||
LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp);
|
||||
|
||||
timer = new Stopwatch();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a VideoAV1 for decoding and rendering.
|
||||
/// </summary>
|
||||
/// <param name="video"></param>
|
||||
public void Load(VideoAV1 video)
|
||||
{
|
||||
if (Video != video)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (RenderTexture == null)
|
||||
{
|
||||
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height);
|
||||
}
|
||||
|
||||
if (yTexture == null)
|
||||
{
|
||||
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height);
|
||||
}
|
||||
|
||||
if (uTexture == null)
|
||||
{
|
||||
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
|
||||
}
|
||||
|
||||
if (vTexture == null)
|
||||
{
|
||||
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
|
||||
}
|
||||
|
||||
if (video.Width != RenderTexture.Width || video.Height != RenderTexture.Height)
|
||||
{
|
||||
RenderTexture.Dispose();
|
||||
RenderTexture = CreateRenderTexture(GraphicsDevice, video.Width, video.Height);
|
||||
}
|
||||
|
||||
if (video.Width != yTexture.Width || video.Height != yTexture.Height)
|
||||
{
|
||||
yTexture.Dispose();
|
||||
yTexture = CreateSubTexture(GraphicsDevice, video.Width, video.Height);
|
||||
}
|
||||
|
||||
if (video.UVWidth != uTexture.Width || video.UVHeight != uTexture.Height)
|
||||
{
|
||||
uTexture.Dispose();
|
||||
uTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
|
||||
}
|
||||
|
||||
if (video.UVWidth != vTexture.Width || video.UVHeight != vTexture.Height)
|
||||
{
|
||||
vTexture.Dispose();
|
||||
vTexture = CreateSubTexture(GraphicsDevice, video.UVWidth, video.UVHeight);
|
||||
}
|
||||
|
||||
Video = video;
|
||||
|
||||
InitializeDav1dStream();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts playing back and decoding the loaded video.
|
||||
/// </summary>
|
||||
public void Play()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (State == VideoState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timer.Start();
|
||||
|
||||
State = VideoState.Playing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses playback and decoding of the currently playing video.
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (State != VideoState.Playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
|
||||
State = VideoState.Paused;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops and resets decoding of the currently playing video.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (Video == null) { return; }
|
||||
|
||||
if (State == VideoState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
lastTimestamp = 0;
|
||||
timeElapsed = 0;
|
||||
|
||||
InitializeDav1dStream();
|
||||
|
||||
State = VideoState.Stopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unloads the currently playing video.
|
||||
/// </summary>
|
||||
public void Unload()
|
||||
{
|
||||
Stop();
|
||||
ResetStreamATask?.Wait();
|
||||
ResetStreamBTask?.Wait();
|
||||
Video = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the video data into RenderTexture.
|
||||
/// </summary>
|
||||
public void Render()
|
||||
{
|
||||
if (Video == null || State == VideoState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed;
|
||||
lastTimestamp = timer.Elapsed.TotalMilliseconds;
|
||||
|
||||
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
|
||||
if (thisFrame > currentFrame)
|
||||
{
|
||||
if (CurrentStream.FrameDataUpdated)
|
||||
{
|
||||
UpdateRenderTexture();
|
||||
CurrentStream.FrameDataUpdated = false;
|
||||
}
|
||||
|
||||
currentFrame = thisFrame;
|
||||
ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame);
|
||||
ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
if (CurrentStream.Ended)
|
||||
{
|
||||
timer.Stop();
|
||||
timer.Reset();
|
||||
|
||||
var task = Task.Run(CurrentStream.Reset);
|
||||
task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
if (CurrentStream == Video.StreamA)
|
||||
{
|
||||
ResetStreamATask = task;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetStreamBTask = task;
|
||||
}
|
||||
|
||||
if (Loop)
|
||||
{
|
||||
// Start over on the next stream!
|
||||
CurrentStream = (CurrentStream == Video.StreamA) ? Video.StreamB : Video.StreamA;
|
||||
currentFrame = -1;
|
||||
timer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
State = VideoState.Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRenderTexture()
|
||||
{
|
||||
lock (CurrentStream)
|
||||
{
|
||||
var commandBuffer = GraphicsDevice.AcquireCommandBuffer();
|
||||
|
||||
commandBuffer.SetTextureDataYUV(
|
||||
yTexture,
|
||||
uTexture,
|
||||
vTexture,
|
||||
CurrentStream.yDataHandle,
|
||||
CurrentStream.uDataHandle,
|
||||
CurrentStream.vDataHandle,
|
||||
CurrentStream.yDataLength,
|
||||
CurrentStream.uvDataLength,
|
||||
CurrentStream.yStride,
|
||||
CurrentStream.uvStride
|
||||
);
|
||||
|
||||
commandBuffer.BeginRenderPass(
|
||||
new ColorAttachmentInfo(RenderTexture, Color.Black)
|
||||
);
|
||||
|
||||
commandBuffer.BindGraphicsPipeline(GraphicsDevice.VideoPipeline);
|
||||
commandBuffer.BindFragmentSamplers(
|
||||
new TextureSamplerBinding(yTexture, LinearSampler),
|
||||
new TextureSamplerBinding(uTexture, LinearSampler),
|
||||
new TextureSamplerBinding(vTexture, LinearSampler)
|
||||
);
|
||||
|
||||
commandBuffer.DrawPrimitives(0, 1, 0, 0);
|
||||
|
||||
commandBuffer.EndRenderPass();
|
||||
|
||||
GraphicsDevice.Submit(commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static Texture CreateRenderTexture(GraphicsDevice graphicsDevice, int width, int height)
|
||||
{
|
||||
return Texture.CreateTexture2D(
|
||||
graphicsDevice,
|
||||
(uint) width,
|
||||
(uint) height,
|
||||
TextureFormat.R8G8B8A8,
|
||||
TextureUsageFlags.ColorTarget | TextureUsageFlags.Sampler
|
||||
);
|
||||
}
|
||||
|
||||
private static Texture CreateSubTexture(GraphicsDevice graphicsDevice, int width, int height)
|
||||
{
|
||||
return Texture.CreateTexture2D(
|
||||
graphicsDevice,
|
||||
(uint) width,
|
||||
(uint) height,
|
||||
TextureFormat.R8,
|
||||
TextureUsageFlags.Sampler
|
||||
);
|
||||
}
|
||||
|
||||
private void InitializeDav1dStream()
|
||||
{
|
||||
ReadNextFrameTask?.Wait();
|
||||
|
||||
ResetStreamATask = Task.Run(Video.StreamA.Reset);
|
||||
ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
ResetStreamBTask = Task.Run(Video.StreamB.Reset);
|
||||
ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted);
|
||||
|
||||
CurrentStream = Video.StreamA;
|
||||
currentFrame = -1;
|
||||
}
|
||||
|
||||
private static void HandleTaskException(Task task)
|
||||
{
|
||||
if (task.Exception.InnerException is not TaskCanceledException)
|
||||
{
|
||||
throw task.Exception;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Unload();
|
||||
|
||||
RenderTexture?.Dispose();
|
||||
yTexture?.Dispose();
|
||||
uTexture?.Dispose();
|
||||
vTexture?.Dispose();
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace MoonWorks.Video
|
||||
{
|
||||
public enum VideoState
|
||||
{
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MoonWorks.Graphics;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a window in the client operating system. <br/>
|
||||
/// Every Game has a MainWindow automatically. <br/>
|
||||
/// You can create additional Windows if you desire. They must be Claimed by the GraphicsDevice to be rendered to.
|
||||
/// </summary>
|
||||
public class Window : IDisposable
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
public ScreenMode ScreenMode { get; private set; }
|
||||
public uint Width { get; private set; }
|
||||
public uint Height { get; private set; }
|
||||
internal Texture SwapchainTexture { get; set; }
|
||||
|
||||
public bool Claimed { get; internal set; }
|
||||
public MoonWorks.Graphics.TextureFormat SwapchainFormat { get; internal set; }
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
private static Dictionary<uint, Window> idLookup = new Dictionary<uint, Window>();
|
||||
|
||||
private System.Action<uint, uint> SizeChangeCallback = null;
|
||||
|
||||
public Window(WindowCreateInfo windowCreateInfo, SDL.SDL_WindowFlags flags)
|
||||
{
|
||||
if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
|
||||
{
|
||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessFullscreen)
|
||||
{
|
||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
if (windowCreateInfo.SystemResizable)
|
||||
{
|
||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
if (windowCreateInfo.StartMaximized)
|
||||
{
|
||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED;
|
||||
}
|
||||
|
||||
ScreenMode = windowCreateInfo.ScreenMode;
|
||||
|
||||
SDL.SDL_GetDesktopDisplayMode(0, out var displayMode);
|
||||
|
||||
Handle = SDL.SDL_CreateWindow(
|
||||
windowCreateInfo.WindowTitle,
|
||||
SDL.SDL_WINDOWPOS_CENTERED,
|
||||
SDL.SDL_WINDOWPOS_CENTERED,
|
||||
windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowWidth : displayMode.w,
|
||||
windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowHeight : displayMode.h,
|
||||
flags
|
||||
);
|
||||
|
||||
/* Requested size might be different in fullscreen, so let's just get the area */
|
||||
SDL.SDL_GetWindowSize(Handle, out var width, out var height);
|
||||
Width = (uint) width;
|
||||
Height = (uint) height;
|
||||
|
||||
idLookup.Add(SDL.SDL_GetWindowID(Handle), this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the ScreenMode of this window.
|
||||
/// </summary>
|
||||
public void SetScreenMode(ScreenMode screenMode)
|
||||
{
|
||||
SDL.SDL_WindowFlags windowFlag = 0;
|
||||
|
||||
if (screenMode == ScreenMode.Fullscreen)
|
||||
{
|
||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else if (screenMode == ScreenMode.BorderlessFullscreen)
|
||||
{
|
||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
|
||||
|
||||
if (screenMode == ScreenMode.Windowed)
|
||||
{
|
||||
SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
|
||||
ScreenMode = screenMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the window. <br/>
|
||||
/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
public void SetWindowSize(uint width, uint height)
|
||||
{
|
||||
SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
if (ScreenMode == ScreenMode.Windowed)
|
||||
{
|
||||
SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Window Lookup(uint windowID)
|
||||
{
|
||||
return idLookup.ContainsKey(windowID) ? idLookup[windowID] : null;
|
||||
}
|
||||
|
||||
internal void Show()
|
||||
{
|
||||
SDL.SDL_ShowWindow(Handle);
|
||||
}
|
||||
|
||||
internal void HandleSizeChange(uint width, uint height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
if (SizeChangeCallback != null)
|
||||
{
|
||||
SizeChangeCallback(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You can specify a method to run when the window size changes.
|
||||
/// </summary>
|
||||
public void RegisterSizeChangeCallback(System.Action<uint, uint> sizeChangeCallback)
|
||||
{
|
||||
SizeChangeCallback = sizeChangeCallback;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// dispose managed state (managed objects)
|
||||
}
|
||||
|
||||
idLookup.Remove(SDL.SDL_GetWindowID(Handle));
|
||||
SDL.SDL_DestroyWindow(Handle);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~Window()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
using System;
|
||||
using SDL2;
|
||||
|
||||
namespace MoonWorks.Window
|
||||
{
|
||||
public class OSWindow
|
||||
{
|
||||
internal IntPtr Handle { get; }
|
||||
public ScreenMode ScreenMode { get; }
|
||||
|
||||
public OSWindow(WindowCreateInfo windowCreateInfo)
|
||||
{
|
||||
var windowFlags = SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
|
||||
|
||||
if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
|
||||
{
|
||||
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessWindow)
|
||||
{
|
||||
windowFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
ScreenMode = windowCreateInfo.ScreenMode;
|
||||
|
||||
Handle = SDL.SDL_CreateWindow(
|
||||
windowCreateInfo.WindowTitle,
|
||||
SDL.SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL.SDL_WINDOWPOS_UNDEFINED,
|
||||
(int)windowCreateInfo.WindowWidth,
|
||||
(int)windowCreateInfo.WindowHeight,
|
||||
windowFlags
|
||||
);
|
||||
}
|
||||
|
||||
public void ChangeScreenMode(ScreenMode screenMode)
|
||||
{
|
||||
SDL.SDL_WindowFlags windowFlag = 0;
|
||||
|
||||
if (screenMode == ScreenMode.Fullscreen)
|
||||
{
|
||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
else if (screenMode == ScreenMode.BorderlessWindow)
|
||||
{
|
||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the window.
|
||||
/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
public void SetWindowSize(uint width, uint height)
|
||||
{
|
||||
SDL.SDL_SetWindowSize(Handle, (int)width, (int)height);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace MoonWorks.Window
|
||||
{
|
||||
public enum ScreenMode
|
||||
{
|
||||
Fullscreen,
|
||||
BorderlessWindow,
|
||||
Windowed
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
namespace MoonWorks.Window
|
||||
{
|
||||
public struct WindowCreateInfo
|
||||
{
|
||||
public string WindowTitle;
|
||||
public uint WindowWidth;
|
||||
public uint WindowHeight;
|
||||
public ScreenMode ScreenMode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
namespace MoonWorks
|
||||
{
|
||||
/// <summary>
|
||||
/// All the information required for window creation.
|
||||
/// </summary>
|
||||
public struct WindowCreateInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the window that will be displayed in the operating system.
|
||||
/// </summary>
|
||||
public string WindowTitle;
|
||||
/// <summary>
|
||||
/// The width of the window.
|
||||
/// </summary>
|
||||
public uint WindowWidth;
|
||||
/// <summary>
|
||||
/// The height of the window.
|
||||
/// </summary>
|
||||
public uint WindowHeight;
|
||||
/// <summary>
|
||||
/// Specifies if the window will be created in windowed mode or a fullscreen mode.
|
||||
/// </summary>
|
||||
public ScreenMode ScreenMode;
|
||||
/// <summary>
|
||||
/// Specifies the presentation mode for the window. Roughly equivalent to V-Sync.
|
||||
/// </summary>
|
||||
public PresentMode PresentMode;
|
||||
/// <summary>
|
||||
/// Whether the window can be resized using the operating system's window dragging feature.
|
||||
/// </summary>
|
||||
public bool SystemResizable;
|
||||
/// <summary>
|
||||
/// Specifies if the window will open at the maximum desktop resolution.
|
||||
/// </summary>
|
||||
public bool StartMaximized;
|
||||
|
||||
public WindowCreateInfo(
|
||||
string windowTitle,
|
||||
uint windowWidth,
|
||||
uint windowHeight,
|
||||
ScreenMode screenMode,
|
||||
PresentMode presentMode,
|
||||
bool systemResizable = false,
|
||||
bool startMaximized = false
|
||||
) {
|
||||
WindowTitle = windowTitle;
|
||||
WindowWidth = windowWidth;
|
||||
WindowHeight = windowHeight;
|
||||
ScreenMode = screenMode;
|
||||
PresentMode = presentMode;
|
||||
SystemResizable = systemResizable;
|
||||
StartMaximized = startMaximized;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue