Compare commits

...

312 Commits
0.1.0 ... main

Author SHA1 Message Date
cosmonaut bb7e45b9a3 expose Params on ReverbEffect 2024-02-09 13:53:33 -08:00
cosmonaut 4cedf768f7 fix AttackHoldRelease timing 2024-02-03 00:40:29 -08:00
cosmonaut d986b3013f fix TextBatch index buffers being created as vertex buffers 2024-01-27 17:34:26 -08:00
cosmonaut 42e3ac91af Vertex instance input shortcut (#53)
Reviewed-on: #53
2024-01-27 03:44:19 +00:00
cosmonaut 0df2944ccf CommandBuffer is now a pooled class + more state validation 2024-01-18 12:27:34 -08:00
cosmonaut e50fb472b1 Debug mode sample count and depth assertions 2024-01-15 23:16:29 -08:00
cosmonaut df3f38a67b Debug mode bounds checks for buffer and texture upload 2024-01-15 22:19:59 -08:00
Evan Hemsley eaa9266521 remove Marshal call from KeyboardButton.CheckPressed 2023-12-28 18:54:21 -08:00
cosmonaut 4dbd5a2cbe MSDF font rendering + improved resource tracking (#52)
This is a major rewrite of the Font system. MoonWorks now uses MSDF font rendering, which allows high quality rendering of fonts at arbitrary sizes.

We now ship default embedded shader binaries for Video and Font. If you replace them with shader binaries of the same name located in your base directory, those will be used instead.

Many improvements have been made to resource tracking to prevent memory corruption, particularly on shutdown.

You must be careful not to leak AudioResource classes in particular, as there isn't much we can automatically do to recover from this without potentially crashing your game.

Reviewed-on: #52
2023-12-15 18:46:43 +00:00
cosmonaut 2e890fd696 Remove x64 specification 2023-12-13 11:19:27 -08:00
cosmonaut 385783a846 restructure audio cleanup 2023-12-08 16:33:52 -08:00
cosmonaut 450b08cbd8 Atomically call graphics resource destroy function 2023-12-08 16:07:38 -08:00
cosmonaut 528fb7ac7c fix potential heap corruption on audio shutdown 2023-12-08 15:06:17 -08:00
cosmonaut fcd08fe231 .NET 8 2023-11-21 15:23:52 -08:00
cosmonaut e961a18a83 UpdatingSourceVoice + warn on audio leak 2023-11-20 18:56:22 -08:00
cosmonaut 772a0378bb avoid calling Thread.Join from finalizer 2023-11-20 17:59:26 -08:00
cosmonaut 40fb313d12 change AudioResource WeakRef to GCHandle 2023-11-20 17:52:44 -08:00
cosmonaut a736ed031d clean up graphics resource code 2023-11-20 17:09:22 -08:00
cosmonaut b2a0ca3515 replace WeakReference with weak GCHandle 2023-11-20 15:18:06 -08:00
cosmonaut 36a88afe52 add no-op delegate to gamepad connect events 2023-11-14 11:19:21 -08:00
cosmonaut 6c93350f7f Add OnGamepadConnected and OnGamepadDisconnected events (#51)
Reviewed-on: #51
2023-11-13 19:10:29 +00:00
cosmonaut 352bb34f82 update dll config for major versions 2023-10-18 12:15:30 -07:00
Evan Hemsley de7d76c03d update dll.config for macOS 2023-10-16 11:20:55 -07:00
cosmonaut 18d92aeec8 remove Fix64 dependency on System.Random 2023-10-13 13:38:26 -07:00
cosmonaut e616b0fa62 resource management and logging improvements 2023-10-04 14:45:17 -07:00
cosmonaut 1d27a9e4a4 update README with API docs and Discord 2023-09-26 09:44:32 -07:00
cosmonaut 514a0bed29 update FAudio and SDL 2023-09-25 10:03:40 -07:00
cosmonaut 78252d1f6c doxygen generator config 2023-09-19 17:55:17 -07:00
cosmonaut daae1a34b9 fix a few more compile errors 2023-09-19 17:14:48 -07:00
cosmonaut 2e5657789c fix a Conversions issue 2023-09-19 17:11:14 -07:00
cosmonaut 0c76c568a4 document the Game class 2023-09-19 17:04:28 -07:00
cosmonaut abdcac1608 change AudioDevice constructor to internal 2023-09-19 17:04:03 -07:00
cosmonaut d8064862bf move PackedVector classes to MoonWorks.Graphics.PackedVector 2023-09-19 16:50:08 -07:00
cosmonaut b223c31c8b even more frame limiter clarification 2023-09-19 13:48:50 -07:00
cosmonaut dd79090028 update frame limiter docs some more 2023-09-19 13:47:19 -07:00
cosmonaut 653f90c29f correct frame limiter documentation mistake 2023-09-19 13:46:41 -07:00
cosmonaut 402c26131d fix erroneous GetData length warning 2023-09-19 13:40:48 -07:00
cosmonaut b026b9e81f add lots more doc comments 2023-09-19 13:19:41 -07:00
cosmonaut e0f05881b0 new MoonWorks.Graphics.Fence API 2023-09-18 23:18:21 -07:00
cosmonaut 7e18764942 add doc comments for the Input namespace 2023-09-14 11:23:04 -07:00
cosmonaut 1bff459be6 move default reverb params to a static var 2023-09-12 15:24:45 -07:00
cosmonaut be77e8bad1 add exponentiation functions to Fix64 2023-09-07 17:30:35 -07:00
cosmonaut 7f6b6a7bae fix voices not respecting the faux mastering voice 2023-08-10 10:46:19 -07:00
cosmonaut 1de3c73bb7 fix voices being created in the Playing state 2023-08-09 16:10:00 -07:00
cosmonaut e77c87c772 register new SourceVoices as active 2023-08-09 15:58:18 -07:00
cosmonaut 088e7c4b6f add debug check for zero length buffer copy 2023-08-07 10:12:46 -07:00
cosmonaut f298a5ec11 fix StreamingVoice loop behavior 2023-08-04 12:14:14 -07:00
cosmonaut 0cd2c799ee Audio Restructuring (#50)
This is a complete redesign of the MoonWorks Audio API.

Voices are the new major concept. All Voices can be configured with volume, pitch, filters, panning and reverb. SourceVoices take in AudioBuffers and use them to play sound. They contain their own playback state.

There are multiple kinds of SourceVoices:
TransientVoice: Used for short sound effects where the client will not be keeping track of a reference over multiple frames.
PersistentVoice: Used when the client needs to hold on to a Voice reference long-term.
StreamingVoice: Used for playing back AudioDataStreamable objects.
SoundSequence: Used to play back a series of AudioBuffers in sequence. They have a callback so that AudioBuffers can be added dynamically by the client.

SourceVoices are intended to be pooled. You can obtain one from the AudioDevice pool by calling AudioDevice.Obtain<T> where T is the type of SourceVoice you wish to obtain. When you call Return on the voice it will be returned to the pool. TransientVoices are automatically returned to the pool when they have finished playing back their AudioBuffer.

SourceVoices can send audio to SubmixVoices. This is a convenient way to manage categories of audio. For example the client could have a MusicSubmix that all music-related voices send to. Then the volume of all music can be changed at once without the client having to manage all the individual music voices.

By default all voices send audio to AudioDevice.MasteringVoice. This is also a SubmixVoice that can be controlled like any other voice.

AudioDataStreamable is used in conjunction with a StreamingVoice to play back streaming audio from an ogg or qoa file.

AudioDataWav, AudioDataOgg, and AudioDataQoa all have a static CreateBuffer method that can be used to create an AudioBuffer from an audio file.

Reviewed-on: #50
2023-08-03 19:54:02 +00:00
cosmonaut 81cd397013 fix AudioDevice crash on shutdown 2023-07-28 15:02:20 -07:00
cosmonaut e73c7ede55 rename SoundQueue to SoundSequence 2023-07-28 13:21:50 -07:00
cosmonaut 83f1cc24db rename OnBufferNeeded to OnSoundNeeded 2023-07-28 13:10:01 -07:00
cosmonaut 1d86d0c210 add SoundQueue 2023-07-28 13:02:50 -07:00
cosmonaut dbbd6540ab fix StaticSoundInstance.Pause not actually pausing 2023-06-30 11:51:20 -07:00
cosmonaut 74ae295036 fix audio thread race condition on StaticAudio.GetInstance 2023-06-29 15:01:22 -07:00
cosmonaut 36ddb03d8f only check video exception on thread fault 2023-06-29 12:14:46 -07:00
cosmonaut bf3ad0c8b0 add Game.ShowRuntimeError method 2023-06-29 11:30:32 -07:00
cosmonaut f761d4f76e re-throw exceptions from video thread 2023-06-29 11:30:16 -07:00
cosmonaut 4c731401ff fix vertical axis on axis buttons being backwards 2023-06-28 18:19:34 -07:00
cosmonaut 071518732e set axis button threshold default to 0.5 2023-06-28 17:17:57 -07:00
cosmonaut 1adb76d5c7 exception handlers for video decoder threads 2023-06-28 13:20:33 -07:00
cosmonaut 5ff7da927a try-catch inside AudioThreadMain 2023-06-28 13:20:18 -07:00
cosmonaut 0fd3365d1d update dll.config 2023-06-23 16:17:44 -07:00
cosmonaut affb592c15 add dav1dfile dependency to README 2023-06-19 16:19:36 -07:00
cosmonaut 7e79e4a11d fix validator warnings on Quit 2023-06-15 17:45:26 -07:00
cosmonaut fc0937b2ff fix crash caused by audio weak references 2023-06-14 18:22:49 -07:00
cosmonaut c83997609f fix some scenarios where video pointers should not be replaced 2023-06-13 19:18:23 -07:00
cosmonaut b65d4e391c public Fix64 RawValue 2023-06-12 09:53:44 -07:00
cosmonaut 56bab545ba add MouseButton.Button lookup 2023-06-09 18:15:15 -07:00
cosmonaut 2ae116c72b fix window dimensions when starting in fullscreen 2023-06-09 16:27:43 -07:00
cosmonaut bd3e70b096 make KeyboardButton.KeyCode public 2023-06-09 16:17:41 -07:00
cosmonaut b1fe7f96b2 recenter window on windowed mode change 2023-06-09 11:42:20 -07:00
cosmonaut 00366cc9d4 fix dav1dfile submodule 2023-06-07 15:35:07 -07:00
cosmonaut 3bc25bc3a1 remove theorafile from dll config 2023-06-07 15:29:45 -07:00
cosmonaut 496eb670ab AV1 Video instead of Theora (#49)
VideoPlayer now takes AV1 video instead of Ogg Theora. This brings a significant decode speed improvement. The decoder now also operates in a threaded manner, which should prevent runtime stalls when fetching video frames.

Reviewed-on: #49
2023-06-07 21:18:44 +00:00
cosmonaut 00f4bfdeae optimize StreamingSoundQoa.Create 2023-05-22 19:24:26 -07:00
cosmonaut adeba633e5 csproj tweaks to support app publish 2023-05-22 18:28:13 -07:00
cosmonaut 300ef9f88e fix unnecessary copy in PackFontRanges 2023-05-22 11:41:19 -07:00
cosmonaut 76684eaa33 fix memory leak in StreamingSoundQoa.Create 2023-05-11 19:45:07 -07:00
cosmonaut c037b4cb69 fix StaticSoundInstance race condition and state 2023-05-11 18:59:26 -07:00
cosmonaut 5df08727c1 Sound instancing rework 2023-05-11 17:56:40 -07:00
cosmonaut 537517afb9 remove buffer size log 2023-05-10 15:13:19 -07:00
cosmonaut bd405dfbf0 QOA Support (#48)
Reviewed-on: #48
2023-05-05 22:26:32 +00:00
cosmonaut 2d7bb24b5c rename Texture load methods for clarity 2023-04-19 00:50:59 -07:00
cosmonaut 0ea60a376b new image loader API 2023-04-19 00:41:18 -07:00
cosmonaut e3c2f0e119 fix controller hot swapping 2023-04-05 16:52:36 -07:00
cosmonaut 3584e670ee add array overloads to avoid explicit generic parameter 2023-04-05 12:40:34 -07:00
cosmonaut 5a2f7eadb8 change array param to span in Buffer.GetData 2023-04-05 11:32:12 -07:00
cosmonaut 1e3f04235e remove array parameters from API functions 2023-04-05 11:07:16 -07:00
cosmonaut dd06205399 stop static sound instance on Free 2023-04-05 01:18:13 -07:00
cosmonaut 1cf04a7279 assets stream data directly into unmanaged memory 2023-04-05 00:47:02 -07:00
cosmonaut 3bd435746b StaticSound external byte buffer constructor 2023-04-04 17:12:03 -07:00
cosmonaut 8134761e44 load images from memory + QOI support 2023-04-03 17:28:00 -07:00
cosmonaut 8209051a3c remove non-static Normalize + return identity on zero vector 2023-03-29 10:06:37 -07:00
cosmonaut 80f3711f4c add Quit method to Game 2023-03-16 16:48:52 -07:00
cosmonaut 3a6b73e637 add int clamp to MathHelper 2023-03-14 14:42:13 -07:00
cosmonaut 12e7e6b9c1 add Color.FromHSV 2023-03-09 15:14:16 -08:00
cosmonaut 455f4048df MathHelper.Quantize uses round instead of floor 2023-03-09 13:52:45 -08:00
cosmonaut 1f0e3b5040 Audio Improvements (#47)
- Audio is now processed on a background thread instead of the main thread
- Audio tick rate is now ~200Hz
- MoonWorks.Math.Easings class completely rewritten to be easier to understand and use
- SoundInstance properties no longer call into FAudio unless the value actually changed
- SoundInstance property values can now be interpolated over time (tweens)
- SoundInstance tweens can be delayed
- SoundInstance sets a sane filter frequency default when switching filter type
- StreamingSound classes can be designated to update automatically on the audio thread or manually
- StreamingSound buffer consumption should now set Stopped state in a more sane way
- Added ReverbEffect, which creates a submix voice for a reverb effect
- SoundInstance can apply a ReverbEffect, which enables the Reverb property
- Audio resource tracking improvements
- Some tweaks to VideoPlayer to make its behavior more consistent

Reviewed-on: #47
2023-03-07 23:28:57 +00:00
cosmonaut f8b14ea94f add ReverbEffect 2023-03-01 17:47:09 -08:00
cosmonaut 472da0edd2 add Hidden property to Mouse 2023-03-01 13:47:25 -08:00
cosmonaut bd825b6c91 allow pushing raw uniform data 2023-02-23 16:59:34 -08:00
cosmonaut 515c2ebbca fix controller double open 2023-02-23 16:59:19 -08:00
cosmonaut 86322e9373 log controller open errors 2023-02-21 16:00:16 -08:00
cosmonaut e9aacb44da Add synchronized audio playback mechanism 2023-02-16 15:12:35 -08:00
cosmonaut 5baa1d7b40 Fix video shader base path lookup 2023-02-08 12:44:36 -08:00
cosmonaut f673803c37 FAudio 23.02 2023-02-07 12:31:37 -08:00
cosmonaut 0f78cd1a0c Refresh 1.11.0 2023-02-07 12:28:52 -08:00
cosmonaut 36ce74b58a tweak video shader filenames 2023-02-03 15:07:32 -08:00
cosmonaut 40d12357c0 Remove MoonWorks.Collision (#46)
After months of tweaking and refactoring I have realized that collision is like rendering - it's so fundamental to the structure of your game that making broad decisions about how it should work from a library level is too restrictive and difficult to optimize. Anyone skilled enough to use MoonWorks should be easily able to roll their own collision detection.

Reviewed-on: #46
2023-02-03 19:51:36 +00:00
evan e52fe60657 update RefreshCS 2023-01-31 12:29:17 -08:00
TheSpydog b39526ca90 Textures now have a sample count, not render passes (#45)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #45
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2023-01-31 20:27:26 +00:00
TheSpydog 88d9119830 Remove warning from DrawInstancedPrimitives doc comment (#44)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #44
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2023-01-24 00:14:46 +00:00
TheSpydog 05de9a4066 Make video shaders optional and search for them in the root output directory (#42)
Whenever the video shaders change, they can be rebuilt with refreshc and distributed alongside the moonlibs.

Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #42
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2023-01-21 23:37:01 +00:00
TheSpydog b1d30a9e6c Remove isFixed param from AcquireCommandBuffer call (#41)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #41
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2023-01-14 18:04:53 +00:00
TheSpydog 030745361b Fix BC7 loading for textures without DDSD_CAPS/FMT (#40)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #40
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2023-01-07 01:46:59 +00:00
cosmonaut c43df10c2a vertex binding API improvements 2023-01-05 13:41:48 -08:00
cosmonaut 95981f0f03 rework vertex input state creation to avoid reflection 2023-01-04 17:34:01 -08:00
cosmonaut 703b694bf6 Refresh 1.10.0 2023-01-04 11:35:31 -08:00
cosmonaut fe561b61ef update libs 2023-01-04 11:34:54 -08:00
TheSpydog 230c1b41b4 Add IndirectDrawCommand struct (#39)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #39
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2023-01-04 18:44:56 +00:00
cosmonaut 1fb3e5adf0 update RefreshCS 2022-12-28 19:24:08 -08:00
TheSpydog 6f8858c8b7 RasterizerState / BlendFactor ABI break (#38)
Updates the APIs to match [this Refresh PR](MoonsideGames/Refresh#27).

Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #38
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-12-29 03:22:47 +00:00
TheSpydog f96298f991 Add validation checks for AcquireSwapchainTexture and BindVertexBuffers (#37)
This replaces the Refresh-side check for window claim status in AcquireSwapchainTexture, and adds validation to ensure that BindVertexBuffers is not called before BindGraphicsPipeline.

Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #37
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-12-29 03:18:36 +00:00
cosmonaut d76633bdfc change sizeof calls to Marshal.SizeOf in interop situations 2022-12-13 19:01:40 -08:00
cosmonaut debb76f62a validate scissor dimensions 2022-12-13 16:09:32 -08:00
cosmonaut ca61e94b13 optimize mouse update 2022-12-13 15:13:43 -08:00
cosmonaut 36e6c6f332 optimize AcquireSwapchainTexture 2022-12-13 00:52:35 -08:00
cosmonaut 916962da6c garbage collection optimizations 2022-12-13 00:34:16 -08:00
cosmonaut cfd52b00bd add FIXME note for spatial hash boxing 2022-12-08 13:29:04 -08:00
cosmonaut b0e1ad3cf8 add note about disposing swapchain texture 2022-12-01 13:20:11 -08:00
cosmonaut 2c4e1b972a .NET 7 2022-11-30 10:01:09 -08:00
cosmonaut 751b8310ce Fix crash when no audio device found 2022-11-30 09:43:49 -08:00
cosmonaut 474b8fe37d only check depth format if there is a depth attachment 2022-11-29 13:06:03 -08:00
cosmonaut bc41d2c079 add IsIdle and IsUp to Keyboard 2022-11-28 10:19:03 -08:00
cosmonaut d5ddd44bd3 add IsIdle and IsUp to VirtualButton 2022-11-28 00:48:48 -08:00
cosmonaut de9d13757a distinguish between idle and released on buttons 2022-11-27 12:41:09 -08:00
cosmonaut ca9fb45536 fix spacing 2022-11-18 10:34:32 -08:00
cosmonaut faf81bceed add support for mouse X1 and X2 buttons (#36)
Reviewed-on: #36
2022-11-18 18:33:15 +00:00
cosmonaut fa992652b1 fix mouse button updating twice 2022-11-17 18:16:26 -08:00
marpe c4bae77408 Fix infinite recursion on certain Easing functions 2022-11-17 14:40:24 -08:00
cosmonaut ecf1a8ed55 Optimize CommandBuffer (#35)
- remove params methods in favor of overloads and last-resort `in Span<T>` overloads
- add `in` to params that take structs to reduce unnecessary copying
- Buffer can now implicitly cast to BufferBinding
- add render pass / graphics pipeline format match validation

Reviewed-on: #35
2022-11-17 20:35:21 +00:00
cosmonaut 970517c3c2 add window size change callback support 2022-11-15 22:53:37 -08:00
cosmonaut f3c5bbf902 rename BorderlessWindow to BorderlessFullscreen 2022-11-15 22:49:07 -08:00
cosmonaut 02b20f79f5 allow sampler binding calls to take array segment 2022-11-15 17:53:30 -08:00
TheSpydog c2ef78d136 Add validation check that all work group dimension counts are >= 1 (#34)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #34
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-11-14 18:21:03 +00:00
TheSpydog 5533eeb2fd Fix bad pointer arithmetic in SetBufferData (#33)
Fixes a bug where SetBufferData was writing garbage to the buffer if `startElement` was non-zero.

Rather than fixing the pointer addition (it should have been `ptr + startElement`) I opted to remove it and instead pass the index explicitly when grabbing the address of the array element. I think that's a little easier to understand, and it's slightly safer too -- if you pass a startElement beyond the array bounds it will now throw an IndexOutOfRangeException instead of silently reading from outside the array bounds.

Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #33
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-11-12 03:42:35 +00:00
cosmonaut 934f3fd623 Refresh 1.9.0 2022-11-09 12:14:33 -08:00
TheSpydog 02b0c12ad8 Bunch of Refresh API changes, plus more validation (#31)
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-11-09 18:49:53 +00:00
TheSpydog 1b38f8606b Added more graphics validation, misc api tweaks (#30)
* Refactored render pass attachment validation to avoid copy-pasting the asserts
* Added validation for texture usage flags and null textures/samplers
* Added an exception for when GraphicsPipeline/ComputePipeline creation fails
* Changed TextureSamplerBinding so that it holds references to the Texture and Sampler classes, rather than just their handles
* Removed the CommandBuffer.BindVertex/FragmentSamplers overloads that took a length parameter

Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #30
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-11-08 19:29:05 +00:00
cosmonaut db38ada410 update dependency libraries 2022-11-01 16:33:26 -07:00
cosmonaut bd98ae8441 fix validation error when TextBatch is empty 2022-10-24 17:22:24 -07:00
cosmonaut 59190e619d fix Mouse.Wheel calculation 2022-10-21 11:39:06 -07:00
cosmonaut ea86212199 add AudioDevice.MasteringVolume property 2022-10-20 15:00:25 -07:00
cosmonaut 66c6ceec04 hide main window until Game.Run is called 2022-09-30 13:03:05 -07:00
cosmonaut dfddc24d0e add PresentMode to WindowCreateInfo 2022-09-30 13:02:51 -07:00
cosmonaut b66e077376 GraphicsDevice throw if backend invalid 2022-09-29 19:21:47 -07:00
cosmonaut 07c0b1b9a2 Window API revision + Framerate limiter (#27)
- add `Backend` enum
- `GraphicsDevice` can now take a preferred backend at creation

- add `GraphicsDevice.ClaimWindow`
- add `GraphicsDevice.UnclaimWindow`
- add `GraphicsDevice.SetPresentMode`

- add `Window.Claimed`
- add `Window.SwapchainFormat`
- fix certain odd behaviors in multi-window scenarios

- rename `FramerateSettings` to `FrameLimiterSettings`
- add `Game.SetFrameLimiter`

Reviewed-on: #27
2022-09-29 22:22:50 +00:00
TheSpydog 3ffdf8a929 Specify readonly access when creating ShaderModule stream (#26)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #26
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-09-28 23:52:29 +00:00
cosmonaut bace9f570d add Fix64.Step 2022-09-22 20:17:13 -07:00
cosmonaut 32f80282a0 clean up Video.cs 2022-09-21 15:16:34 -07:00
cosmonaut 23252a149f ignore currently loaded video on VideoPlayer.Load 2022-09-20 11:31:27 -07:00
cosmonaut 80f19e4521 Refresh 1.7.0 2022-09-13 13:58:09 -07:00
cosmonaut 7ea9a6aea3 bump FAudio version 2022-09-07 13:11:24 -07:00
cosmonaut d5a7daa524 add graphics state validation in debug mode 2022-09-07 13:07:17 -07:00
cosmonaut 5c080a4c42 add another DepthStencilAttachment constructor 2022-09-07 12:38:05 -07:00
TheSpydog eb8c350a47 Fix debug assertion in AudioDevice constructor (#25)
Let's try this again...

Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #25
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-08-30 17:09:32 +00:00
cosmonaut 0933b8e70f reset static audio on free + rework filter API 2022-08-29 22:45:28 -07:00
cosmonaut 1af16231ce Refresh 1.6.0 2022-08-25 12:32:49 -07:00
cosmonaut 0c6bb538fb fix depth-only render pass invocation + throw instead of return 2022-08-22 19:30:44 -07:00
cosmonaut db66fbf115 update to latest SDL2 and FAudio 2022-08-22 17:52:21 -07:00
cosmonaut f93be99545 https links in README 2022-08-22 17:49:43 -07:00
TheSpydog ebfd4fd457 Ignore dllmap logic when running under NativeAOT (#23)
Co-authored-by: Caleb Cornett <caleb.cornett@outlook.com>
Reviewed-on: #23
Co-authored-by: TheSpydog <thespydog@noreply.example.org>
Co-committed-by: TheSpydog <thespydog@noreply.example.org>
2022-08-22 16:43:19 +00:00
cosmonaut b380707462 Video Optimization (#22)
- Videos are now shoved into memory when created to avoid disk latency issues
- Added VideoPlayer class to avoid redundant texture creation on videos
- Most Video functions are now on VideoPlayer

Reviewed-on: #22
2022-08-18 20:45:34 +00:00
cosmonaut 491eafac76 fix default Video playback speed being 0 2022-08-18 00:57:22 -07:00
cosmonaut 0dddf2a0af fix default pitch 2022-08-17 18:45:35 -07:00
cosmonaut 15aefc2212 add MoonWorks.Math.Easing 2022-08-16 18:24:46 -07:00
cosmonaut a427b79510 add Fix64-int arithmetic 2022-08-16 18:24:34 -07:00
TheSpydog 49f852a822 Combine all dependencies into the main csproj (#21)
This removes the need for per-library dllmap logic and simplifies the build process.

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

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

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

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

Reviewed-on: #14
Co-authored-by: cosmonaut <evan@moonside.games>
Co-committed-by: cosmonaut <evan@moonside.games>
2022-02-25 05:34:36 +00:00
cosmonaut 8a3d93d2dc intellisense improvement + API tweak 2022-02-22 22:16:06 -08:00
cosmonaut 8973b3e658 Formatting pass 2022-02-22 21:14:32 -08:00
cosmonaut a0c57c7a59 Cleanup fixes 2022-02-22 16:44:39 -08:00
cosmonaut 5679dba978 Actually dispose graphics resources 2022-02-22 14:29:50 -08:00
cosmonaut cb25e6feff resource destroy now requires a CB 2022-02-18 21:02:16 -08:00
cosmonaut 4acc2588e1 QueuePresent ABI break 2022-02-08 17:17:51 -08:00
cosmonaut 8022cd1011 add DispatchCompute to command buffer 2022-01-17 20:39:03 -08:00
cosmonaut d2fca3654b never mind on SetDataOptions, bad idea 2022-01-13 14:48:32 -08:00
cosmonaut a5c8ebfc3a update buffer upload ABI 2022-01-13 14:29:08 -08:00
cosmonaut 7d7437721c uniforms are now pushed via command buffer 2022-01-12 13:01:32 -08:00
cosmonaut 0c588b96f4 add more info to Texture 2022-01-10 12:20:01 -08:00
cosmonaut 1fe256a479 get rid of badly conceived command buffer queue 2022-01-10 12:11:24 -08:00
cosmonaut 9df9aaeb3a update texture SetData API 2021-11-14 21:08:02 -08:00
cosmonaut e1a26e7c69 update libs 2021-11-14 20:44:46 -08:00
cosmonaut 29891c51e5 update libs 2021-07-30 13:01:30 -07:00
cosmonaut 9f588d389b tighten game loop timing 2021-07-23 15:47:02 -07:00
cosmonaut ae45aa9ff2 update RefreshCS 2021-07-23 14:38:41 -07:00
cosmonaut a337b94dfa .NET 5 2021-07-23 14:33:16 -07:00
180 changed files with 74354 additions and 5547 deletions

14
.editorconfig Normal file
View File

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

6
.gitmodules vendored
View File

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

2862
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

1
lib/WellspringCS Submodule

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

1
lib/dav1dfile Submodule

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

79
src/Audio/AudioBuffer.cs Normal file
View File

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

147
src/Audio/AudioDataOgg.cs Normal file
View File

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

164
src/Audio/AudioDataQoa.cs Normal file
View File

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

View File

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

100
src/Audio/AudioDataWav.cs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

58
src/Audio/AudioTween.cs Normal file
View File

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

View File

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

10
src/Audio/FilterType.cs Normal file
View File

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

36
src/Audio/Format.cs Normal file
View File

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

7
src/Audio/IPoolable.cs Normal file
View File

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

View File

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

83
src/Audio/ReverbEffect.cs Normal file
View File

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

View File

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

View File

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

View File

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

218
src/Audio/SourceVoice.cs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

169
src/Audio/StreamingVoice.cs Normal file
View File

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

56
src/Audio/SubmixVoice.cs Normal file
View File

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

View File

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

View File

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

578
src/Audio/Voice.cs Normal file
View File

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

45
src/Conversions.cs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

32
src/Graphics/FencePool.cs Normal file
View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -19,7 +19,7 @@ using System;
using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Graphics
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)

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -14,9 +14,9 @@
#endregion
using MoonWorks.Math;
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,24 @@
using System;
using System;
using RefreshCS;
namespace MoonWorks.Graphics
{
/// <summary>
/// A sampler specifies how a texture will be sampled in a shader.
/// </summary>
public class Sampler : 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()
);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

116
src/Input/KeyCode.cs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

886
src/Math/Easing.cs Normal file
View File

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

1050
src/Math/Fixed/Fix64.cs Normal file

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

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

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,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
}
}

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

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

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

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
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;
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
using System.Text;
#endregion
namespace MoonWorks.Math
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 = -------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
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();

View File

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

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

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

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -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);
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
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,

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
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

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
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;
}

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -20,7 +20,7 @@ using System.Diagnostics;
#endregion
namespace MoonWorks.Math
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(

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math
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>

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -22,7 +22,7 @@ using System.Text;
#endregion
namespace MoonWorks.Math
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>

View File

@ -1,4 +1,4 @@
#region License
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
#endregion
namespace MoonWorks.Math
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)

View File

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

View File

@ -1,826 +0,0 @@
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Microsoft.
* Released under the MIT license.
* See microsoft.LICENSE for details.
*/
using System;
using System.Globalization;
namespace MoonWorks.Math
{
/// <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();
}
}
}

202
src/MoonWorksDllMap.cs Normal file
View File

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

9
src/ScreenMode.cs Normal file
View File

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

89
src/Video/VideoAV1.cs Normal file
View File

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

View File

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

330
src/Video/VideoPlayer.cs Normal file
View File

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

9
src/Video/VideoState.cs Normal file
View File

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

174
src/Window.cs Normal file
View File

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

View File

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

View File

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

View File

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

55
src/WindowCreateInfo.cs Normal file
View File

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