├── .gitignore ├── .gitmodules ├── CODESTYLE.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── PATRONS.md ├── README.md ├── dub.sdl ├── modules └── openal │ ├── .gitignore │ ├── README.md │ ├── dub.sdl │ └── source │ └── openal │ ├── binding.d │ ├── package.d │ └── types.d ├── shaders ├── debug.frag ├── debug.vert ├── font_batch.frag ├── font_batch.vert ├── sprite_batch.frag └── sprite_batch.vert └── source └── polyplex ├── core ├── audio │ ├── effect.d │ ├── effects │ │ ├── autocomp.d │ │ ├── autowah.d │ │ ├── chorus.d │ │ ├── distortion.d │ │ ├── eaxreverb.d │ │ ├── echo.d │ │ ├── eq.d │ │ ├── flanger.d │ │ ├── freqshifter.d │ │ ├── package.d │ │ ├── pitchshifter.d │ │ ├── reverb.d │ │ ├── ringmod.d │ │ └── vocalmorph.d │ ├── filters │ │ ├── bandpass.d │ │ ├── highpass.d │ │ ├── lowpass.d │ │ └── package.d │ ├── listener.d │ ├── music.d │ ├── package.d │ ├── soundeffect.d │ └── syncgroup.d ├── color.d ├── colors.d ├── content │ ├── contentmanager.d │ ├── data.d │ ├── font.d │ ├── gl │ │ ├── package.d │ │ └── textures.d │ ├── package.d │ ├── textures.d │ └── vk │ │ ├── package.d │ │ └── textures.d ├── events.d ├── game.d ├── input │ ├── controller.d │ ├── keyboard.d │ ├── mouse.d │ ├── package.d │ └── touch.d ├── locale │ ├── language.d │ ├── package.d │ └── translation.d ├── package.d ├── render │ ├── camera.d │ ├── common.d │ ├── enums.d │ ├── gl │ │ ├── batch.d │ │ ├── debug2d.d │ │ ├── gloo.d │ │ ├── objects.d │ │ ├── package.d │ │ ├── renderbuf.d │ │ └── shader.d │ ├── package.d │ └── shapes.d ├── time.d ├── window.d └── windows │ ├── package.d │ └── sdlwindow.d ├── math ├── linear │ ├── matrix2x2.d │ ├── matrix3x3.d │ ├── matrix4x4.d │ ├── package.d │ ├── quaternion.d │ ├── transform.d │ └── vectors.d ├── mathf.d ├── package.d ├── random.d └── rectangle.d ├── package.d └── utils ├── logging.d ├── mathutils.d ├── package.d ├── sdlbool.d └── strutils.d /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.o 3 | *.obj 4 | 5 | # Compiled Dynamic libraries 6 | *.so 7 | *.dylib 8 | *.dll 9 | 10 | # Compiled Static libraries 11 | *.a 12 | *.lib 13 | 14 | # Executables 15 | *.exe 16 | 17 | # DUB 18 | .dub 19 | docs.json 20 | __dummy.html 21 | docs/ 22 | 23 | # Code coverage 24 | *.lst 25 | 26 | 27 | lib/ 28 | pp-test-library 29 | 30 | dub.selections.json -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PolyplexEngine/libpp/b47d1b183859f331bf2aa11cecf0e6095dcff1da/.gitmodules -------------------------------------------------------------------------------- /CODESTYLE.md: -------------------------------------------------------------------------------- 1 | # NOTE: These will be rewritten soon 2 | 3 | # Coding Guidelines 4 | These code guidelines should be followed when developing for Polyplex, right now not all of polyplex adheres to this, but should do when 1.0 is ready. 5 | Please note that these guidelines are based of the MonoGame Guidelines, adapted for D and the Polyplex prefered codestyle. And it will be expanded on as time goes. 6 | 7 | ## Tabs & Indentation 8 | Tab characters (\0x09) are to be used in code. 9 | 10 | ## Bracing 11 | Open braces should always be at the end of an statement. Contents of the brace should be indented by a single tab. For example: 12 | ```d 13 | if (someExpression) { 14 | DoSomething(); 15 | DoAnotherThing(); 16 | } else { 17 | DoSomethingElse(); 18 | DoAnotherThingElse(); 19 | } 20 | ``` 21 | 22 | `case` statements should be indented from the switch statement like this: 23 | ```d 24 | switch (someExpression) { 25 | case 0: 26 | DoSomething(); 27 | break; 28 | 29 | case 1: 30 | DoSomethingElse(); 31 | break; 32 | } 33 | ``` 34 | 35 | Braces are not used for single statement blocks immediately following a `for`, `foreach`, `if`, `do`, etc. 36 | ``` 37 | for (int i = 0; i < 100; ++i) DoSomething(i); 38 | ``` 39 | 40 | ## Properties 41 | If a property only does one thing, it should be put in one line. 42 | Properties that set values should have `value` as the name of the setting argument. 43 | 44 | Example: 45 | ```d 46 | public class Foo { 47 | private int bar; 48 | 49 | public @property int Bar() { return bar; } 50 | public @property void Bar(int value) { bar = value; } 51 | } 52 | ``` 53 | 54 | ## Commenting 55 | Comments should be used to describe intention, algorithmic overview, and/or logical flow. It would be ideal, if from reading the comments alone, someone other than the author could understand a function's intended behavior and general operation. While there are no minimum comment requirements and certainly some very small routines need no commenting at all, it is hoped that most routines will have comments reflecting the programmer's intent and approach. 56 | 57 | Comments must provide added value or explanation to the code. Simply describing the code is not helpful or useful. 58 | ```d 59 | // Wrong 60 | // Set count to 1 61 | count = 1; 62 | 63 | // Right 64 | // Set the initial reference count so it isn't cleaned up next frame 65 | count = 1; 66 | ``` 67 | 68 | ### Documentation Comments 69 | Please creation documentation comments as multiline comments 70 | Example: 71 | ```d 72 | public class Foo 73 | { 74 | /** 75 | MyMethod does some cool stuff! 76 | */ 77 | public void MyMethod(int bar) 78 | { 79 | // ... 80 | } 81 | } 82 | ``` 83 | 84 | ### Comment Style 85 | The // (two slashes) style of comment tags should be used in most situations. Where ever possible, place comments above the code instead of beside it. 86 | ```d 87 | // This is required for WebClient to work through the proxy 88 | GlobalProxySelection.Select = new WebProxy("http://itgproxy"); 89 | 90 | // Create object to access Internet resources 91 | WebClient myClient = new WebClient(); 92 | ``` 93 | 94 | ## Spacing 95 | Spaces improve readability by decreasing code density. Here are some guidelines for the use of space characters within code: 96 | 97 | Do use a single space after a comma between function arguments. 98 | ```d 99 | Console.In.Read(myChar, 0, 1); // Right 100 | Console.In.Read(myChar,0,1); // Wrong 101 | ``` 102 | 103 | Do not use a space after the parenthesis and function arguments 104 | ```d 105 | CreateFoo(myChar, 0, 1) // Right 106 | CreateFoo( myChar, 0, 1 ) // Wrong 107 | ``` 108 | 109 | Do not use spaces between a function name and parenthesis. 110 | ```d 111 | CreateFoo() // Right 112 | CreateFoo () // Wrong 113 | ``` 114 | 115 | Do not use spaces inside brackets. 116 | ```d 117 | x = dataArray[index]; // Right 118 | x = dataArray[ index ]; // Wrong 119 | ``` 120 | 121 | Do use a single space before flow control statements 122 | ```d 123 | while (x == y) // Right 124 | while(x==y) // Wrong 125 | ``` 126 | 127 | Do use a single space before and after binary operators 128 | ```d 129 | if (x == y) // Right 130 | if (x==y) // Wrong 131 | ``` 132 | 133 | Do not use a space between a unary operator and the operand 134 | ```d 135 | ++i; // Right 136 | ++ i; // Wrong 137 | ``` 138 | 139 | Do not use a space before a semi-colon. Do use a space after a semi-colon if there is more on the same line 140 | ```d 141 | for (int i = 0; i < 100; ++i) // Right 142 | for (int i=0 ; i<100 ; ++i) // Wrong 143 | ``` 144 | 145 | ## Naming 146 | * Do not use Hungarian notation 147 | * Do not use an underscore prefix for member variables, e.g. `_foo`, underscore suffixes are allowed. 148 | * Do use camelCase for private member variables, function, property and event names 149 | * Do use camelCase for parameters 150 | * Do use camelCase for local variables 151 | * Do use PascalCasing for public member variables, function, property, event, and class names (all words initial uppercase) 152 | * Do prefix interfaces names with **I** 153 | * Do not prefix enums, classes, or delegates with any letter 154 | 155 | The reasons to extend the public rules (no Hungarian, underscore prefix for member variables, etc.) is to produce a consistent source code appearance. In addition a goal is to have clean readable source. Code legibility should be a primary goal. 156 | 157 | ## File Organization 158 | * Class members should be grouped logically, and encapsulated into regions (Fields, Properties, Events, Constructors, Methods, Private interface implementations, Nested types) 159 | * `import` statements should be at the top of the file. 160 | 161 | ```d 162 | import std.stdio; 163 | import sev.events; 164 | 165 | public class MyClass : IFoo 166 | { 167 | int foo; 168 | 169 | public @property int Foo() { return foo; } 170 | public @property void Foo(int value) { 171 | FooChanged(); 172 | foo = value; 173 | } 174 | 175 | public Event FooChanged; 176 | 177 | public this() 178 | { 179 | // ... 180 | } 181 | 182 | void DoSomething() 183 | { 184 | // ... 185 | } 186 | 187 | void FindSomething() 188 | { 189 | // ... 190 | } 191 | 192 | void IFoo.DoSomething() 193 | { 194 | DoSomething(); 195 | } 196 | 197 | class NestedType 198 | { 199 | // ... 200 | } 201 | } 202 | ``` 203 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Polyplex Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Copyright (c) 2018 Clipsey 4 | 5 | Permission is hereby granted, free of charge, to any person or organization 6 | obtaining a copy of the software and accompanying documentation covered by 7 | this license (the "Software") to use, reproduce, display, distribute, 8 | execute, and transmit the Software, and to prepare derivative works of the 9 | Software, and to permit third-parties to whom the Software is furnished to 10 | do so, all subject to the following: 11 | 12 | The copyright notices in the Software and this entire statement, including 13 | the above license grant, this restriction and the following disclaimer, 14 | must be included in all copies of the Software, in whole or in part, and 15 | all derivative works of the Software, unless such copies or derivative 16 | works are solely in the form of machine-executable object code generated by 17 | a source language processor. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PATRONS.md: -------------------------------------------------------------------------------- 1 | # Thanks to the following people for supprting the development on patreon! 2 | If you've pledged $5 or over, make sure to join the Discord server to get your discord reward! :D 3 | ## SDLWindow ($1) pledges 4 | 5 | ## GlRenderer ($5) pledges 6 | * Heather Ellsworth 7 | 8 | ## VkRenderer ($10) pledges 9 | * The Linux Gamer Community 10 | * WeimTime 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Polyplex Logo][logo]](https://github.com/PolyplexEngine/branding) 2 | 3 | # Polyplex Main Library (libpp) 4 | [Mastodon](https://mastodon.social/@Polyplex) | [Twitter](https://twitter.com/polyplexengine) 5 | 6 | [Join the Discord Server](https://discord.gg/Dus5ArV) 7 | 8 | 9 | libpp is an game development framework written in D, supporting OpenGL 3.3 (and above). 10 | 11 | _OpenGL ES 2 and Vulkan will be supported in the future._ 12 | 13 | libpp is the base rendering, input handling, content handling, etc. library for the WIP Polyplex engine. 14 | 15 | ### Top Tier Patrons 16 | Previously a patreon was up for this project, that's no longer the case. 17 | Here's the people who contributed the highest tier at the time 18 | * The Linux Gamer Community 19 | * WeimTime 20 | 21 | ## Using libpp 22 | Find libpp on the [dub database](https://code.dlang.org/packages/pp) for instructions on adding libpp as a dependency to your dub project. 23 | 24 | Once added, you will need to set logging levels, choose a backend and create a window. 25 | 26 | ### Current capabilities 27 | Polyplex is still very early in development, but libpp can already be used to make simple 2D games, that are relatively easy to port to other platforms. 28 | Polyplex packages textures, sounds, etc. into files with the extension ppc. To convert ogg, png, jpeg or tga files to .ppc, use [ppcc](https://github.com/PolyplexEngine/ppcc) 29 | 30 | Othewise, you can use `Content.Load!(Type)(string);` by prepending `!` to the path (based on content directory) to the raw file. 31 | 32 | 33 | ### Examples 34 | ## PPCC 35 | `ppcc -c (or --convert) my_file.(extension)` output will be put in `my_file.ppc`. 36 | 37 | From libpp it can be accessed via `ContentManager.Load!Type("my_file")` 38 | 39 | ## libpp 40 | Example of simple polyplex application: 41 | ```d 42 | module example; 43 | import std.stdio; 44 | import polyplex; 45 | import polyplex.math; 46 | 47 | void main(string[] args) 48 | { 49 | arg = args[1..$]; 50 | 51 | // Enable info and debug logs. 52 | LogLevel |= LogType.Info; 53 | LogLevel |= LogType.Debug; 54 | 55 | // Create game instance and start game. 56 | MyGame game = new MyGame(); 57 | game.Run(); 58 | } 59 | 60 | class MyGame : Game 61 | { 62 | Texture2D myTexture; 63 | this() { 64 | 65 | } 66 | 67 | public override void Init() 68 | { 69 | // Enable/Disable VSync. 70 | Window.VSync = VSyncState.VSync; 71 | } 72 | 73 | public override void LoadContent() { 74 | // Load textures, sound, shaders, etc. here 75 | 76 | //Example: 77 | myTexture = Content.Load!Texture2D("myTexture"); 78 | } 79 | 80 | public override void Update(GameTimes game_time) 81 | { 82 | 83 | } 84 | 85 | public override void Draw(GameTimes game_time) 86 | { 87 | Renderer.ClearColor(Color.CornflowerBlue); 88 | } 89 | } 90 | 91 | ``` 92 | 93 | You can also check out [example_game](http://github.com/PolyplexEngine/example_game), which is used as a testbed for new libpp features/fixes. 94 | 95 | ## Notice 96 | This framework will **not** work officially on macOS, in future updates support code will be fully removed. The reason for this is that Apple has deprecated OpenGL, Polyplex will not support Metal, in any form (including MoltenVK) in the near-to-far future. 97 | 98 | ##### The library is written in what would be considered non-idiomatic D. 99 | 100 | [logo]: https://raw.githubusercontent.com/PolyplexEngine/branding/master/flat/libpp-pngs/libpp_transparent%40256w.png 101 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | /* 2 | Basic Info 3 | */ 4 | name "pp" 5 | description "The base library of the polyplex engine. (Heavily WIP, non-idiomatic)" 6 | license "Boost" 7 | targetType "dynamicLibrary" 8 | stringImportPaths "shaders/" 9 | 10 | /* 11 | Subpackages 12 | */ 13 | subPackage "./modules/openal" 14 | 15 | /* 16 | Imports 17 | */ 18 | 19 | // Graphics API stuff 20 | dependency "bindbc-loader" version="~>0.2.1" 21 | dependency "bindbc-opengl" version=">0.5.0" 22 | dependency "bindbc-sdl" version="~>0.10.1" 23 | 24 | // Backend functionality stuff 25 | dependency "colorize" version="~>1.0.5" 26 | dependency "sharpevents" version="~>2.0.0" 27 | dependency "ppc" version="~>0.3.0" 28 | 29 | // Audio stuff 30 | dependency "pp:openal" version="*" 31 | 32 | 33 | /* 34 | Sub Configurations 35 | */ 36 | 37 | configuration "OpenGL" { 38 | targetType "dynamicLibrary" 39 | 40 | // We're targeting SDL 2.0.9, as well OpenGL 3.3 (w/ some depricated functions) 41 | versions "SDL_209" "GL_33" "GL_AllowDeprecated" "OpenGL" 42 | } 43 | 44 | configuration "OpenGL_ES" { 45 | targetType "dynamicLibrary" 46 | 47 | // We're targeting SDL 2.0.9, as well OpenGL 3.3 (w/ some depricated functions) 48 | versions "SDL_209" "GL_33" "GL_AllowDeprecated" "OpenGL_ES" 49 | } 50 | 51 | configuration "Vulkan" { 52 | targetType "dynamicLibrary" 53 | versions "Vulkan" "SDL_209" 54 | } -------------------------------------------------------------------------------- /modules/openal/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | pp-oal.so 6 | pp-oal.dylib 7 | pp-oal.dll 8 | pp-oal.a 9 | pp-oal.lib 10 | pp-oal-test-* 11 | *.exe 12 | *.o 13 | *.obj 14 | *.lst 15 | -------------------------------------------------------------------------------- /modules/openal/README.md: -------------------------------------------------------------------------------- 1 | ## Polyplex bindings to OpenAL. -------------------------------------------------------------------------------- /modules/openal/dub.sdl: -------------------------------------------------------------------------------- 1 | name "openal" 2 | description "An BindBC binding to OpenAL" 3 | authors "Clipsey" 4 | copyright "Copyright © 2018, Clipsey" 5 | license "BSL-1.0" 6 | dependency "bindbc-loader" version="~>0.2.1" 7 | -------------------------------------------------------------------------------- /modules/openal/source/openal/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Boost Software License - Version 1.0 - August 17th, 2003 3 | 4 | Copyright (c) 2018 Clipsey 5 | 6 | Permission is hereby granted, free of charge, to any person or organization 7 | obtaining a copy of the software and accompanying documentation covered by 8 | this license (the "Software") to use, reproduce, display, distribute, 9 | execute, and transmit the Software, and to prepare derivative works of the 10 | Software, and to permit third-parties to whom the Software is furnished to 11 | do so, all subject to the following: 12 | 13 | The copyright notices in the Software and this entire statement, including 14 | the above license grant, this restriction and the following disclaimer, 15 | must be included in all copies of the Software, in whole or in part, and 16 | all derivative works of the Software, unless such copies or derivative 17 | works are solely in the form of machine-executable object code generated by 18 | a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | */ 28 | module openal; 29 | public import openal.types; 30 | public import openal.binding; 31 | -------------------------------------------------------------------------------- /shaders/debug.frag: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | precision highp float; 4 | 5 | in vec4 exColor; 6 | out vec4 outColor; 7 | 8 | void main(void) { 9 | outColor = exColor; 10 | } -------------------------------------------------------------------------------- /shaders/debug.vert: -------------------------------------------------------------------------------- 1 | #version 130 2 | in vec2 ppPosition; 3 | in vec4 ppColor; 4 | 5 | uniform mat4 ppProjection; 6 | 7 | out vec4 exColor; 8 | 9 | void main(void) { 10 | gl_Position = ppProjection * vec4(ppPosition.xy, 0.0, 1.0); 11 | exColor = ppColor; 12 | } -------------------------------------------------------------------------------- /shaders/font_batch.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | precision highp float; 4 | 5 | uniform sampler2D ppTexture; 6 | in vec4 exColor; 7 | in vec2 exTexcoord; 8 | out vec4 outColor; 9 | 10 | void main(void) { 11 | vec4 tex_col = vec4(exColor.r, exColor.g, exColor.b, texture2D(ppTexture, exTexcoord).r*exColor.a); 12 | outColor = tex_col; 13 | } 14 | -------------------------------------------------------------------------------- /shaders/font_batch.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec3 ppPosition; 3 | in vec2 ppTexcoord; 4 | in vec4 ppColor; 5 | 6 | uniform mat4 PROJECTION; 7 | 8 | out vec4 exColor; 9 | out vec2 exTexcoord; 10 | 11 | void main(void) { 12 | gl_Position = PROJECTION * vec4(ppPosition.xyz, 1.0); 13 | exTexcoord = ppTexcoord; 14 | exColor = ppColor; 15 | } 16 | -------------------------------------------------------------------------------- /shaders/sprite_batch.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | precision highp float; 4 | 5 | uniform sampler2D ppTexture; 6 | in vec4 exColor; 7 | in vec2 exTexcoord; 8 | out vec4 outColor; 9 | 10 | void main(void) { 11 | vec4 tex_col = texture2D(ppTexture, exTexcoord); 12 | outColor = exColor * tex_col; 13 | } 14 | -------------------------------------------------------------------------------- /shaders/sprite_batch.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec3 ppPosition; 3 | in vec2 ppTexcoord; 4 | in vec4 ppColor; 5 | 6 | uniform mat4 PROJECTION; 7 | 8 | out vec4 exColor; 9 | out vec2 exTexcoord; 10 | 11 | void main(void) { 12 | gl_Position = PROJECTION * vec4(ppPosition.xyz, 1.0); 13 | exTexcoord = ppTexcoord; 14 | exColor = ppColor; 15 | } 16 | -------------------------------------------------------------------------------- /source/polyplex/core/audio/effect.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effect; 2 | public import polyplex.core.audio.effects; 3 | public import polyplex.core.audio.filters; 4 | public import polyplex.core.audio; 5 | import std.algorithm.searching; 6 | import std.array; 7 | import std.algorithm.mutation : remove; 8 | import openal; 9 | 10 | private enum ErrorNotOpenAlSoftRouting = "Chaining effects together is not supported on the current OpenAL implementation 11 | 12 | - Try using OpenAL-Soft which is open source."; 13 | 14 | enum EffectType : ALenum { 15 | Nothing = AL_EFFECT_NULL, 16 | RoomDynamics = AL_EFFECT_EAXREVERB, 17 | Reverb = AL_EFFECT_REVERB, 18 | Chorus = AL_EFFECT_CHORUS, 19 | Distortion = AL_EFFECT_DISTORTION, 20 | Echo = AL_EFFECT_ECHO, 21 | Flanger = AL_EFFECT_FLANGER, 22 | FreqencyShifter = AL_EFFECT_FREQUENCY_SHIFTER, 23 | VocalMorpher = AL_EFFECT_VOCAL_MORPHER, 24 | PitchShifter = AL_EFFECT_PITCH_SHIFTER, 25 | RingModulation = AL_EFFECT_RING_MODULATOR, 26 | AutoWah = AL_EFFECT_AUTOWAH, 27 | Compressor = AL_EFFECT_COMPRESSOR, 28 | Equalizer = AL_EFFECT_EQUALIZER 29 | } 30 | 31 | enum FilterType : ALenum { 32 | Nothing = AL_FILTER_NULL, 33 | Lowpass = AL_FILTER_LOWPASS, 34 | Highpass = AL_FILTER_HIGHPASS, 35 | Bandpass = AL_FILTER_BANDPASS 36 | } 37 | 38 | public class AudioEffect { 39 | protected: 40 | ALuint id; 41 | ALuint sendId; 42 | EffectType effectType; 43 | 44 | AudioEffect target = null; 45 | AudioEffect[] attachedEffects; 46 | 47 | void setupDone() { 48 | alAuxiliaryEffectSloti(sendId, AL_EFFECTSLOT_EFFECT, id); 49 | } 50 | 51 | void deattachAllChildren() { 52 | foreach (effect; attachedEffects) { 53 | effect.unbind(); 54 | } 55 | attachedEffects.length = 0; 56 | } 57 | 58 | void attach(AudioEffect effect) { 59 | if (!effect.attachedEffects.canFind(this)) { 60 | effect.attachedEffects ~= this; 61 | } 62 | target = effect; 63 | } 64 | 65 | void deattachChild(AudioEffect effect) { 66 | if (attachedEffects.canFind(effect)) { 67 | attachedEffects.remove(attachedEffects.countUntil(effect)); 68 | } 69 | effect.unbind(); 70 | } 71 | 72 | void bind(ALuint id) { 73 | alAuxiliaryEffectSloti(Id, AL_EFFECTSLOT_TARGET_SOFT, id); 74 | } 75 | 76 | void unbind() { 77 | bind(0); 78 | } 79 | 80 | public: 81 | 82 | this(EffectType eType) { 83 | if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 84 | return; 85 | } 86 | 87 | // clear errors 88 | alGetError(); 89 | 90 | alGenEffects(1, &id); 91 | alGenAuxiliaryEffectSlots(1, &sendId); 92 | this.effectType = eType; 93 | 94 | alEffecti(id, AL_EFFECT_TYPE, cast(ALenum)eType); 95 | 96 | import std.conv; 97 | ErrCodes err = cast(ErrCodes)alGetError(); 98 | if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to create object "~err.to!string); 99 | } 100 | 101 | ~this() { 102 | if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 103 | return; 104 | } 105 | if (AudioDevice.SupportedExtensions & ALExtensionSupport.EffectChaining) { 106 | // Be SURE to unmap this effect from something else. 107 | // Prevents OpenAL crashing in some instances. 108 | deattachAllChildren(); 109 | unbind(); 110 | } 111 | alDeleteAuxiliaryEffectSlots(1, &sendId); 112 | alDeleteEffects(1, &id); 113 | } 114 | 115 | ALuint Id() { 116 | return sendId; 117 | } 118 | 119 | ALuint RawId() { 120 | return id; 121 | } 122 | 123 | EffectType Type() { 124 | return effectType; 125 | } 126 | 127 | /** 128 | Attach sound effect to another. 129 | 130 | A sound effect can only attach to one other sound effect. 131 | 132 | A sound effect can be attached to by many others. 133 | */ 134 | void AttachTo(AudioEffect effect) { 135 | if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EffectChaining)) { 136 | throw new Exception(ErrorNotOpenAlSoftRouting); 137 | } 138 | 139 | // Unaffiliate self with previous parent when change is requested. 140 | if (target !is null) { 141 | target.deattachChild(this); 142 | } 143 | 144 | // Bind to new effect. 145 | bind(effect.Id); 146 | effect.attach(this); 147 | } 148 | 149 | /** 150 | Deattaches the sound effect from ALL other sound effects. 151 | */ 152 | void Deattach() { 153 | if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EffectChaining)) { 154 | throw new Exception(ErrorNotOpenAlSoftRouting); 155 | } 156 | // Skip all the unbinding as it's not neccesary, small optimization. 157 | if (target is null) { 158 | return; 159 | } 160 | unbind(); 161 | target.deattachChild(this); 162 | target = null; 163 | } 164 | } 165 | 166 | public class AudioFilter { 167 | protected: 168 | ALuint id; 169 | FilterType filterType; 170 | 171 | public: 172 | this(FilterType fType) { 173 | if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 174 | return; 175 | } 176 | alGenFilters(1, &id); 177 | this.filterType = fType; 178 | 179 | alFilteri(id, AL_FILTER_TYPE, cast(ALenum)fType); 180 | 181 | import std.conv; 182 | ErrCodes err = cast(ErrCodes)alGetError(); 183 | if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to create object "~err.to!string); 184 | } 185 | 186 | ~this() { 187 | if (!(AudioDevice.SupportedExtensions & ALExtensionSupport.EFX)) { 188 | return; 189 | } 190 | alDeleteFilters(1, &id); 191 | } 192 | 193 | ALuint Id() { 194 | return id; 195 | } 196 | 197 | FilterType Type() { 198 | return filterType; 199 | } 200 | 201 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/autocomp.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.autocomp; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// An automatic compressor 7 | public class AutoCompressorEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.Compressor); 11 | setupDone(); 12 | } 13 | 14 | @property bool IsOn() { 15 | ALint val; 16 | alGetEffecti(id, AL_AUTOWAH_ATTACK_TIME, &val); 17 | return cast(bool)val; 18 | } 19 | 20 | @property void IsOn(bool val) { 21 | alEffecti(id, AL_AUTOWAH_ATTACK_TIME, cast(ALint)val); 22 | } 23 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/autowah.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.autowah; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /** 7 | An autowah effect 8 | 9 | MIGHT NOT WORK WITH OPENAL-SOFT 10 | */ 11 | public class AutoWahEffect : AudioEffect { 12 | public: 13 | this() { 14 | super(EffectType.AutoWah); 15 | setupDone(); 16 | } 17 | 18 | @property float Attack() { 19 | ALfloat val; 20 | alGetEffectf(id, AL_AUTOWAH_ATTACK_TIME, &val); 21 | return val; 22 | } 23 | 24 | @property void Attack(ALfloat val) { 25 | alEffectf(id, AL_AUTOWAH_ATTACK_TIME, val); 26 | } 27 | 28 | @property float Release() { 29 | ALfloat val; 30 | alGetEffectf(id, AL_AUTOWAH_RELEASE_TIME, &val); 31 | return val; 32 | } 33 | 34 | @property void Release(ALfloat val) { 35 | alEffectf(id, AL_AUTOWAH_RELEASE_TIME, val); 36 | } 37 | 38 | @property float Resonance() { 39 | ALfloat val; 40 | alGetEffectf(id, AL_AUTOWAH_RESONANCE, &val); 41 | return val; 42 | } 43 | 44 | @property void Resonance(ALfloat val) { 45 | alEffectf(id, AL_AUTOWAH_RESONANCE, val); 46 | } 47 | 48 | @property float PeakGain() { 49 | ALfloat val; 50 | alGetEffectf(id, AL_AUTOWAH_PEAK_GAIN, &val); 51 | return val; 52 | } 53 | 54 | @property void PeakGain(ALfloat val) { 55 | alEffectf(id, AL_AUTOWAH_PEAK_GAIN, val); 56 | } 57 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/chorus.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.chorus; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// A Chours 7 | public class ChorusEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.Chorus); 11 | setupDone(); 12 | } 13 | 14 | @property WaveformType Waveform() { 15 | ALenum val; 16 | alGetEffecti(id, AL_CHORUS_WAVEFORM, &val); 17 | return cast(WaveformType)val; 18 | } 19 | 20 | @property void Waveform(WaveformType val) { 21 | alEffecti(id, AL_CHORUS_WAVEFORM, cast(ALuint)val); 22 | } 23 | 24 | @property int Phase() { 25 | ALint val; 26 | alGetEffecti(id, AL_CHORUS_PHASE, &val); 27 | return cast(int)val; 28 | } 29 | 30 | @property void Phase(int val) { 31 | alEffecti(id, AL_CHORUS_PHASE, cast(ALint)val); 32 | } 33 | 34 | @property float Rate() { 35 | ALfloat val; 36 | alGetEffectf(id, AL_CHORUS_RATE, &val); 37 | return val; 38 | } 39 | 40 | @property void Rate(ALfloat val) { 41 | alEffectf(id, AL_CHORUS_RATE, val); 42 | } 43 | 44 | @property float Depth() { 45 | ALfloat val; 46 | alGetEffectf(id, AL_CHORUS_DEPTH, &val); 47 | return val; 48 | } 49 | 50 | @property void Depth(ALfloat val) { 51 | alEffectf(id, AL_CHORUS_DEPTH, val); 52 | } 53 | 54 | @property float Feedback() { 55 | ALfloat val; 56 | alGetEffectf(id, AL_CHORUS_FEEDBACK, &val); 57 | return val; 58 | } 59 | 60 | @property void Feedback(ALfloat val) { 61 | alEffectf(id, AL_CHORUS_FEEDBACK, val); 62 | } 63 | 64 | @property float Delay() { 65 | ALfloat val; 66 | alGetEffectf(id, AL_CHORUS_DELAY, &val); 67 | return val; 68 | } 69 | 70 | @property void Delay(ALfloat val) { 71 | alEffectf(id, AL_CHORUS_DELAY, val); 72 | } 73 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/distortion.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.distortion; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// An Distortion 7 | public class DistortionEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.Distortion); 11 | setupDone(); 12 | } 13 | 14 | @property float Edge() { 15 | ALfloat val; 16 | alGetEffectf(id, AL_DISTORTION_EDGE, &val); 17 | return val; 18 | } 19 | 20 | @property void Edge(ALfloat val) { 21 | alEffectf(id, AL_DISTORTION_EDGE, val); 22 | } 23 | 24 | @property float Gain() { 25 | ALfloat val; 26 | alGetEffectf(id, AL_DISTORTION_GAIN, &val); 27 | return val; 28 | } 29 | 30 | @property void Gain(ALfloat val) { 31 | alEffectf(id, AL_DISTORTION_GAIN, val); 32 | } 33 | 34 | @property float LowpassCutoff() { 35 | ALfloat val; 36 | alGetEffectf(id, AL_DISTORTION_LOWPASS_CUTOFF, &val); 37 | return val; 38 | } 39 | 40 | @property void LowpassCutoff(ALfloat val) { 41 | alEffectf(id, AL_DISTORTION_LOWPASS_CUTOFF, val); 42 | } 43 | 44 | @property float EQCenter() { 45 | ALfloat val; 46 | alGetEffectf(id, AL_DISTORTION_EQCENTER, &val); 47 | return val; 48 | } 49 | 50 | @property void EQCenter(ALfloat val) { 51 | alEffectf(id, AL_DISTORTION_EQCENTER, val); 52 | } 53 | 54 | @property float EQBandwidth() { 55 | ALfloat val; 56 | alGetEffectf(id, AL_DISTORTION_EQBANDWIDTH, &val); 57 | return val; 58 | } 59 | 60 | @property void EQBandwidth(ALfloat val) { 61 | alEffectf(id, AL_DISTORTION_EQBANDWIDTH, val); 62 | } 63 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/eaxreverb.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.eaxreverb; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /* 7 | An advanced form of reverb which also supports echoing 8 | WARNING: Some sound cards might not support this. 9 | 10 | If in doubt you can bundle OpenAL-soft with the application. 11 | OpenAL-Soft has no hardware accelleration but supports AL_EFFECT_EAXREVERB. 12 | */ 13 | public class RoomDynamicsEffect : AudioEffect { 14 | public: 15 | this() { 16 | super(EffectType.RoomDynamics); 17 | setupDone(); 18 | } 19 | 20 | @property float Decay() { 21 | ALfloat val; 22 | alGetEffectf(id, AL_EAXREVERB_DECAY_TIME, &val); 23 | return val; 24 | } 25 | 26 | @property void Decay(ALfloat val) { 27 | alEffectf(id, AL_EAXREVERB_DECAY_TIME, val); 28 | } 29 | 30 | @property float DecayHighFreqRatio() { 31 | ALfloat val; 32 | alGetEffectf(id, AL_EAXREVERB_DECAY_HFRATIO, &val); 33 | return val; 34 | } 35 | 36 | @property void DecayHighFreqRatio(ALfloat val) { 37 | alEffectf(id, AL_EAXREVERB_DECAY_HFRATIO, val); 38 | } 39 | 40 | @property float DecayLowFreqRatio() { 41 | ALfloat val; 42 | alGetEffectf(id, AL_EAXREVERB_DECAY_LFRATIO, &val); 43 | return val; 44 | } 45 | 46 | @property void DecayLowFreqRatio(ALfloat val) { 47 | alEffectf(id, AL_EAXREVERB_DECAY_LFRATIO, val); 48 | } 49 | 50 | @property float Density() { 51 | ALfloat val; 52 | alGetEffectf(id, AL_EAXREVERB_DENSITY, &val); 53 | return val; 54 | } 55 | 56 | @property void Density(ALfloat val) { 57 | alEffectf(id, AL_EAXREVERB_DENSITY, val); 58 | } 59 | 60 | @property float Diffusion() { 61 | ALfloat val; 62 | alGetEffectf(id, AL_EAXREVERB_DIFFUSION, &val); 63 | return val; 64 | } 65 | 66 | @property void Diffusion(ALfloat val) { 67 | alEffectf(id, AL_EAXREVERB_DIFFUSION, val); 68 | } 69 | 70 | @property float Gain() { 71 | ALfloat val; 72 | alGetEffectf(id, AL_EAXREVERB_GAIN, &val); 73 | return val; 74 | } 75 | 76 | @property void Gain(ALfloat val) { 77 | alEffectf(id, AL_EAXREVERB_GAIN, val); 78 | } 79 | 80 | @property float GainHighFreq() { 81 | ALfloat val; 82 | alGetEffectf(id, AL_EAXREVERB_GAINHF, &val); 83 | return val; 84 | } 85 | 86 | @property void GainHighFreq(ALfloat val) { 87 | alEffectf(id, AL_EAXREVERB_GAINHF, val); 88 | } 89 | 90 | @property float GainLowFreq() { 91 | ALfloat val; 92 | alGetEffectf(id, AL_EAXREVERB_GAINLF, &val); 93 | return val; 94 | } 95 | 96 | @property void GainLowFreq(ALfloat val) { 97 | alEffectf(id, AL_EAXREVERB_GAINLF, val); 98 | } 99 | 100 | @property float ReflectionsGain() { 101 | ALfloat val; 102 | alGetEffectf(id, AL_EAXREVERB_REFLECTIONS_GAIN, &val); 103 | return val; 104 | } 105 | 106 | @property void ReflectionsGain(ALfloat val) { 107 | alEffectf(id, AL_EAXREVERB_REFLECTIONS_GAIN, val); 108 | } 109 | 110 | @property float ReflectionsDelay() { 111 | ALfloat val; 112 | alGetEffectf(id, AL_EAXREVERB_REFLECTIONS_DELAY, &val); 113 | return val; 114 | } 115 | 116 | @property void ReflectionsDelay(ALfloat val) { 117 | alEffectf(id, AL_EAXREVERB_REFLECTIONS_DELAY, val); 118 | } 119 | 120 | @property float ReflectionsPan() { 121 | ALfloat val; 122 | alGetEffectf(id, AL_EAXREVERB_REFLECTIONS_PAN, &val); 123 | return val; 124 | } 125 | 126 | @property void ReflectionsPan(ALfloat val) { 127 | alEffectf(id, AL_EAXREVERB_REFLECTIONS_PAN, val); 128 | } 129 | 130 | @property float EchoTime() { 131 | ALfloat val; 132 | alGetEffectf(id, AL_EAXREVERB_ECHO_TIME, &val); 133 | return val; 134 | } 135 | 136 | @property void EchoTime(ALfloat val) { 137 | alEffectf(id, AL_EAXREVERB_ECHO_TIME, val); 138 | } 139 | 140 | @property float EchoDepth() { 141 | ALfloat val; 142 | alGetEffectf(id, AL_EAXREVERB_ECHO_DEPTH, &val); 143 | return val; 144 | } 145 | 146 | @property void EchoDepth(ALfloat val) { 147 | alEffectf(id, AL_EAXREVERB_ECHO_DEPTH, val); 148 | } 149 | 150 | @property float ModulationTime() { 151 | ALfloat val; 152 | alGetEffectf(id, AL_EAXREVERB_MODULATION_TIME, &val); 153 | return val; 154 | } 155 | 156 | @property void ModulationTime(ALfloat val) { 157 | alEffectf(id, AL_EAXREVERB_MODULATION_TIME, val); 158 | } 159 | 160 | @property float ModulationDepth() { 161 | ALfloat val; 162 | alGetEffectf(id, AL_EAXREVERB_MODULATION_DEPTH, &val); 163 | return val; 164 | } 165 | 166 | @property void ModulationDepth(ALfloat val) { 167 | alEffectf(id, AL_EAXREVERB_MODULATION_DEPTH, val); 168 | } 169 | 170 | @property float LateReverbGain() { 171 | ALfloat val; 172 | alGetEffectf(id, AL_EAXREVERB_LATE_REVERB_GAIN, &val); 173 | return val; 174 | } 175 | 176 | @property void LateReverbGain(ALfloat val) { 177 | alEffectf(id, AL_EAXREVERB_LATE_REVERB_GAIN, val); 178 | } 179 | 180 | @property float LateReverbDelay() { 181 | ALfloat val; 182 | alGetEffectf(id, AL_EAXREVERB_LATE_REVERB_DELAY, &val); 183 | return val; 184 | } 185 | 186 | @property void LateReverbDelay(ALfloat val) { 187 | alEffectf(id, AL_EAXREVERB_LATE_REVERB_DELAY, val); 188 | } 189 | 190 | @property float AirAbsorbtionGainHighFreq() { 191 | ALfloat val; 192 | alGetEffectf(id, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, &val); 193 | return val; 194 | } 195 | 196 | @property void AirAbsorbtionGainHighFreq(ALfloat val) { 197 | alEffectf(id, AL_EAXREVERB_AIR_ABSORPTION_GAINHF, val); 198 | } 199 | 200 | @property float RoomRollOffFactor() { 201 | ALfloat val; 202 | alGetEffectf(id, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, &val); 203 | return val; 204 | } 205 | 206 | @property void RoomRollOffFactor(ALfloat val) { 207 | alEffectf(id, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, val); 208 | } 209 | 210 | @property float HighFreqReference() { 211 | ALfloat val; 212 | alGetEffectf(id, AL_EAXREVERB_HFREFERENCE, &val); 213 | return val; 214 | } 215 | 216 | @property void HighFreqReference(ALfloat val) { 217 | alEffectf(id, AL_EAXREVERB_HFREFERENCE, val); 218 | } 219 | 220 | @property float LowFreqReference() { 221 | ALfloat val; 222 | alGetEffectf(id, AL_EAXREVERB_LFREFERENCE, &val); 223 | return val; 224 | } 225 | 226 | @property void LowFreqReference(ALfloat val) { 227 | alEffectf(id, AL_EAXREVERB_LFREFERENCE, val); 228 | } 229 | 230 | @property float DecayHighFreqLimit() { 231 | ALfloat val; 232 | alGetEffectf(id, AL_EAXREVERB_DECAY_HFLIMIT, &val); 233 | return val; 234 | } 235 | 236 | @property void DecayHighFreqLimit(ALfloat val) { 237 | alEffectf(id, AL_EAXREVERB_DECAY_HFLIMIT, val); 238 | } 239 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/echo.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.echo; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// An Echo 7 | public class EchoEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.Echo); 11 | setupDone(); 12 | } 13 | 14 | @property float Delay() { 15 | ALfloat val; 16 | alGetEffectf(id, AL_ECHO_DELAY, &val); 17 | return val; 18 | } 19 | 20 | @property void Delay(ALfloat val) { 21 | alEffectf(id, AL_ECHO_DELAY, val); 22 | } 23 | 24 | @property float LRDelay() { 25 | ALfloat val; 26 | alGetEffectf(id, AL_ECHO_LRDELAY, &val); 27 | return val; 28 | } 29 | 30 | @property void LRDelay(ALfloat val) { 31 | alEffectf(id, AL_ECHO_LRDELAY, val); 32 | } 33 | 34 | @property float Damping() { 35 | ALfloat val; 36 | alGetEffectf(id, AL_ECHO_DAMPING, &val); 37 | return val; 38 | } 39 | 40 | @property void Damping(ALfloat val) { 41 | alEffectf(id, AL_ECHO_DAMPING, val); 42 | } 43 | 44 | @property float Feedback() { 45 | ALfloat val; 46 | alGetEffectf(id, AL_ECHO_FEEDBACK, &val); 47 | return val; 48 | } 49 | 50 | @property void Feedback(ALfloat val) { 51 | alEffectf(id, AL_ECHO_FEEDBACK, val); 52 | } 53 | 54 | @property float Spread() { 55 | ALfloat val; 56 | alGetEffectf(id, AL_ECHO_SPREAD, &val); 57 | return val; 58 | } 59 | 60 | @property void Spread(ALfloat val) { 61 | alEffectf(id, AL_ECHO_SPREAD, val); 62 | } 63 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/eq.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.eq; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// An equalizer. 7 | public class EqualizerEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.Equalizer); 11 | setupDone(); 12 | } 13 | 14 | // LOW 15 | 16 | @property float LowGain() { 17 | ALfloat val; 18 | alGetEffectf(id, AL_EQUALIZER_LOW_GAIN, &val); 19 | return val; 20 | } 21 | 22 | @property void LowGain(ALfloat val) { 23 | alEffectf(id, AL_EQUALIZER_LOW_GAIN, val); 24 | } 25 | 26 | @property float LowCutoff() { 27 | ALfloat val; 28 | alGetEffectf(id, AL_EQUALIZER_LOW_CUTOFF, &val); 29 | return val; 30 | } 31 | 32 | @property void LowCutoff(ALfloat val) { 33 | alEffectf(id, AL_EQUALIZER_LOW_CUTOFF, val); 34 | } 35 | 36 | // MID 1 37 | 38 | @property float Mid1Gain() { 39 | ALfloat val; 40 | alGetEffectf(id, AL_EQUALIZER_MID1_GAIN, &val); 41 | return val; 42 | } 43 | 44 | @property void Mid1Gain(ALfloat val) { 45 | alEffectf(id, AL_EQUALIZER_MID1_GAIN, val); 46 | } 47 | 48 | @property float Mid1Center() { 49 | ALfloat val; 50 | alGetEffectf(id, AL_EQUALIZER_MID1_CENTER, &val); 51 | return val; 52 | } 53 | 54 | @property void Mid1Center(ALfloat val) { 55 | alEffectf(id, AL_EQUALIZER_MID1_CENTER, val); 56 | } 57 | 58 | @property float Mid1Width() { 59 | ALfloat val; 60 | alGetEffectf(id, AL_EQUALIZER_MID1_WIDTH, &val); 61 | return val; 62 | } 63 | 64 | @property void Mid1Width(ALfloat val) { 65 | alEffectf(id, AL_EQUALIZER_MID1_WIDTH, val); 66 | } 67 | 68 | // MID 2 69 | 70 | @property float Mid2Gain() { 71 | ALfloat val; 72 | alGetEffectf(id, AL_EQUALIZER_MID2_GAIN, &val); 73 | return val; 74 | } 75 | 76 | @property void Mid2Gain(ALfloat val) { 77 | alEffectf(id, AL_EQUALIZER_MID2_GAIN, val); 78 | } 79 | 80 | @property float Mid2Center() { 81 | ALfloat val; 82 | alGetEffectf(id, AL_EQUALIZER_MID2_CENTER, &val); 83 | return val; 84 | } 85 | 86 | @property void Mid2Center(ALfloat val) { 87 | alEffectf(id, AL_EQUALIZER_MID2_CENTER, val); 88 | } 89 | 90 | @property float Mid2Width() { 91 | ALfloat val; 92 | alGetEffectf(id, AL_EQUALIZER_MID2_WIDTH, &val); 93 | return val; 94 | } 95 | 96 | @property void Mid2Width(ALfloat val) { 97 | alEffectf(id, AL_EQUALIZER_MID2_WIDTH, val); 98 | } 99 | 100 | // HIGH 101 | 102 | @property float HighGain() { 103 | ALfloat val; 104 | alGetEffectf(id, AL_EQUALIZER_HIGH_GAIN, &val); 105 | return val; 106 | } 107 | 108 | @property void HighGain(ALfloat val) { 109 | alEffectf(id, AL_EQUALIZER_HIGH_GAIN, val); 110 | } 111 | 112 | @property float HighCutoff() { 113 | ALfloat val; 114 | alGetEffectf(id, AL_EQUALIZER_HIGH_CUTOFF, &val); 115 | return val; 116 | } 117 | 118 | @property void HighCutoff(ALfloat val) { 119 | alEffectf(id, AL_EQUALIZER_HIGH_CUTOFF, val); 120 | } 121 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/flanger.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.flanger; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// A Flanger 7 | public class FlangerEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.Flanger); 11 | setupDone(); 12 | } 13 | 14 | @property WaveformType Waveform() { 15 | ALenum val; 16 | alGetEffecti(id, AL_FLANGER_WAVEFORM, &val); 17 | return cast(WaveformType)val; 18 | } 19 | 20 | @property void Waveform(WaveformType val) { 21 | alEffecti(id, AL_FLANGER_WAVEFORM, cast(ALuint)val); 22 | } 23 | 24 | @property int Phase() { 25 | ALint val; 26 | alGetEffecti(id, AL_FLANGER_PHASE, &val); 27 | return cast(int)val; 28 | } 29 | 30 | @property void Phase(int val) { 31 | alEffecti(id, AL_FLANGER_PHASE, cast(ALint)val); 32 | } 33 | 34 | @property float Rate() { 35 | ALfloat val; 36 | alGetEffectf(id, AL_FLANGER_RATE, &val); 37 | return val; 38 | } 39 | 40 | @property void Rate(ALfloat val) { 41 | alEffectf(id, AL_FLANGER_RATE, val); 42 | } 43 | 44 | @property float Depth() { 45 | ALfloat val; 46 | alGetEffectf(id, AL_FLANGER_DEPTH, &val); 47 | return val; 48 | } 49 | 50 | @property void Depth(ALfloat val) { 51 | alEffectf(id, AL_FLANGER_DEPTH, val); 52 | } 53 | 54 | @property float Feedback() { 55 | ALfloat val; 56 | alGetEffectf(id, AL_FLANGER_FEEDBACK, &val); 57 | return val; 58 | } 59 | 60 | @property void Feedback(ALfloat val) { 61 | alEffectf(id, AL_FLANGER_FEEDBACK, val); 62 | } 63 | 64 | @property float Delay() { 65 | ALfloat val; 66 | alGetEffectf(id, AL_FLANGER_DELAY, &val); 67 | return val; 68 | } 69 | 70 | @property void Delay(ALfloat val) { 71 | alEffectf(id, AL_FLANGER_DELAY, val); 72 | } 73 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/freqshifter.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.freqshifter; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | enum FrequencyShiftDirection : ALenum { 7 | Down = 0, 8 | Up = 1, 9 | Off = 2 10 | } 11 | 12 | /** 13 | A frequency shifter 14 | 15 | MIGHT NOT WORK WITH OPENAL-SOFT 16 | */ 17 | public class FrequencyShifterEffect : AudioEffect { 18 | public: 19 | this() { 20 | super(EffectType.FreqencyShifter); 21 | setupDone(); 22 | } 23 | 24 | @property float Frequency() { 25 | ALfloat val; 26 | alGetEffectf(id, AL_FREQUENCY_SHIFTER_FREQUENCY, &val); 27 | return val; 28 | } 29 | 30 | @property void Frequency(ALfloat val) { 31 | alEffectf(id, AL_FREQUENCY_SHIFTER_FREQUENCY, val); 32 | } 33 | 34 | @property FrequencyShiftDirection RightDirection() { 35 | ALenum val; 36 | alGetEffecti(id, AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION, &val); 37 | return cast(FrequencyShiftDirection)val; 38 | } 39 | 40 | @property void RightDirection(FrequencyShiftDirection val) { 41 | alEffecti(id, AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION, cast(ALuint)val); 42 | } 43 | 44 | @property FrequencyShiftDirection LeftDirection() { 45 | ALenum val; 46 | alGetEffecti(id, AL_FREQUENCY_SHIFTER_LEFT_DIRECTION, &val); 47 | return cast(FrequencyShiftDirection)val; 48 | } 49 | 50 | @property void LeftDirection(FrequencyShiftDirection val) { 51 | alEffecti(id, AL_FREQUENCY_SHIFTER_LEFT_DIRECTION, cast(ALuint)val); 52 | } 53 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects; 2 | 3 | // Room simulation 4 | public import polyplex.core.audio.effects.eaxreverb; 5 | public import polyplex.core.audio.effects.reverb; 6 | public import polyplex.core.audio.effects.echo; 7 | 8 | // Auto wah 9 | public import polyplex.core.audio.effects.autowah; 10 | 11 | // Distortion causing effects 12 | public import polyplex.core.audio.effects.ringmod; 13 | public import polyplex.core.audio.effects.distortion; 14 | 15 | // Frequency & pitch changers 16 | public import polyplex.core.audio.effects.freqshifter; 17 | public import polyplex.core.audio.effects.pitchshifter; 18 | public import polyplex.core.audio.effects.vocalmorph; 19 | 20 | // Chorus and Flanger 21 | public import polyplex.core.audio.effects.chorus; 22 | public import polyplex.core.audio.effects.flanger; 23 | 24 | // Mixing/Mastering 25 | public import polyplex.core.audio.effects.autocomp; 26 | public import polyplex.core.audio.effects.eq; 27 | 28 | import openal; 29 | 30 | enum WaveformType : ALenum { 31 | /// A sine wave form 32 | Sinusoid = AL_WAVEFORM_SINUSOID, 33 | 34 | /// A triangle wave form 35 | Triangle = AL_WAVEFORM_TRIANGLE, 36 | 37 | /// A saw wave form 38 | Saw = AL_WAVEFORM_SAW 39 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/pitchshifter.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.pitchshifter; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /** 7 | A pitch shifter 8 | 9 | MIGHT NOT WORK WITH OPENAL-SOFT 10 | */ 11 | public class PitchShifterEffect : AudioEffect { 12 | public: 13 | this() { 14 | super(EffectType.PitchShifter); 15 | setupDone(); 16 | } 17 | 18 | @property float CourseTune() { 19 | ALfloat val; 20 | alGetEffectf(id, AL_PITCH_SHIFTER_COARSE_TUNE, &val); 21 | return val; 22 | } 23 | 24 | @property void CourseTune(ALfloat val) { 25 | alEffectf(id, AL_PITCH_SHIFTER_COARSE_TUNE, val); 26 | } 27 | 28 | @property float FineTune() { 29 | ALfloat val; 30 | alGetEffectf(id, AL_PITCH_SHIFTER_FINE_TUNE, &val); 31 | return val; 32 | } 33 | 34 | @property void FineTune(ALfloat val) { 35 | alEffectf(id, AL_PITCH_SHIFTER_FINE_TUNE, val); 36 | } 37 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/reverb.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.reverb; 2 | import polyplex.core.audio.effect; 3 | import openal; 4 | 5 | public class ReverbEffect : AudioEffect { 6 | public: 7 | this() { 8 | super(EffectType.Reverb); 9 | setupDone(); 10 | } 11 | 12 | @property float Decay() { 13 | ALfloat val; 14 | alGetEffectf(id, AL_REVERB_DECAY_TIME, &val); 15 | return val; 16 | } 17 | 18 | @property void Decay(ALfloat val) { 19 | alEffectf(id, AL_REVERB_DECAY_TIME, val); 20 | } 21 | 22 | @property float DecayHighFreqRatio() { 23 | ALfloat val; 24 | alGetEffectf(id, AL_REVERB_DECAY_HFRATIO, &val); 25 | return val; 26 | } 27 | 28 | @property void DecayHighFreqRatio(ALfloat val) { 29 | alEffectf(id, AL_REVERB_DECAY_HFRATIO, val); 30 | } 31 | 32 | @property float Density() { 33 | ALfloat val; 34 | alGetEffectf(id, AL_REVERB_DENSITY, &val); 35 | return val; 36 | } 37 | 38 | @property void Density(ALfloat val) { 39 | alEffectf(id, AL_REVERB_DENSITY, val); 40 | } 41 | 42 | @property float Diffusion() { 43 | ALfloat val; 44 | alGetEffectf(id, AL_REVERB_DIFFUSION, &val); 45 | return val; 46 | } 47 | 48 | @property void Diffusion(ALfloat val) { 49 | alEffectf(id, AL_REVERB_DIFFUSION, val); 50 | } 51 | 52 | @property float Gain() { 53 | ALfloat val; 54 | alGetEffectf(id, AL_REVERB_GAIN, &val); 55 | return val; 56 | } 57 | 58 | @property void Gain(ALfloat val) { 59 | alEffectf(id, AL_REVERB_GAIN, val); 60 | } 61 | 62 | @property float GainHighFreq() { 63 | ALfloat val; 64 | alGetEffectf(id, AL_REVERB_GAINHF, &val); 65 | return val; 66 | } 67 | 68 | @property void GainHighFreq(ALfloat val) { 69 | alEffectf(id, AL_REVERB_GAINHF, val); 70 | } 71 | 72 | @property float ReflectionsGain() { 73 | ALfloat val; 74 | alGetEffectf(id, AL_REVERB_REFLECTIONS_GAIN, &val); 75 | return val; 76 | } 77 | 78 | @property void ReflectionsGain(ALfloat val) { 79 | alEffectf(id, AL_REVERB_REFLECTIONS_GAIN, val); 80 | } 81 | 82 | @property float ReflectionsDelay() { 83 | ALfloat val; 84 | alGetEffectf(id, AL_REVERB_REFLECTIONS_DELAY, &val); 85 | return val; 86 | } 87 | 88 | @property void ReflectionsDelay(ALfloat val) { 89 | alEffectf(id, AL_REVERB_REFLECTIONS_DELAY, val); 90 | } 91 | 92 | @property float LateReverbGain() { 93 | ALfloat val; 94 | alGetEffectf(id, AL_REVERB_LATE_REVERB_GAIN, &val); 95 | return val; 96 | } 97 | 98 | @property void LateReverbGain(ALfloat val) { 99 | alEffectf(id, AL_REVERB_LATE_REVERB_GAIN, val); 100 | } 101 | 102 | @property float LateReverbDelay() { 103 | ALfloat val; 104 | alGetEffectf(id, AL_REVERB_LATE_REVERB_DELAY, &val); 105 | return val; 106 | } 107 | 108 | @property void LateReverbDelay(ALfloat val) { 109 | alEffectf(id, AL_REVERB_LATE_REVERB_DELAY, val); 110 | } 111 | 112 | @property float AirAbsorbtionGainHighFreq() { 113 | ALfloat val; 114 | alGetEffectf(id, AL_REVERB_AIR_ABSORPTION_GAINHF, &val); 115 | return val; 116 | } 117 | 118 | @property void AirAbsorbtionGainHighFreq(ALfloat val) { 119 | alEffectf(id, AL_REVERB_AIR_ABSORPTION_GAINHF, val); 120 | } 121 | 122 | @property float RoomRollOffFactor() { 123 | ALfloat val; 124 | alGetEffectf(id, AL_REVERB_ROOM_ROLLOFF_FACTOR, &val); 125 | return val; 126 | } 127 | 128 | @property void RoomRollOffFactor(ALfloat val) { 129 | alEffectf(id, AL_REVERB_ROOM_ROLLOFF_FACTOR, val); 130 | } 131 | 132 | @property float DecayHighFreqLimit() { 133 | ALfloat val; 134 | alGetEffectf(id, AL_REVERB_DECAY_HFLIMIT, &val); 135 | return val; 136 | } 137 | 138 | @property void DecayHighFreqLimit(ALfloat val) { 139 | alEffectf(id, AL_REVERB_DECAY_HFLIMIT, val); 140 | } 141 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/ringmod.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.ringmod; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | /// A ring modulator 7 | public class RingModEffect : AudioEffect { 8 | public: 9 | this() { 10 | super(EffectType.RingModulation); 11 | setupDone(); 12 | } 13 | 14 | @property float Frequency() { 15 | ALfloat val; 16 | alGetEffectf(id, AL_RING_MODULATOR_FREQUENCY, &val); 17 | return val; 18 | } 19 | 20 | @property void Frequency(ALfloat val) { 21 | alEffectf(id, AL_RING_MODULATOR_FREQUENCY, val); 22 | } 23 | 24 | @property float HighpassCutoff() { 25 | ALfloat val; 26 | alGetEffectf(id, AL_RING_MODULATOR_HIGHPASS_CUTOFF, &val); 27 | return val; 28 | } 29 | 30 | @property void HighpassCutoff(ALfloat val) { 31 | alEffectf(id, AL_RING_MODULATOR_HIGHPASS_CUTOFF, val); 32 | } 33 | 34 | @property WaveformType Waveform() { 35 | ALenum val; 36 | alGetEffecti(id, AL_RING_MODULATOR_WAVEFORM, &val); 37 | return cast(WaveformType)val; 38 | } 39 | 40 | @property void Waveform(WaveformType val) { 41 | alEffecti(id, AL_RING_MODULATOR_WAVEFORM, cast(ALuint)val); 42 | } 43 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/effects/vocalmorph.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.effects.vocalmorph; 2 | import polyplex.core.audio.effects; 3 | import polyplex.core.audio.effect; 4 | import openal; 5 | 6 | enum Phoneme : ALenum { 7 | A = AL_VOCAL_MORPHER_PHONEME_A, 8 | E = AL_VOCAL_MORPHER_PHONEME_E, 9 | I = AL_VOCAL_MORPHER_PHONEME_I, 10 | O = AL_VOCAL_MORPHER_PHONEME_O, 11 | U = AL_VOCAL_MORPHER_PHONEME_U, 12 | 13 | AA = AL_VOCAL_MORPHER_PHONEME_AA, 14 | AE = AL_VOCAL_MORPHER_PHONEME_AE, 15 | AH = AL_VOCAL_MORPHER_PHONEME_AH, 16 | AO = AL_VOCAL_MORPHER_PHONEME_AO, 17 | 18 | EH = AL_VOCAL_MORPHER_PHONEME_EH, 19 | ER = AL_VOCAL_MORPHER_PHONEME_ER, 20 | 21 | IH = AL_VOCAL_MORPHER_PHONEME_IH, 22 | IY = AL_VOCAL_MORPHER_PHONEME_IY, 23 | 24 | UH = AL_VOCAL_MORPHER_PHONEME_UH, 25 | UW = AL_VOCAL_MORPHER_PHONEME_UW, 26 | 27 | B = AL_VOCAL_MORPHER_PHONEME_B, 28 | D = AL_VOCAL_MORPHER_PHONEME_D, 29 | F = AL_VOCAL_MORPHER_PHONEME_F, 30 | G = AL_VOCAL_MORPHER_PHONEME_G, 31 | J = AL_VOCAL_MORPHER_PHONEME_J, 32 | K = AL_VOCAL_MORPHER_PHONEME_K, 33 | L = AL_VOCAL_MORPHER_PHONEME_L, 34 | M = AL_VOCAL_MORPHER_PHONEME_M, 35 | N = AL_VOCAL_MORPHER_PHONEME_N, 36 | P = AL_VOCAL_MORPHER_PHONEME_P, 37 | R = AL_VOCAL_MORPHER_PHONEME_R, 38 | S = AL_VOCAL_MORPHER_PHONEME_S, 39 | T = AL_VOCAL_MORPHER_PHONEME_T, 40 | V = AL_VOCAL_MORPHER_PHONEME_V, 41 | Z = AL_VOCAL_MORPHER_PHONEME_Z, 42 | } 43 | 44 | /** 45 | A 4-band formant filter, allowing to morph vocal-ish texture in to sound. 46 | 47 | WARNING: Only few *hardware* OpenAL implementations support this currently. 48 | */ 49 | public class VocalMorpherEffect : AudioEffect { 50 | public: 51 | this() { 52 | super(EffectType.VocalMorpher); 53 | setupDone(); 54 | } 55 | 56 | @property Phoneme PhonemeA() { 57 | ALenum val; 58 | alGetEffecti(id, AL_VOCAL_MORPHER_PHONEMEA, &val); 59 | return cast(Phoneme)val; 60 | } 61 | 62 | @property void PhonemeA(Phoneme val) { 63 | alEffecti(id, AL_VOCAL_MORPHER_PHONEMEA, cast(ALuint)val); 64 | } 65 | 66 | @property float TuningA() { 67 | ALfloat val; 68 | alGetEffectf(id, AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING, &val); 69 | return val; 70 | } 71 | 72 | @property void TuningA(ALfloat val) { 73 | alEffectf(id, AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING, val); 74 | } 75 | 76 | @property Phoneme PhonemeB() { 77 | ALenum val; 78 | alGetEffecti(id, AL_VOCAL_MORPHER_PHONEMEB, &val); 79 | return cast(Phoneme)val; 80 | } 81 | 82 | @property void PhonemeB(Phoneme val) { 83 | alEffecti(id, AL_VOCAL_MORPHER_PHONEMEB, cast(ALuint)val); 84 | } 85 | 86 | @property float TuningB() { 87 | ALfloat val; 88 | alGetEffectf(id, AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING, &val); 89 | return val; 90 | } 91 | 92 | @property void TuningB(ALfloat val) { 93 | alEffectf(id, AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING, val); 94 | } 95 | 96 | @property WaveformType Waveform() { 97 | ALenum val; 98 | alGetEffecti(id, AL_VOCAL_MORPHER_WAVEFORM, &val); 99 | return cast(WaveformType)val; 100 | } 101 | 102 | @property void Waveform(WaveformType val) { 103 | alEffecti(id, AL_VOCAL_MORPHER_WAVEFORM, cast(ALuint)val); 104 | } 105 | 106 | @property float Rate() { 107 | ALfloat val; 108 | alGetEffectf(id, AL_VOCAL_MORPHER_RATE, &val); 109 | return val; 110 | } 111 | 112 | @property void Rate(ALfloat val) { 113 | alEffectf(id, AL_VOCAL_MORPHER_RATE, val); 114 | } 115 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/filters/bandpass.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.filters.bandpass; 2 | import polyplex.core.audio.effect; 3 | import openal; 4 | 5 | // TODO: Implement high pass filter 6 | 7 | public class BandpassFilter : AudioFilter { 8 | public: 9 | this() { 10 | super(FilterType.Bandpass); 11 | } 12 | 13 | @property float GainBase() { 14 | ALfloat val; 15 | alGetFilterf(id, AL_BANDPASS_GAIN, &val); 16 | return val; 17 | } 18 | 19 | @property void GainBase(ALfloat val) { 20 | alFilterf(id, AL_BANDPASS_GAIN, val); 21 | } 22 | 23 | @property float GainLow() { 24 | ALfloat val; 25 | alGetFilterf(id, AL_BANDPASS_GAINLF, &val); 26 | return val; 27 | } 28 | 29 | @property void GainLow(ALfloat val) { 30 | alFilterf(id, AL_BANDPASS_GAINLF, val); 31 | } 32 | 33 | @property float GainHigh() { 34 | ALfloat val; 35 | alGetFilterf(id, AL_BANDPASS_GAINHF, &val); 36 | return val; 37 | } 38 | 39 | @property void GainHigh(ALfloat val) { 40 | alFilterf(id, AL_BANDPASS_GAINHF, val); 41 | } 42 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/filters/highpass.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.filters.highpass; 2 | import polyplex.core.audio.effect; 3 | import openal; 4 | 5 | // TODO: Implement high pass filter 6 | 7 | public class HighpassFilter : AudioFilter { 8 | public: 9 | this() { 10 | super(FilterType.Highpass); 11 | } 12 | 13 | @property float Gain() { 14 | return GainBase; 15 | } 16 | 17 | @property void Gain(ALfloat val) { 18 | GainBase = val; 19 | GainLF = val; 20 | } 21 | 22 | @property float GainBase() { 23 | ALfloat val; 24 | alGetFilterf(id, AL_HIGHPASS_GAIN, &val); 25 | return val; 26 | } 27 | 28 | @property void GainBase(ALfloat val) { 29 | alFilterf(id, AL_HIGHPASS_GAIN, val); 30 | } 31 | 32 | @property float GainLF() { 33 | ALfloat val; 34 | alGetFilterf(id, AL_HIGHPASS_GAINLF, &val); 35 | return val; 36 | } 37 | 38 | @property void GainLF(ALfloat val) { 39 | alFilterf(id, AL_HIGHPASS_GAINLF, val); 40 | } 41 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/filters/lowpass.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.filters.lowpass; 2 | import polyplex.core.audio.effect; 3 | import openal; 4 | 5 | // TODO: Implement high pass filter 6 | 7 | public class LowpassFilter : AudioFilter { 8 | public: 9 | this() { 10 | super(FilterType.Lowpass); 11 | } 12 | 13 | @property float Gain() { 14 | return GainBase; 15 | } 16 | 17 | @property void Gain(ALfloat val) { 18 | GainBase = val; 19 | GainHF = val; 20 | } 21 | 22 | @property float GainBase() { 23 | ALfloat val; 24 | alGetFilterf(id, AL_LOWPASS_GAIN, &val); 25 | return val; 26 | } 27 | 28 | @property void GainBase(ALfloat val) { 29 | alFilterf(id, AL_LOWPASS_GAIN, val); 30 | } 31 | 32 | @property float GainHF() { 33 | ALfloat val; 34 | alGetFilterf(id, AL_LOWPASS_GAINHF, &val); 35 | return val; 36 | } 37 | 38 | @property void GainHF(ALfloat val) { 39 | alFilterf(id, AL_LOWPASS_GAINHF, val); 40 | } 41 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/filters/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.filters; 2 | public import polyplex.core.audio.filters.highpass; 3 | public import polyplex.core.audio.filters.lowpass; 4 | public import polyplex.core.audio.filters.bandpass; -------------------------------------------------------------------------------- /source/polyplex/core/audio/listener.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.al.listener; 2 | import openal; 3 | import polyplex.math; 4 | 5 | public class Listener { 6 | private static Vector3 position; 7 | private static Vector3 velocity; 8 | 9 | public static @property Vector3 Position() { 10 | return position; 11 | } 12 | public static @property void Position(Vector3 val) { 13 | this.position = val; 14 | alListenerfv(AL_POSITION, val.ptr); 15 | } 16 | 17 | public static @property Vector3 Velocity() { 18 | return velocity; 19 | } 20 | public static @property void Velocity(Vector3 val) { 21 | this.velocity = val; 22 | alListenerfv(AL_VELOCITY, val.ptr); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /source/polyplex/core/audio/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio; 2 | import polyplex.utils.logging; 3 | import openal; 4 | public import polyplex.core.audio.soundeffect; 5 | public import polyplex.core.audio.music; 6 | public import polyplex.core.audio.syncgroup; 7 | public import polyplex.core.audio.effect; 8 | 9 | public enum AudioRenderFormats : int { 10 | /** 11 | Auto detect (only available for PPC Audio streams) 12 | */ 13 | Auto = 0, 14 | Mono8 = AL_FORMAT_MONO8, 15 | Mono16 = AL_FORMAT_MONO16, 16 | MonoFloat = AL_FORMAT_MONO_FLOAT32, 17 | 18 | Stereo8 = AL_FORMAT_STEREO8, 19 | Stereo16 = AL_FORMAT_STEREO16, 20 | StereoFloat = AL_FORMAT_STEREO_FLOAT32, 21 | } 22 | 23 | public enum ALExtensionSupport { 24 | /** 25 | Basic OpenAL context is supported. 26 | */ 27 | Basic = 0x00, 28 | 29 | /** 30 | EAX 2.0 is supported. 31 | */ 32 | EAX2 = 0x01, 33 | 34 | /** 35 | EFX is supported. 36 | */ 37 | EFX = 0x02, 38 | 39 | /** 40 | OpenAL-Soft effect chaining 41 | */ 42 | EffectChaining = 0x04 43 | // TODO add more extensions here. 44 | } 45 | 46 | public static AudioDevice DefaultAudioDevice; 47 | 48 | //TODO: remove this 49 | enum ErrCodes : ALCenum { 50 | ALC_FREQUENCY = 0x1007, 51 | ALC_REFRESH = 0x1008, 52 | ALC_SYNC = 0x1009, 53 | 54 | ALC_MONO_SOURCES = 0x1010, 55 | ALC_STEREO_SOURCES = 0x1011, 56 | 57 | ALC_NO_ERROR = ALC_FALSE, 58 | ALC_INVALID_DEVICE = 0xA001, 59 | ALC_INVALID_CONTEXT = 0xA002, 60 | ALC_INVALID_ENUM = 0xA003, 61 | ALC_INVALID_VALUE = 0xA004, 62 | ALC_OUT_OF_MEMORY = 0xA005, 63 | 64 | ALC_DEFAULT_DEVICE_SPECIFIER = 0x1004, 65 | ALC_DEVICE_SPECIFIER = 0x1005, 66 | ALC_EXTENSIONS = 0x1006, 67 | 68 | ALC_MAJOR_VERSION = 0x1000, 69 | ALC_MINOR_VERSION = 0x1001, 70 | 71 | ALC_ATTRIBUTES_SIZE = 0x1002, 72 | ALC_ALL_ATTRIBUTES = 0x1003, 73 | 74 | ALC_CAPTURE_DEVICE_SPECIFIER = 0x310, 75 | ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER = 0x311, 76 | ALC_CAPTURE_SAMPLES = 0x312, 77 | } 78 | 79 | protected __gshared ALint maxSlots; 80 | 81 | public class AudioDevice { 82 | private static bool deviceCreationSucceeded; 83 | public ALCdevice* ALDevice; 84 | public ALCcontext* ALContext; 85 | private static ALExtensionSupport supportedExtensions; 86 | public static ALint MixerSize = 10; 87 | 88 | public static bool DeviceCreationSucceeded() { 89 | return deviceCreationSucceeded; 90 | } 91 | 92 | public static ALExtensionSupport SupportedExtensions() { 93 | return supportedExtensions; 94 | } 95 | 96 | /** 97 | Constucts an audio device, NULL for preffered device. 98 | */ 99 | this(string device = null) { 100 | ALint[] attribs = null; 101 | 102 | Logger.Info("Initializing OpenAL device..."); 103 | ALCdevice* dev = alcOpenDevice(device.ptr); 104 | 105 | // If EAX 2.0 is supported, flag it as supported. 106 | bool supex = cast(bool)alIsExtensionPresent("EAX2.0"); 107 | if (supex) { 108 | Logger.Success("EAX 2.0 extensions are supported!"); 109 | supportedExtensions |= ALExtensionSupport.EAX2; 110 | } 111 | 112 | // If EFX is supported, flag it as supported. 113 | supex = cast(bool)alcIsExtensionPresent(dev, "ALC_EXT_EFX"); 114 | if (supex) { 115 | supportedExtensions |= ALExtensionSupport.EFX; 116 | Logger.Success("EFX extensions are supported!"); 117 | Logger.Info("Setting up mixer channel count..."); 118 | attribs = new ALint[4]; 119 | attribs[0] = ALC_MAX_AUXILIARY_SENDS; 120 | attribs[1] = MixerSize; 121 | } 122 | 123 | // If Effect Chaining is supported, flag it as supported. 124 | supex = cast(bool)alcIsExtensionPresent(dev, "AL_SOFTX_effect_chain"); 125 | if (supex) { 126 | supportedExtensions |= ALExtensionSupport.EffectChaining; 127 | Logger.Success("Effect chains are supported!"); 128 | } 129 | 130 | if (dev) { 131 | ALContext = alcCreateContext(dev, attribs.ptr); 132 | alcMakeContextCurrent(ALContext); 133 | } else { 134 | import std.conv; 135 | import std.stdio; 136 | Logger.Err("Could not create device! {0}", (cast(ErrCodes)alcGetError(dev)).to!string); 137 | deviceCreationSucceeded = false; 138 | return; 139 | } 140 | if (supex) { 141 | ALint sourceMax; 142 | alcGetIntegerv(dev, ALC_MAX_AUXILIARY_SENDS, 1, &sourceMax); 143 | 144 | maxSlots = sourceMax; 145 | 146 | Logger.Info("Created {0} mixer sends", sourceMax); 147 | } 148 | deviceCreationSucceeded = true; 149 | } 150 | 151 | ~this() { 152 | alcMakeContextCurrent(null); 153 | alcDestroyContext(ALContext); 154 | alcCloseDevice(ALDevice); 155 | } 156 | 157 | /** 158 | Makes the context for this device, a current context. 159 | */ 160 | public void MakeCurrent() { 161 | alcMakeContextCurrent(ALContext); 162 | } 163 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/soundeffect.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.soundeffect; 2 | import polyplex.core.audio; 3 | import polyplex.core.audio.effect; 4 | import ppc.types.audio; 5 | import openal; 6 | import ppc.backend.cfile; 7 | import polyplex.math; 8 | import polyplex.utils.logging; 9 | 10 | /// A sound effect which is persistent in memory. 11 | public class SoundEffect { 12 | private: 13 | // Buffer 14 | Audio stream; 15 | byte[] streamBuffer; 16 | ALuint buffer; 17 | AudioRenderFormats fFormat; 18 | 19 | // Source 20 | ALuint source; 21 | 22 | // Effects & filters 23 | AudioEffect attachedEffect; 24 | AudioFilter attachedFilter; 25 | 26 | void applyEffectsAndFilters() { 27 | alGetError(); 28 | ALuint efId = attachedEffect !is null ? attachedEffect.Id : AL_EFFECTSLOT_NULL; 29 | ALuint flId = attachedFilter !is null ? attachedFilter.Id : AL_FILTER_NULL; 30 | 31 | Logger.Debug("Applying effect {0} and filter {1} on SoundEffect {2}...", efId, flId, source); 32 | 33 | alSource3i(source, AL_AUXILIARY_SEND_FILTER, efId, 0, flId); 34 | alSourcei(source, AL_DIRECT_FILTER, flId); 35 | 36 | import std.conv; 37 | ErrCodes err = cast(ErrCodes)alGetError(); 38 | if (cast(ALint)err != AL_NO_ERROR) throw new Exception("Failed to create object "~err.to!string); 39 | } 40 | 41 | public: 42 | 43 | /// Creates a new sound effect from an audio stream 44 | this(Audio audio, AudioRenderFormats format = AudioRenderFormats.Auto) { 45 | stream = audio; 46 | import std.stdio; 47 | 48 | // Select format if told to. 49 | if (format == AudioRenderFormats.Auto) { 50 | import std.conv; 51 | if (stream.info.channels == 1) fFormat = AudioRenderFormats.Mono16; 52 | else if (stream.info.channels == 2) fFormat = AudioRenderFormats.Stereo16; 53 | else throw new Exception("Unsupported amount of channels! " ~ stream.info.channels.to!string); 54 | } 55 | 56 | // Clear errors 57 | alGetError(); 58 | 59 | // Buffer data from audio source. 60 | alGenBuffers(1, &buffer); 61 | streamBuffer = audio.readAll; 62 | alBufferData(buffer, fFormat, streamBuffer.ptr, cast(int)streamBuffer.length, cast(int)stream.info.bitrate); 63 | 64 | // Create audio source 65 | alGenSources(1, &source); 66 | alSourcei(source, AL_BUFFER, buffer); 67 | } 68 | 69 | ~this() { 70 | alDeleteSources(1, &source); 71 | alDeleteBuffers(1, &buffer); 72 | } 73 | 74 | /// Creates a new sound effect from a file. 75 | this(string file) { 76 | this(Audio(loadFile(file)), AudioRenderFormats.Auto); 77 | } 78 | 79 | void Play(bool looping = false) { 80 | this.Looping = looping; 81 | alSourcePlay(source); 82 | } 83 | 84 | void Pause() { 85 | alSourcePause(source); 86 | } 87 | 88 | void Stop() { 89 | alSourceStop(source); 90 | } 91 | 92 | void Rewind() { 93 | alSourceRewind(source); 94 | } 95 | 96 | bool IsPlaying() { 97 | ALenum state; 98 | alGetSourcei(source, AL_SOURCE_STATE, &state); 99 | return (state == AL_PLAYING); 100 | } 101 | 102 | @property AudioEffect Effect() { 103 | return this.attachedEffect; 104 | } 105 | 106 | @property void Effect(AudioEffect effect) { 107 | attachedEffect = effect; 108 | applyEffectsAndFilters(); 109 | } 110 | 111 | @property AudioFilter Filter() { 112 | return this.attachedFilter; 113 | } 114 | 115 | @property void Filter(AudioFilter filter) { 116 | attachedFilter = filter; 117 | applyEffectsAndFilters(); 118 | } 119 | 120 | @property bool Looping() { 121 | int v = 0; 122 | alGetSourcei(source, AL_LOOPING, &v); 123 | return (v == 1); 124 | } 125 | @property void Looping(bool val) { alSourcei(source, AL_LOOPING, cast(int)val); } 126 | 127 | @property int ByteOffset() { 128 | int v = 0; 129 | alGetSourcei(source, AL_BYTE_OFFSET, &v); 130 | return v; 131 | } 132 | 133 | @property int SecondOffset() { 134 | int v = 0; 135 | alGetSourcei(source, AL_SEC_OFFSET, &v); 136 | return v; 137 | } 138 | 139 | @property int SampleOffset() { 140 | int v = 0; 141 | alGetSourcei(source, AL_SAMPLE_OFFSET, &v); 142 | return v; 143 | } 144 | 145 | /* 146 | PITCH 147 | */ 148 | @property float Pitch() { 149 | float v = 0f; 150 | alGetSourcef(source, AL_PITCH, &v); 151 | return v; 152 | } 153 | @property void Pitch(float val) { alSourcef(source, AL_PITCH, val); } 154 | 155 | /* 156 | GAIN 157 | */ 158 | @property float Gain() { 159 | float v = 0f; 160 | alGetSourcef(source, AL_GAIN, &v); 161 | return v; 162 | } 163 | @property void Gain(float val) { alSourcef(source, AL_GAIN, val); } 164 | 165 | /* 166 | MIN GAIN 167 | */ 168 | @property float MinGain() { 169 | float v = 0f; 170 | alGetSourcef(source, AL_MIN_GAIN, &v); 171 | return v; 172 | } 173 | @property void MinGain(float val) { alSourcef(source, AL_MIN_GAIN, val); } 174 | 175 | /* 176 | MAX GAIN 177 | */ 178 | @property float MaxGain() { 179 | float v = 0f; 180 | alGetSourcef(source, AL_MAX_GAIN, &v); 181 | return v; 182 | } 183 | @property void MaxGain(float val) { alSourcef(source, AL_MAX_GAIN, val); } 184 | 185 | /* 186 | MAX DISTANCE 187 | */ 188 | @property float MaxDistance() { 189 | float v = 0f; 190 | alGetSourcef(source, AL_MAX_DISTANCE, &v); 191 | return v; 192 | } 193 | @property void MaxDistance(float val) { alSourcef(source, AL_MAX_DISTANCE, val); } 194 | 195 | /* 196 | ROLLOFF FACTOR 197 | */ 198 | @property float RolloffFactor() { 199 | float v = 0f; 200 | alGetSourcef(source, AL_ROLLOFF_FACTOR, &v); 201 | return v; 202 | } 203 | @property void RolloffFactor(float val) { alSourcef(source, AL_ROLLOFF_FACTOR, val); } 204 | 205 | /* 206 | CONE OUTER GAIN 207 | */ 208 | @property float ConeOuterGain() { 209 | float v = 0f; 210 | alGetSourcef(source, AL_CONE_OUTER_GAIN, &v); 211 | return v; 212 | } 213 | @property void ConeOuterGain(float val) { alSourcef(source, AL_CONE_OUTER_GAIN, val); } 214 | 215 | /* 216 | CONE INNER ANGLE 217 | */ 218 | @property float ConeInnerAngle() { 219 | float v = 0f; 220 | alGetSourcef(source, AL_CONE_INNER_ANGLE, &v); 221 | return v; 222 | } 223 | @property void ConeInnerAngle(float val) { alSourcef(source, AL_CONE_INNER_ANGLE, val); } 224 | 225 | /* 226 | CONE OUTER ANGLE 227 | */ 228 | @property float ConeOuterAngle() { 229 | float v = 0f; 230 | alGetSourcef(source, AL_CONE_OUTER_ANGLE, &v); 231 | return v; 232 | } 233 | @property void ConeOuterAngle(float val) { alSourcef(source, AL_CONE_OUTER_ANGLE, val); } 234 | 235 | /* 236 | REFERENCE DISTANCE 237 | */ 238 | @property float ReferenceDistance() { 239 | float v = 0f; 240 | alGetSourcef(source, AL_REFERENCE_DISTANCE, &v); 241 | return v; 242 | } 243 | @property void ReferenceDistance(float val) { alSourcef(source, AL_REFERENCE_DISTANCE, val); } 244 | 245 | /* 246 | POSITION 247 | */ 248 | @property Vector3 Position() { 249 | Vector3 v = 0f; 250 | alGetSourcefv(source, AL_POSITION, v.ptr); 251 | return v; 252 | } 253 | @property void Position(Vector3 val) { alSourcefv(source, AL_POSITION, val.ptr); } 254 | 255 | /* 256 | VELOCITY 257 | */ 258 | @property Vector3 Velocity() { 259 | Vector3 v = 0f; 260 | alGetSourcefv(source, AL_VELOCITY, v.ptr); 261 | return v; 262 | } 263 | @property void Velocity(Vector3 val) { alSourcefv(source, AL_VELOCITY, val.ptr); } 264 | 265 | /* 266 | DIRECTION 267 | */ 268 | @property Vector3 Direction() { 269 | Vector3 v = 0f; 270 | alGetSourcefv(source, AL_DIRECTION, v.ptr); 271 | return v; 272 | } 273 | @property void Direction(Vector3 val) { alSourcefv(source, AL_DIRECTION, val.ptr); } 274 | 275 | } -------------------------------------------------------------------------------- /source/polyplex/core/audio/syncgroup.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.audio.syncgroup; 2 | import polyplex.core.audio; 3 | import polyplex.core; 4 | import polyplex.utils.logging; 5 | 6 | /** 7 | SyncGroup is a rudementary and naive form of audio synchronization for music channels. 8 | It's recommended to implement your own algorithm to handle this. 9 | Feel free to look at the source for some hints on how to do this. 10 | */ 11 | public class SyncGroup { 12 | private: 13 | Music[] group; 14 | size_t syncSource; 15 | size_t combinedXruns; 16 | 17 | public: 18 | /** 19 | Creates a new SyncGroup which keeps Music instances synchronized. 20 | The first track in the group is the one that will be the sync source by default. 21 | */ 22 | this(Music[] group) { 23 | this.group = group; 24 | } 25 | 26 | ~this() { 27 | destroy(group); 28 | } 29 | 30 | /// Sets the synchronization source 31 | void SetSyncSource(Music mus) { 32 | foreach(i; 0 .. group.length) { 33 | if (group[i] == mus) 34 | syncSource = i; 35 | } 36 | } 37 | 38 | /// Call every frame to make sure that XRuns are handled. 39 | void Update() { 40 | // If Xruns have been unhandled. 41 | if (combinedXruns > 0) 42 | Resync(); 43 | 44 | combinedXruns = 0; 45 | foreach(audio; group) combinedXruns += audio.XRuns; 46 | } 47 | 48 | /// Resyncronizes audio tracks 49 | void Resync() { 50 | size_t sourceTell = group[syncSource].Tell; 51 | foreach(track; group) { 52 | if (track.Playing) track.Stop(); 53 | track.Seek(sourceTell); 54 | track.HandledXRun(); 55 | } 56 | 57 | // Once resynced, start again. 58 | foreach(track; group) track.Play(); 59 | Logger.Debug("Resynced SyncGroup..."); 60 | } 61 | 62 | /// Play all audio tracks in the sync group 63 | void Play() { 64 | foreach(track; group) track.Play(); 65 | } 66 | 67 | /// Pause all audio tracks in the sync group 68 | void Pause() { 69 | foreach(track; group) track.Pause(); 70 | } 71 | 72 | /// Stop all audio tracks in the sync group 73 | void Stop() { 74 | foreach(track; group) track.Stop(); 75 | } 76 | 77 | /* 78 | PITCH 79 | */ 80 | @property void Pitch(float val) { 81 | foreach(track; group) 82 | track.Pitch = val; 83 | } 84 | 85 | /* 86 | GAIN 87 | */ 88 | @property void Gain(float val) { 89 | foreach(track; group) 90 | track.Gain = val; 91 | } 92 | } -------------------------------------------------------------------------------- /source/polyplex/core/color.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.color; 2 | import polyplex.math; 3 | import polyplex.utils.logging; 4 | import polyplex.core.colors; 5 | 6 | /** 7 | A class handling color and color blending 8 | */ 9 | public class Color { 10 | public: 11 | mixin ColorList; 12 | 13 | union { 14 | struct { 15 | /// Red color 16 | int R; 17 | 18 | /// Green color 19 | int G; 20 | 21 | /// Blue color 22 | int B; 23 | 24 | /// Alpha transparency 25 | int A; 26 | } 27 | 28 | /// OpenGL friendly color 29 | Vector4i GLColor; 30 | } 31 | 32 | /// Constructor 33 | this(int r, int g, int b, int a) { 34 | R = Mathf.Clamp(r, 0, 255); 35 | G = Mathf.Clamp(g, 0, 255); 36 | B = Mathf.Clamp(b, 0, 255); 37 | A = Mathf.Clamp(a, 0, 255); 38 | } 39 | 40 | /// Constructor 41 | this(int r, int g, int b) { 42 | this(r, g, b, 255); 43 | } 44 | 45 | /// Constructor 46 | this(uint packed) { 47 | this((packed >> 24) & 0xff, (packed >> 16) & 0xff, (packed >> 8) & 0xff, packed & 0xff); 48 | } 49 | 50 | /// Float variant of red color 51 | @property float Rf() { return cast(float)R/255; } 52 | 53 | /// Float variant of green color 54 | @property float Gf() { return cast(float)G/255; } 55 | 56 | /// Float variant of blue color 57 | @property float Bf() { return cast(float)B/255; } 58 | 59 | /// Float variant of alpha transparency 60 | @property float Af() { return cast(float)A/255; } 61 | 62 | /// GL friendly float color 63 | @property Vector4 GLfColor() { return Vector4(Rf(), Gf(), Bf(), Af()); } 64 | 65 | /// Gets teh 66 | @property ubyte[] ColorBytes() { return [cast(ubyte)R, cast(ubyte)G, cast(ubyte)B, cast(ubyte)A]; } 67 | 68 | /** 69 | Premultiplied alpha blending. 70 | */ 71 | Color PreMultAlphaBlend(Color other) { 72 | Color o = new Color(R, G, B, A); 73 | o.R = cast(int)(((other.Rf * other.Af) + (o.Rf * (1f - other.Af)))*255); 74 | o.G = cast(int)(((other.Gf * other.Af) + (o.Gf * (1f - other.Af)))*255); 75 | o.B = cast(int)(((other.Bf * other.Af) + (o.Bf * (1f - other.Af)))*255); 76 | o.A = 255; 77 | return o; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /source/polyplex/core/content/contentmanager.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.contentmanager; 2 | import polyplex.core.content.textures; 3 | import polyplex.core.content.gl; 4 | import polyplex.core.content.vk; 5 | import polyplex.core.content.font; 6 | import polyplex.core.content.data; 7 | import polyplex.utils.logging; 8 | import polyplex.utils.strutils; 9 | import polyplex.core.audio; 10 | 11 | import polyplex.core.render : Shader, ShaderCode; 12 | import polyplex.core.render.gl.shader; 13 | 14 | static import ppct = ppc.types; 15 | import ppc.backend.loaders.ppc; 16 | import ppc.backend.cfile; 17 | import bindbc.sdl; 18 | 19 | public enum SupportedAudio { 20 | OGG 21 | } 22 | 23 | public class ContentManager { 24 | private static bool content_init = false; 25 | protected SupportedAudio supported_audio; 26 | 27 | public string ContentRoot = "content/"; 28 | 29 | this() { 30 | if (!content_init) { 31 | Logger.Debug("ContentManagerWarmup: Starting warmup..."); 32 | 33 | // TODO: Configure OpenAL 34 | 35 | content_init = true; 36 | Logger.Debug("ContentManager initialized..."); 37 | } 38 | } 39 | 40 | T loadLocal(T)(string name) if (is(T : SoundEffect)) { 41 | return new SoundEffect(ppct.Audio(loadFile(name))); 42 | } 43 | 44 | public T Load(T)(string name) if (is(T : SoundEffect)) { 45 | // Load raw file if instructed to. 46 | if (name[0] == '!') return loadLocal!T(name[1..$]); 47 | 48 | // Otherwise load PPC file 49 | PPC ppc = PPC(loadFile(this.ContentRoot~name~".ppc")); 50 | return new SoundEffect(ppct.Audio(ppc.data)); 51 | } 52 | 53 | T loadLocal(T)(string name) if (is(T : Music)) { 54 | return new Music(ppct.Audio(loadFile(name))); 55 | } 56 | 57 | public T Load(T)(string name) if (is(T : Music)) { 58 | // Load raw file if instructed to. 59 | if (name[0] == '!') return loadLocal!T(name[1..$]); 60 | 61 | // Otherwise load PPC file 62 | PPC ppc = PPC(loadFile(this.ContentRoot~name~".ppc")); 63 | return new Music(ppct.Audio(ppc.data)); 64 | } 65 | 66 | T loadLocal(T)(string name) if (is(T : Texture2D)) { 67 | auto imgd = ppct.Image(loadFile(name)); 68 | TextureImg img = new TextureImg(cast(int)imgd.width, cast(int)imgd.height, imgd.pixelData, name); 69 | return new GlTexture2D(img); 70 | } 71 | 72 | public T Load(T)(string name) if (is(T : Texture2D)) { 73 | // Load raw file if instructed to. 74 | if (name[0] == '!') return loadLocal!T(name[1..$]); 75 | 76 | // Otherwise load PPC file 77 | PPC ppc = PPC(this.ContentRoot~name~".ppc"); 78 | auto imgd = ppct.Image(ppc.data); 79 | TextureImg img = new TextureImg(cast(int)imgd.width, cast(int)imgd.height, imgd.pixelData, name); 80 | return new GlTexture2D(img); 81 | } 82 | 83 | T loadLocal(T)(string name) if (is(T : SpriteFont)) { 84 | throw new Exception("SpriteFont does not support localLoad at current time!"); 85 | 86 | // auto tface = ppct.TypeFace(loadFile(name)); 87 | // return new SpriteFont(tface); 88 | } 89 | 90 | public T Load(T)(string name) if (is(T : SpriteFont)) { 91 | // Load raw file if instructed to. 92 | if (name[0] == '!') return loadLocal!T(name[1..$]); 93 | 94 | // Otherwise load PPC file 95 | PPC ppc = PPC(this.ContentRoot~name~".ppc"); 96 | auto tface = ppct.TypeFace(ppc.data); 97 | return new SpriteFont(tface); 98 | } 99 | 100 | public T Load(T)(string name) if (is(T : Shader)) { 101 | // Shaders can't be loaded locally 102 | if (name[0] == '!') throw new Exception("Shaders cannot be loaded rawly, please use ppcc to convert to PSGL"); 103 | 104 | // Otherwise load PPC file 105 | Logger.Debug("Loading {0}...", name); 106 | PPC ppc = PPC(this.ContentRoot~name~".ppc"); 107 | auto shd = ppct.Shader(ppc.data); 108 | ShaderCode sc = new ShaderCode(); 109 | Logger.VerboseDebug("Shader Count: {0}", shd.shaders.length); 110 | foreach(k, v; shd.shaders) { 111 | if (k == ppct.ShaderType.Vertex) { 112 | sc.Vertex = v.toString; 113 | Logger.VerboseDebug("Vertex Shader:\n{0}", sc.Vertex); 114 | } 115 | if (k == ppct.ShaderType.Fragment) { 116 | sc.Fragment = v.toString; 117 | Logger.VerboseDebug("Fragment Shader:\n{0}", sc.Fragment); 118 | } 119 | if (k == ppct.ShaderType.Geometry) { 120 | sc.Geometry = v.toString; 121 | } 122 | } 123 | return new Shader(sc); 124 | } 125 | 126 | /** 127 | Load Raw file 128 | */ 129 | public T Load(T)(string name) if (is(T : string)) { 130 | if (name[0] == '!') return loadLocal!T(name[1..$]); 131 | PPC ppc = PPC(this.ContentRoot~name~".ppc"); 132 | return cast(T)ppc.data.toArray(); 133 | } 134 | 135 | public T loadLocal(T)(string name) if (is(T : string)) { 136 | import fio = std.file; 137 | return cast(T)fio.read(name); 138 | } 139 | } -------------------------------------------------------------------------------- /source/polyplex/core/content/data.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.data; 2 | 3 | //TODO: Data will be any binary data not predefined. 4 | public class Data { 5 | 6 | } -------------------------------------------------------------------------------- /source/polyplex/core/content/font.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.font; 2 | import polyplex.core.content.textures; 3 | import polyplex.core.content.gl.textures; 4 | import polyplex.core.render.gl.gloo; 5 | import ppc.types.font; 6 | import polyplex.math; 7 | 8 | public class SpriteFont { 9 | private: 10 | GlTexture2DImpl!(GL_RED, 1) texture; 11 | TypeFace typeface; 12 | 13 | Vector2i baseCharSize; 14 | 15 | public: 16 | /// Constructor 17 | this(TypeFace typeface) { 18 | this.typeface = typeface; 19 | PSize size = typeface.getAtlasSize(); 20 | this.texture = new GlTexture2DImpl!(GL_RED, 1)(new TextureImg(cast(int)size.width, cast(int)size.height, typeface.getTexture())); 21 | 22 | // Gets the base character height of the character A 23 | baseCharSize = cast(Vector2i)MeasureCharacter('A'); 24 | } 25 | 26 | /// Returns the texture for this sprite font 27 | Texture2D getTexture() { 28 | return cast(Texture2D)texture; 29 | } 30 | 31 | /// Indexer for glyph info 32 | GlyphInfo* opIndex(dchar c) { 33 | return typeface[c]; 34 | } 35 | 36 | /// Measure the size of a string 37 | Vector2 MeasureString(string text) { 38 | int lines = 1; 39 | float currentLineLength = 0; 40 | Vector2 size = Vector2(0, 0); 41 | foreach(dchar c; text) { 42 | if (c == '\n') { 43 | lines++; 44 | currentLineLength = 0; 45 | } 46 | if (this[c] is null) continue; 47 | 48 | // Bitshift by 6 to make it be in pixels 49 | currentLineLength += (this[c].advance.x >> 6); 50 | 51 | if (currentLineLength > size.X) size.X = currentLineLength; 52 | } 53 | float height = lines*(baseCharSize.Y+(baseCharSize.Y/2)); 54 | return Vector2(size.X, height-(baseCharSize.Y/2)); 55 | } 56 | 57 | /// Measure the size of an individual character 58 | Vector2 MeasureCharacter(dchar c) { 59 | return Vector2((this[c].advance.x >> 6), this[c].bearing.y); 60 | } 61 | 62 | /** 63 | Returns the base size for the character 'A' 64 | */ 65 | @property Vector2i BaseCharSize() { 66 | return baseCharSize; 67 | } 68 | 69 | /** 70 | Gets the size of the sprite font texture atlas 71 | */ 72 | @property Vector2i TexSize() { 73 | return Vector2i(texture.Width, texture.Height); 74 | } 75 | } -------------------------------------------------------------------------------- /source/polyplex/core/content/gl/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.gl; 2 | public import polyplex.core.content.gl.textures; -------------------------------------------------------------------------------- /source/polyplex/core/content/gl/textures.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.gl.textures; 2 | import polyplex.core.content.textures; 3 | import polyplex.utils.logging; 4 | import polyplex.core.render; 5 | import polyplex.core.render.gl.gloo; 6 | import polyplex.core.render.gl.shader; 7 | import polyplex.core.color; 8 | import std.stdio; 9 | 10 | 11 | alias GlTexture2D = GlTexture2DImpl!(GL_RGBA, 4); 12 | 13 | public class GlTexture2DImpl(int mode = GL_RGBA, int alignment = 4) : Texture2D { 14 | private: 15 | Texture tex; 16 | 17 | static int MAX_TEX_UNITS = -1; 18 | 19 | protected: 20 | override void rebuffer() { 21 | glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); 22 | if (tex !is null) { 23 | UpdatePixelData(image.Pixels); 24 | return; 25 | } 26 | tex = new Texture(); 27 | 28 | Bind(); 29 | tex.Image2D(TextureType.Tex2D, 0, mode, image.Width, image.Height, 0, mode, GL_UNSIGNED_BYTE, image.Pixels.ptr); 30 | Unbind(); 31 | 32 | // Set max texture units. 33 | if (MAX_TEX_UNITS == -1) { 34 | glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &MAX_TEX_UNITS); 35 | Logger.Info("Set max texture units to: {0}", MAX_TEX_UNITS); 36 | } 37 | } 38 | public: 39 | override uint Id() { 40 | return tex.Id; 41 | } 42 | 43 | Texture GLTexture() { 44 | return tex; 45 | } 46 | 47 | this(TextureImg img) { 48 | Logger.VerboseDebug("Created GL Texture from image: {0}", img.InternalName); 49 | super(img); 50 | } 51 | 52 | this(Color[][] colors) { 53 | super(colors); 54 | } 55 | 56 | ~this() { 57 | destroy(tex); 58 | } 59 | 60 | override void UpdatePixelData(ubyte[] data) { 61 | Bind(); 62 | if (image !is null) image.SetPixels(data); 63 | glTexSubImage2D(TextureType.Tex2D, 0, 0, 0, image.Width, image.Height, mode, GL_UNSIGNED_BYTE, data.ptr); 64 | Unbind(); 65 | } 66 | 67 | /** 68 | Binds the texture. 69 | */ 70 | override void Bind(int attachPos = 0, Shader s = null) { 71 | if (s is null) { 72 | tex.Bind(TextureType.Tex2D); 73 | return; 74 | } 75 | if (!s.HasUniform("ppTexture")) throw new Exception("Texture2D requires ppTexture sampler2d uniform to attach to!"); 76 | if (attachPos > MAX_TEX_UNITS) attachPos = MAX_TEX_UNITS; 77 | if (attachPos < 0) attachPos = 0; 78 | 79 | tex.Bind(TextureType.Tex2D); 80 | tex.AttachTo(attachPos); 81 | } 82 | 83 | /** 84 | Unbinds the texture. 85 | */ 86 | override void Unbind() { 87 | tex.Unbind(); 88 | } 89 | } -------------------------------------------------------------------------------- /source/polyplex/core/content/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content; 2 | public import polyplex.core.content.textures; 3 | public import polyplex.core.content.font; 4 | public import polyplex.core.content.data; 5 | public import polyplex.core.content.contentmanager; -------------------------------------------------------------------------------- /source/polyplex/core/content/vk/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.vk; 2 | public import polyplex.core.content.vk.textures; -------------------------------------------------------------------------------- /source/polyplex/core/content/vk/textures.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.content.vk.textures; -------------------------------------------------------------------------------- /source/polyplex/core/events.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.events; 2 | import polyplex.core.input; 3 | import bindbc.sdl; 4 | import std.stdio; 5 | import events; 6 | 7 | public class GameResizeEventArgs : EventArgs { 8 | public void* SDLEvent; 9 | } 10 | 11 | public class PPEvents { 12 | public static SDL_Event[] Events; 13 | 14 | public static PumpEvents() { 15 | // Then poll everything and push it to our own event queue 16 | Events.length = 0; 17 | SDL_Event ev; 18 | while(SDL_PollEvent(&ev)) { 19 | Events ~= ev; 20 | } 21 | } 22 | } 23 | 24 | public class GameEventSystem { 25 | private bool lastHandled; 26 | 27 | public Event!GameResizeEventArgs OnWindowSizeChanged = new Event!GameResizeEventArgs; 28 | public BasicEvent OnExitRequested = new BasicEvent; 29 | 30 | this() { 31 | } 32 | 33 | /** 34 | Update essential things like handling game exit and resizing. 35 | */ 36 | public void Update() { 37 | 38 | foreach(SDL_Event ev; PPEvents.Events) { 39 | lastHandled = true; 40 | if (ev.type == SDL_QUIT) { 41 | OnExitRequested(cast(void*)this); 42 | } 43 | if (ev.type == SDL_WINDOWEVENT) { 44 | if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED || ev.window.event == SDL_WINDOWEVENT_RESIZED) { 45 | GameResizeEventArgs args = new GameResizeEventArgs(); 46 | args.SDLEvent = cast(void*)ev.window.event; 47 | OnWindowSizeChanged(cast(void*)this, args); 48 | } 49 | } 50 | 51 | //Update last handled. 52 | lastHandled = false; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /source/polyplex/core/game.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.game; 2 | 3 | import polyplex.utils.logging; 4 | 5 | import polyplex.math; 6 | import polyplex.core.windows; 7 | import polyplex.core.render; 8 | import polyplex.core.input; 9 | import polyplex.core.events; 10 | import polyplex.core.content; 11 | import polyplex.core.audio; 12 | import polyplex.core.time; 13 | static import win = polyplex.core.window; 14 | 15 | import polyplex.utils.strutils; 16 | import polyplex : InitLibraries, UnInitLibraries; 17 | 18 | import bindbc.sdl; 19 | import events; 20 | 21 | import std.math; 22 | import std.random; 23 | import std.typecons; 24 | import std.stdio; 25 | import std.conv; 26 | 27 | import core.memory; 28 | 29 | /** 30 | The base system to manage a game 31 | This class handles the following: 32 | * Spawning a GameWindow 33 | * Starting an event loop for input 34 | * Creating an audio subsystem 35 | * Fixed timestep updates 36 | * Content management pipeline creation 37 | 38 | Basicly, extend this class to create a game. 39 | */ 40 | abstract class Game { 41 | private: 42 | GameEventSystem events; 43 | GameTime times; 44 | ulong frameTimeStart = 0; 45 | double frameTimeDelta = 0; 46 | ulong frameTimeLast = 0; 47 | bool enableAudio = true; 48 | 49 | double lag = 0.0; 50 | double msTarget = 0.250; 51 | 52 | void doUpdate() { 53 | Prepare(); 54 | while (!RunOne()) { 55 | } 56 | } 57 | 58 | protected: 59 | //Private properties 60 | win.Window window; 61 | ContentManager Content; 62 | SpriteBatch spriteBatch; 63 | 64 | package: 65 | void forceWindowChange(win.Window newWindow) { 66 | this.window = newWindow; 67 | } 68 | 69 | public: 70 | 71 | /** 72 | Run a fixed time step update 73 | */ 74 | void FixedUpdate(double fixedDelta) { } 75 | 76 | // Abstract declarations 77 | abstract { 78 | /** 79 | Initialize core game variables and the like. 80 | 81 | Content can be loaded in LoadContent. 82 | */ 83 | void Init(); 84 | /** 85 | Load game assets from disk, etc. 86 | */ 87 | void LoadContent(); 88 | 89 | /** 90 | Unload game assets, etc. 91 | 92 | Use D's `destroy(<*>);` function to unload content 93 | */ 94 | void UnloadContent(); 95 | 96 | /** 97 | Run an update iteration 98 | */ 99 | void Update(GameTime gameTime); 100 | 101 | /** 102 | Run a draw iteration 103 | */ 104 | void Draw(GameTime gameTime); 105 | } 106 | 107 | final: 108 | /// Event raised when the window changes its size 109 | Event!GameResizeEventArgs OnWindowSizeChanged = new Event!GameResizeEventArgs; 110 | 111 | /// How much time since game started 112 | @property GameTimeSpan TotalTime() { return times.TotalTime; } 113 | 114 | /// How much time since last frame 115 | @property GameTimeSpan DeltaTime() { return times.DeltaTime; } 116 | 117 | /// Wether the system cursor should be shown while inside the game window. 118 | @property bool ShowCursor() { 119 | return (SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE); 120 | } 121 | 122 | /// ditto. 123 | @property void ShowCursor(bool value) { 124 | SDL_ShowCursor(cast(int)value); 125 | } 126 | 127 | /// Wether the audio backend is enabled. 128 | @property bool AudioEnabled() { 129 | return !(DefaultAudioDevice is null); 130 | } 131 | 132 | /// ditto. 133 | @property void AudioEnabled(bool val) { 134 | enableAudio = val; 135 | if (val == true) DefaultAudioDevice = new AudioDevice(); 136 | else DefaultAudioDevice = null; 137 | } 138 | 139 | /// How many miliseconds since the last frame was drawn 140 | @property double Frametime() { 141 | return frameTimeDelta; 142 | } 143 | 144 | /// How many miliseconds since the last frame was drawn 145 | @property double FixedFrametime() { 146 | return msTarget; 147 | } 148 | 149 | /// Returns true if the game is running slowly 150 | @property bool IsGameLagging() { 151 | return lag > 10.0; 152 | } 153 | 154 | /// The window the game is being rendered to 155 | @property win.Window Window() { return window; } 156 | 157 | /** 158 | Create a new game instance. 159 | */ 160 | this(bool audio = true, bool eventSystem = true) { 161 | enableAudio = audio; 162 | if (eventSystem) events = new GameEventSystem(); 163 | } 164 | 165 | ~this() { 166 | UnloadContent(); 167 | destroy(window); 168 | } 169 | 170 | /** 171 | Start/run the game 172 | */ 173 | void Run() { 174 | if (window is null) { 175 | window = new SDLGameWindow(Rectanglei(0, 0, 0, 0), false); 176 | } 177 | InitLibraries(); 178 | window.Show(); 179 | Renderer.setWindow(window); 180 | Renderer.Init(); 181 | 182 | doUpdate(); 183 | UnInitLibraries(); 184 | } 185 | 186 | /** 187 | Poll system events 188 | */ 189 | void PollEvents() { 190 | events.Update(); 191 | } 192 | 193 | 194 | /** 195 | Run a single frame of the game 196 | */ 197 | bool RunOne() { 198 | //FPS begin counting. 199 | frameTimeStart = SDL_GetPerformanceCounter(); 200 | times.updateTotal(cast(double)SDL_GetTicks()); 201 | 202 | //Update events. 203 | if (events !is null) { 204 | PPEvents.PumpEvents(); 205 | 206 | //Do actual updating and drawing. 207 | events.Update(); 208 | } 209 | 210 | // Run user set update and draw functions 211 | Update(times); 212 | 213 | lag += frameTimeDelta; 214 | while (lag >= msTarget) { 215 | 216 | FixedUpdate(msTarget); 217 | lag -= msTarget; 218 | } 219 | if (lag < 0) lag = 0; 220 | 221 | Draw(times); 222 | 223 | // Exit the game if the window is closed. 224 | if (!window.Visible) { 225 | Quit(); 226 | return true; 227 | } 228 | 229 | //Swap buffers and chain. 230 | if (spriteBatch !is null) spriteBatch.SwapChain(); 231 | Renderer.SwapBuffers(); 232 | 233 | // Update frametime delta 234 | frameTimeDelta = cast(double)((SDL_GetPerformanceCounter() - frameTimeStart) * 1000) / cast(double)SDL_GetPerformanceFrequency(); 235 | times.updateDelta(frameTimeDelta); 236 | frameTimeLast = frameTimeStart; 237 | 238 | return false; 239 | } 240 | 241 | 242 | /** 243 | Prepare the backend for use. 244 | */ 245 | void Prepare(bool waitForVisible = true) { 246 | // Preupdate before init, just in case some event functions are use there. 247 | if (events !is null) events.Update(); 248 | 249 | //Wait for window to open. 250 | Logger.Debug("~~~ Init ~~~"); 251 | while (waitForVisible && !window.Visible) {} 252 | 253 | //Update window info. 254 | window.UpdateState(); 255 | if (events !is null) { 256 | events.OnExitRequested ~= (void* sender, EventArgs data) { 257 | window.Close(); 258 | }; 259 | 260 | events.OnWindowSizeChanged ~= (void* sender, GameResizeEventArgs data) { 261 | window.UpdateState(); 262 | Renderer.AdjustViewport(); 263 | OnWindowSizeChanged(sender, data); 264 | }; 265 | } 266 | 267 | times = new GameTime(new GameTimeSpan(0), new GameTimeSpan(0)); 268 | 269 | // Init sprite batch 270 | this.spriteBatch = new SpriteBatch(); 271 | this.Content = new ContentManager(); 272 | 273 | if (enableAudio) DefaultAudioDevice = new AudioDevice(); 274 | 275 | Init(); 276 | LoadContent(); 277 | Logger.Debug("~~~ Gameloop ~~~"); 278 | } 279 | 280 | /** 281 | Quits the game 282 | */ 283 | void Quit() { 284 | import polyplex.core.audio.music; 285 | 286 | 287 | // Stop music thread(s) and wait... 288 | stopMusicThread(); 289 | 290 | Logger.Info("Cleaning up resources..."); 291 | UnloadContent(); 292 | 293 | destroy(DefaultAudioDevice); 294 | destroy(window); 295 | 296 | GC.collect(); 297 | 298 | Logger.Success("Cleanup completed..."); 299 | Logger.Success("~~~ GAME ENDED ~~~"); 300 | } 301 | } 302 | 303 | -------------------------------------------------------------------------------- /source/polyplex/core/input/controller.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.input.controller; 2 | 3 | public class Controller { 4 | // TODO: Implement controller support 5 | public void Update() { 6 | 7 | } 8 | 9 | public void Refresh() { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /source/polyplex/core/input/keyboard.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.input.keyboard; 2 | import bindbc.sdl; 3 | import std.stdio; 4 | import std.conv; 5 | 6 | enum Keys { 7 | Unknown, 8 | A = SDL_SCANCODE_A, 9 | B = SDL_SCANCODE_B, 10 | C = SDL_SCANCODE_C, 11 | D = SDL_SCANCODE_D, 12 | E = SDL_SCANCODE_E, 13 | F = SDL_SCANCODE_F, 14 | G = SDL_SCANCODE_G, 15 | H = SDL_SCANCODE_H, 16 | I = SDL_SCANCODE_I, 17 | J = SDL_SCANCODE_J, 18 | K = SDL_SCANCODE_K, 19 | L = SDL_SCANCODE_L, 20 | M = SDL_SCANCODE_M, 21 | N = SDL_SCANCODE_N, 22 | O = SDL_SCANCODE_O, 23 | P = SDL_SCANCODE_P, 24 | Q = SDL_SCANCODE_Q, 25 | R = SDL_SCANCODE_R, 26 | S = SDL_SCANCODE_S, 27 | T = SDL_SCANCODE_T, 28 | U = SDL_SCANCODE_U, 29 | V = SDL_SCANCODE_V, 30 | W = SDL_SCANCODE_W, 31 | X = SDL_SCANCODE_X, 32 | Y = SDL_SCANCODE_Y, 33 | Z = SDL_SCANCODE_Z, 34 | One = SDL_SCANCODE_1, 35 | Two = SDL_SCANCODE_2, 36 | Three = SDL_SCANCODE_3, 37 | Four = SDL_SCANCODE_4, 38 | Five = SDL_SCANCODE_5, 39 | Six = SDL_SCANCODE_6, 40 | Seven = SDL_SCANCODE_7, 41 | Eight = SDL_SCANCODE_8, 42 | Nine = SDL_SCANCODE_9, 43 | Zero = SDL_SCANCODE_0, 44 | Return = SDL_SCANCODE_RETURN, 45 | Escape = SDL_SCANCODE_ESCAPE, 46 | Backspace = SDL_SCANCODE_BACKSPACE, 47 | Tab = SDL_SCANCODE_TAB, 48 | Space = SDL_SCANCODE_SPACE, 49 | Minus = SDL_SCANCODE_MINUS, 50 | Equals = SDL_SCANCODE_EQUALS, 51 | LeftBracket = SDL_SCANCODE_LEFTBRACKET, 52 | RightBracket = SDL_SCANCODE_RIGHTBRACKET, 53 | Backslash = SDL_SCANCODE_BACKSLASH, 54 | Nonuhash = SDL_SCANCODE_NONUSHASH, 55 | Semicolon = SDL_SCANCODE_SEMICOLON, 56 | Apostrophe = SDL_SCANCODE_APOSTROPHE, 57 | Grave = SDL_SCANCODE_GRAVE, 58 | Comma = SDL_SCANCODE_COMMA, 59 | Period = SDL_SCANCODE_PERIOD, 60 | Slash = SDL_SCANCODE_SLASH, 61 | Capslock = SDL_SCANCODE_CAPSLOCK, 62 | F1 = SDL_SCANCODE_F1, 63 | F2 = SDL_SCANCODE_F2, 64 | F3 = SDL_SCANCODE_F3, 65 | F4 = SDL_SCANCODE_F4, 66 | F5 = SDL_SCANCODE_F5, 67 | F6 = SDL_SCANCODE_F6, 68 | F7 = SDL_SCANCODE_F7, 69 | F8 = SDL_SCANCODE_F8, 70 | F9 = SDL_SCANCODE_F9, 71 | F10 = SDL_SCANCODE_F10, 72 | F11 = SDL_SCANCODE_F11, 73 | F12 = SDL_SCANCODE_F12, 74 | Execute = SDL_SCANCODE_EXECUTE, 75 | Help = SDL_SCANCODE_HELP, 76 | Menu = SDL_SCANCODE_MENU, 77 | Select = SDL_SCANCODE_SELECT, 78 | Stop = SDL_SCANCODE_STOP, 79 | Again = SDL_SCANCODE_AGAIN, 80 | Undo = SDL_SCANCODE_UNDO, 81 | Cut = SDL_SCANCODE_CUT, 82 | Copy = SDL_SCANCODE_COPY, 83 | Paste = SDL_SCANCODE_PASTE, 84 | Find = SDL_SCANCODE_FIND, 85 | Mute = SDL_SCANCODE_MUTE, 86 | VolumeUp = SDL_SCANCODE_VOLUMEUP, 87 | VolumeDown = SDL_SCANCODE_VOLUMEDOWN, 88 | KPComma = SDL_SCANCODE_KP_COMMA, 89 | KPEqual = SDL_SCANCODE_KP_EQUALS, 90 | International1, 91 | International2, 92 | International3, 93 | International4, 94 | International5, 95 | International6, 96 | International7, 97 | International8, 98 | International9, 99 | Lang1, 100 | Lang2, 101 | Lang3, 102 | Lang4, 103 | Lang5, 104 | Lang6, 105 | Lang7, 106 | Lang8, 107 | Lang9, 108 | AltErase = SDL_SCANCODE_ALTERASE, 109 | Sysreq = SDL_SCANCODE_SYSREQ, 110 | Cancel = SDL_SCANCODE_CANCEL, 111 | Clear = SDL_SCANCODE_CLEAR, 112 | Prior = SDL_SCANCODE_PRIOR, 113 | Return2 = SDL_SCANCODE_RETURN2, 114 | Seperator = SDL_SCANCODE_SEPARATOR, 115 | Out = SDL_SCANCODE_OUT, 116 | Oper = SDL_SCANCODE_OPER, 117 | ClearAgain = SDL_SCANCODE_CLEARAGAIN, 118 | LeftControl = SDL_SCANCODE_LCTRL, 119 | LeftShift = SDL_SCANCODE_LSHIFT, 120 | LeftAlt = SDL_SCANCODE_LALT, 121 | LeftGUI = SDL_SCANCODE_LGUI, 122 | RightControl = SDL_SCANCODE_RCTRL, 123 | RightShift = SDL_SCANCODE_RSHIFT, 124 | RightAlt = SDL_SCANCODE_RALT, 125 | RightGUI = SDL_SCANCODE_RGUI, 126 | Mode, 127 | AudioNext, 128 | AudioPrevious, 129 | AudioStop, 130 | AudioPlay, 131 | AudioMute, 132 | MediaSelect, 133 | WWW, 134 | Mail, 135 | Calculator, 136 | Computer, 137 | ACSearch, 138 | ACHome, 139 | ACBack, 140 | ACForward, 141 | ACStop, 142 | ACRefresh, 143 | ACBookmarks, 144 | BrightnessDown, 145 | BrightnessUp, 146 | DisplaySwitch, 147 | KBDIlluminationToggle, 148 | KBDIlluminationDown, 149 | KBDIlluminationUp, 150 | Eject, 151 | Sleep, 152 | App1, 153 | App2, 154 | Up = SDL_SCANCODE_UP, 155 | Down = SDL_SCANCODE_DOWN, 156 | Left = SDL_SCANCODE_LEFT, 157 | Right = SDL_SCANCODE_RIGHT, 158 | 159 | ScancodeCount = SDL_NUM_SCANCODES 160 | } 161 | 162 | enum KeyState { 163 | Up, 164 | Down 165 | } 166 | 167 | public class KeyboardState { 168 | private ubyte[] key_states; 169 | 170 | this() { 171 | this.key_states = new ubyte[Keys.ScancodeCount]; 172 | } 173 | 174 | this(ubyte* key_states, int size) { 175 | // SDL wants to override the values, so we manually copy them in to another array. 176 | this.key_states = new ubyte[size]; 177 | foreach(i; 0 .. size) { 178 | this.key_states[i] = key_states[i]; 179 | } 180 | } 181 | 182 | /** 183 | Returns true if the specified key is down (pressed). 184 | */ 185 | public bool IsKeyDown(Keys key) { 186 | if (key_states[key] == 1) return true; 187 | return false; 188 | } 189 | 190 | /** 191 | Returns true if all of the specified keys are down (pressed). 192 | */ 193 | public bool AreKeysDown(Keys[] kees) { 194 | foreach(Keys k; kees) { 195 | if (IsKeyUp(k)) return false; 196 | } 197 | return true; 198 | } 199 | 200 | /** 201 | Returns true if the specified key is up (not pressed). 202 | */ 203 | public bool IsKeyUp(Keys key) { 204 | return !IsKeyDown(key); 205 | } 206 | 207 | /** 208 | Returns true if all of the specified keys are up (not pressed). 209 | */ 210 | public bool AreKeysUp(Keys[] kees) { 211 | foreach(Keys k; kees) { 212 | if (IsKeyDown(k)) return false; 213 | } 214 | return true; 215 | } 216 | } 217 | 218 | public class Keyboard { 219 | 220 | private static KeyboardState backupState; 221 | 222 | /** 223 | Returns the current state of the keyboard. 224 | */ 225 | public static KeyboardState GetState() { 226 | int* elements = new int; 227 | ubyte* arr = SDL_GetKeyboardState(elements); 228 | 229 | // Make sure we do actually recieve any data. 230 | if (elements is null) return backupState; 231 | 232 | // If so, we update our backup state and return the new state 233 | backupState = new KeyboardState(arr, *elements); 234 | return backupState; 235 | } 236 | } -------------------------------------------------------------------------------- /source/polyplex/core/input/mouse.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.input.mouse; 2 | import polyplex.core.events; 3 | import bindbc.sdl; 4 | import polyplex.math; 5 | 6 | import std.stdio; 7 | 8 | enum MouseButton : ubyte { 9 | 10 | //Left Mouse Button 11 | Left = SDL_BUTTON_LEFT, 12 | 13 | //Middle Mouse Button 14 | Middle = SDL_BUTTON_MIDDLE, 15 | 16 | //Right Mouse Button 17 | Right = SDL_BUTTON_RIGHT 18 | } 19 | 20 | public class MouseState { 21 | private Vector3 pos; 22 | private float scroll; 23 | private int btn_mask; 24 | 25 | this(int x, int y, int btn_mask, float scrolled) { 26 | this.pos = Vector3(x, y, scrolled); 27 | this.btn_mask = btn_mask; 28 | } 29 | 30 | ubyte SDLButton(ubyte x) { 31 | return cast(ubyte)(1 << (x-1)); 32 | } 33 | 34 | /** 35 | Returns true if the specified MouseButton is pressed. 36 | */ 37 | public bool IsButtonPressed(MouseButton button) { 38 | if (btn_mask & SDLButton(button)) return true; 39 | return false; 40 | } 41 | 42 | /** 43 | Returns true if the specified MouseButton is released (not pressed). 44 | */ 45 | public bool IsButtonReleased(MouseButton button) { 46 | return !IsButtonPressed(button); 47 | } 48 | 49 | /** 50 | Returns the position and scroll for the mouse. 51 | Z = Scrollwheel 52 | */ 53 | public Vector3 Position() { 54 | return pos; 55 | } 56 | } 57 | 58 | public class Mouse { 59 | 60 | private static int lastX = 0; 61 | private static int lastY = 0; 62 | 63 | /** 64 | Returns the current state of the mouse. 65 | */ 66 | public static MouseState GetState() { 67 | 68 | // Cursed allocation 69 | int* x = new int; 70 | int* y = new int; 71 | immutable(int) mask = SDL_GetMouseState(x, y); 72 | 73 | // Get last state if we're null 74 | if (x is null) x = &lastX; 75 | if (y is null) y = &lastY; 76 | 77 | // Update last state to newest state 78 | lastX = *x; 79 | lastY = *y; 80 | 81 | float scroll = 0; 82 | foreach(SDL_Event ev; PPEvents.Events) { 83 | if (ev.type == SDL_MOUSEWHEEL) { 84 | scroll = ev.wheel.y; 85 | } 86 | } 87 | return new MouseState(*x, *y, mask, scroll); 88 | } 89 | 90 | /** 91 | Gets the current state of the mouse. 92 | */ 93 | public static Vector2 Position() { 94 | 95 | // Cursed allocation 96 | int* x = new int; 97 | int* y = new int; 98 | SDL_GetMouseState(x, y); 99 | 100 | // Get last state if we're null 101 | if (x is null) x = &lastX; 102 | if (y is null) y = &lastY; 103 | 104 | // Update last state to newest state 105 | lastX = *x; 106 | lastY = *y; 107 | 108 | return Vector2(*x, *y); 109 | } 110 | } -------------------------------------------------------------------------------- /source/polyplex/core/input/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.input; 2 | public import polyplex.core.input.keyboard; 3 | public import polyplex.core.input.mouse; 4 | public import polyplex.core.input.controller; 5 | public import polyplex.core.input.touch; 6 | -------------------------------------------------------------------------------- /source/polyplex/core/input/touch.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.input.touch; 2 | import polyplex.math; 3 | import bindbc.sdl; 4 | 5 | 6 | public struct FingerState { 7 | /** 8 | The SDL id for the finger. 9 | */ 10 | public SDL_FingerID Id; 11 | 12 | /** 13 | The origin device the finger was registered on. 14 | */ 15 | public int DeviceId; 16 | 17 | /** 18 | Position in X and Y axis, pressure on Z. 19 | All values are normalized (between 0 and 1) 20 | */ 21 | public Vector3 Position; 22 | } 23 | 24 | public class TouchState { 25 | public FingerState[] Fingers; 26 | } 27 | 28 | public class Touch { 29 | private static int touch_devices = -1; 30 | 31 | /** 32 | Returns the amount of touch devices connected to the system. 33 | Calling this will aswell update the internal counter. 34 | */ 35 | public static int Count() { 36 | if (touch_devices != SDL_GetNumTouchDevices()) touch_devices = SDL_GetNumTouchDevices(); 37 | return touch_devices; 38 | } 39 | 40 | /** 41 | Returns the state for current touch screen. (-1 for all touch screens.) 42 | */ 43 | public static TouchState GetState(int touch_id = -1) { 44 | // Get the number of touch screens connected to this system. 45 | if (touch_devices == 0) touch_devices = SDL_GetNumTouchDevices(); 46 | 47 | TouchState st = new TouchState(); 48 | if (touch_id == -1) { 49 | foreach(touch_dev; 0 .. touch_devices) { 50 | int fingers = SDL_GetNumTouchFingers(touch_dev); 51 | foreach(finger; 0 .. fingers) { 52 | SDL_Finger* f = SDL_GetTouchFinger(touch_dev, finger); 53 | st.Fingers ~= FingerState(f.id, touch_dev, Vector3(f.x, f.y, f.pressure)); 54 | } 55 | } 56 | } else { 57 | int fingers = SDL_GetNumTouchFingers(touch_id); 58 | foreach(finger; 0 .. fingers) { 59 | SDL_Finger* f = SDL_GetTouchFinger(touch_id, finger); 60 | st.Fingers ~= FingerState(f.id, touch_id, Vector3(f.x, f.y, f.pressure)); 61 | } 62 | } 63 | return st; 64 | } 65 | } -------------------------------------------------------------------------------- /source/polyplex/core/locale/language.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.locale.language; 2 | 3 | enum KeyboardLocale { 4 | English, 5 | Unicode 6 | } -------------------------------------------------------------------------------- /source/polyplex/core/locale/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.locale; -------------------------------------------------------------------------------- /source/polyplex/core/locale/translation.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.locale.translation; -------------------------------------------------------------------------------- /source/polyplex/core/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core; 2 | 3 | public import polyplex.core.game; 4 | public import polyplex.core.render; 5 | public import polyplex.core.window; 6 | public import polyplex.core.locale; 7 | public import polyplex.core.input; 8 | public import polyplex.core.content; 9 | public import polyplex.core.color; 10 | public import polyplex.core.windows; 11 | public import polyplex.core.audio; 12 | public import polyplex.core.time; 13 | 14 | import polyplex; 15 | import polyplex.utils; 16 | 17 | public class BasicGameLauncher 18 | { 19 | /** 20 | Removes the first element in the arguments array. 21 | (this value is generally the name of the executable and unneeded.) 22 | */ 23 | public static string[] ProcessArgs(string[] args) { 24 | return args[1..$]; 25 | } 26 | 27 | private static void launch(Game game, string[] args) { 28 | foreach (arg; args) { 29 | if (arg == "--no-autofocus") { 30 | game.Window.AutoFocus = false; 31 | } 32 | else if (arg == "--opengl") { 33 | ChosenBackend = GraphicsBackend.OpenGL; 34 | } 35 | } 36 | Logger.Info("Set rendering backend to {0}...", ChosenBackend); 37 | do_launch(game); 38 | } 39 | 40 | private static void do_launch(Game game) { 41 | ChosenBackend = GraphicsBackend.OpenGL; 42 | game.Run(); 43 | } 44 | public static void LaunchGame(Game g, string[] args = []) { 45 | Logger.Debug("Launching {0} with args {1}...", g.Window.Title, args); 46 | launch(g, args); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/polyplex/core/render/camera.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.camera; 2 | import polyplex.math; 3 | import std.stdio; 4 | 5 | /** 6 | A basis of a camera. 7 | */ 8 | public class Camera { 9 | protected Matrix4x4 matrix; 10 | protected float znear; 11 | protected float zfar; 12 | 13 | public @property Matrix4x4 Matrix() { Update(); return this.matrix; } 14 | public @property void Matrix(Matrix4x4 value) { this.matrix = value; Update(); } 15 | 16 | /** 17 | Update the camera rotation, scale and translation. 18 | */ 19 | public abstract void Update(); 20 | 21 | /** 22 | Creates a dimension dependant projection. (Recommended) 23 | */ 24 | public abstract Matrix4x4 Project(float width, float height); 25 | 26 | /** 27 | Creates an perspective projection with the specified width and height. 28 | */ 29 | public Matrix4x4 ProjectPerspective(float width, float fov, float height) { 30 | return Matrix4x4.Perspective(width, height, fov, znear, zfar); 31 | } 32 | 33 | /** 34 | Creates an othrographic projection with the specified width and height. 35 | */ 36 | public Matrix4x4 ProjectOrthographic(float width, float height) { 37 | return Matrix4x4.Identity.Orthographic(0f, width, height, 0f, znear, zfar); 38 | } 39 | 40 | /** 41 | Creates an othrographic projection with the specified width and height. 42 | */ 43 | public Matrix4x4 ProjectOrthographicInv(float width, float height) { 44 | return Matrix4x4.Identity.Orthographic(0f, width, 0f, height, znear, zfar); 45 | } 46 | } 47 | 48 | /** 49 | A basic 3D camera, optimized for use in 3D environments. 50 | */ 51 | public class Camera3D : Camera { 52 | private float fov; 53 | public Vector3 Position; 54 | public Quaternion Rotation; 55 | public float Zoom; 56 | 57 | 58 | this(Vector3 position, Quaternion rotation = Quaternion.Identity, float zoom = 1f, float near = 0.1f, float far = 100f, float fov = 90f) { 59 | this.Position = position; 60 | this.Rotation = rotation; 61 | this.Zoom = zoom; 62 | 63 | this.znear = near; 64 | this.zfar = far; 65 | this.fov = fov; 66 | Update(); 67 | } 68 | 69 | /** 70 | Update the camera rotation, scale and translation. 71 | */ 72 | public override void Update() { 73 | this.matrix = Matrix4x4.Identity; 74 | this.matrix 75 | .Translate(Position) 76 | .RotateX(this.Rotation.X) 77 | .RotateY(this.Rotation.Y) 78 | .RotateZ(this.Rotation.Z) 79 | .Scale(Vector3(Zoom, Zoom, Zoom)); 80 | } 81 | 82 | /** 83 | Creates an perspective projection with the specified width and height. 84 | */ 85 | public override Matrix4x4 Project(float width, float height) { 86 | return ProjectPerspective(width, this.fov, height); 87 | } 88 | } 89 | 90 | public class Camera2D : Camera { 91 | public Vector2 Position; 92 | public float Rotation; 93 | public float RotationY; 94 | public float RotationX; 95 | public float Zoom; 96 | public float Depth; 97 | public Vector2 Origin; 98 | 99 | this(Vector2 position, float depth = 0f, float rotation = 0f, float zoom = 1f, float near = 0f, float far = 100f) { 100 | this.Position = Vector2(position.X, position.Y); 101 | this.Rotation = rotation; 102 | this.RotationX = 0; 103 | this.RotationY = 0; 104 | this.Zoom = zoom; 105 | this.Depth = depth; 106 | this.Origin = Vector2(0, 0); 107 | 108 | this.znear = near; 109 | this.zfar = far; 110 | this.matrix = Matrix4x4.Identity; 111 | Update(); 112 | } 113 | 114 | /** 115 | Update the camera rotation, scale and translation. 116 | */ 117 | public override void Update() { 118 | this.matrix = Matrix4x4.Identity 119 | .Translate(Vector3(-Position.X, -Position.Y, Depth)) 120 | .RotateX(-this.RotationX) 121 | .RotateY(-this.RotationY) 122 | .RotateZ(-this.Rotation) 123 | .Scale(Vector3(Zoom, Zoom, Zoom)) 124 | .Translate(Vector3(Origin.X, Origin.Y, Depth)); 125 | } 126 | 127 | /** 128 | Creates an othrographic projection with the specified width and height. 129 | */ 130 | public override Matrix4x4 Project(float width, float height) { 131 | return ProjectOrthographic(width, height); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/polyplex/core/render/common.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.common; 2 | import polyplex.core.render; 3 | 4 | /** 5 | The code of a shader 6 | */ 7 | class ShaderCode { 8 | private: 9 | ShaderLang language; 10 | 11 | public: 12 | @property ShaderLang Language() { return language; } 13 | string Vertex; 14 | string Fragment; 15 | string Geometry; 16 | 17 | this() {} 18 | 19 | this(string vertex, string fragment) { 20 | this.Vertex = vertex; 21 | language = ShaderLang.GLSL; 22 | if (this.Vertex[0..5] == "shader") { 23 | language = ShaderLang.PPSL; 24 | } 25 | this.Fragment = fragment; 26 | } 27 | 28 | this(string vertex, string geometry, string fragment) { 29 | this.Vertex = vertex; 30 | this.Geometry = geometry; 31 | language = ShaderLang.GLSL; 32 | if (this.Vertex[0..5] == "shader") { 33 | language = ShaderLang.PPSL; 34 | } 35 | this.Fragment = fragment; 36 | } 37 | } -------------------------------------------------------------------------------- /source/polyplex/core/render/enums.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.enums; 2 | 3 | 4 | public enum VSyncState { 5 | LateTearing = -1, 6 | Immidiate = 0, 7 | VSync = 1 8 | } 9 | 10 | public enum Blending { 11 | Opqaue, 12 | AlphaBlend, 13 | NonPremultiplied, 14 | Additive 15 | } 16 | 17 | public enum ProjectionState { 18 | Orthographic, 19 | Perspective 20 | } 21 | 22 | public enum Sampling { 23 | AnisotropicClamp, 24 | AnisotropicWrap, 25 | AnisotropicMirror, 26 | LinearClamp, 27 | LinearWrap, 28 | LinearMirror, 29 | PointClamp, 30 | PointMirror, 31 | PointWrap 32 | } 33 | 34 | public enum SpriteSorting { 35 | BackToFront, 36 | Deferred, 37 | FrontToBack, 38 | Immediate, 39 | Texture 40 | } 41 | 42 | public struct RasterizerState { 43 | public: 44 | static RasterizerState Default() { 45 | return RasterizerState(true, false, false, false, 0f); 46 | } 47 | 48 | bool BackfaceCulling; 49 | bool DepthTest; 50 | bool ScissorTest; 51 | bool MSAA; 52 | float SlopeScaleBias; 53 | } 54 | 55 | /* 56 | public enum Stencil { 57 | Default, 58 | DepthRead, 59 | None 60 | }*/ 61 | 62 | public enum SpriteFlip { 63 | None = 0x0, 64 | FlipVertical = 0x1, 65 | FlipHorizontal = 0x2 66 | } 67 | 68 | enum ShaderType { 69 | Vertex, 70 | Geometry, 71 | Fragment, 72 | 73 | Stencil, 74 | Compute 75 | } 76 | 77 | enum ShaderLang { 78 | PPSL, 79 | GLSL 80 | } 81 | -------------------------------------------------------------------------------- /source/polyplex/core/render/gl/debug2d.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.gl.debug2d; 2 | import polyplex.core.render; 3 | import polyplex.core.render.gl.shader; 4 | import polyplex.core.render.gl.objects; 5 | import polyplex.core.render.camera; 6 | import polyplex.core.color; 7 | import bindbc.opengl; 8 | import bindbc.opengl.gl; 9 | import polyplex.utils; 10 | import polyplex.math; 11 | import polyplex.utils.mathutils; 12 | 13 | import std.stdio; 14 | 15 | private struct DebugVertexLayout { 16 | Vector2 ppPosition; 17 | Vector4 ppColor; 18 | } 19 | 20 | public class Debugging2D { 21 | private static Shader shader; 22 | private static Camera2D cm; 23 | 24 | private static VertexBuffer!(DebugVertexLayout, Layout.Interleaved) buff; 25 | private static int matr_indx; 26 | 27 | /** 28 | Prepares GlDebugging2D for rendering (backend, you don't need to run this yourself.) 29 | */ 30 | public static void PrepDebugging() { 31 | buff = VertexBuffer!(DebugVertexLayout, Layout.Interleaved)([]); 32 | shader = new Shader(new ShaderCode(import("debug.vert"), import("debug.frag"))); 33 | matr_indx = shader.GetUniform("ppProjection"); 34 | cm = new Camera2D(Vector2(0, 0)); 35 | cm.Update(); 36 | } 37 | 38 | private static void create_buffer(Rectangle rect, Color color) { 39 | buff.Data = [ 40 | DebugVertexLayout(Vector2(rect.X, rect.Y), color.GLfColor()), 41 | DebugVertexLayout(Vector2(rect.X, rect.Y+rect.Height), color.GLfColor()), 42 | DebugVertexLayout(Vector2(rect.X+rect.Width, rect.Y+rect.Height), color.GLfColor()), 43 | DebugVertexLayout(Vector2(rect.X, rect.Y), color.GLfColor()), 44 | DebugVertexLayout(Vector2(rect.X+rect.Width, rect.Y), color.GLfColor()), 45 | DebugVertexLayout(Vector2(rect.X+rect.Width, rect.Y+rect.Height), color.GLfColor()), 46 | ]; 47 | } 48 | 49 | private static void create_buffer_line(Vector2[] lines, Color color) { 50 | buff.Data.length = lines.length*2; 51 | foreach(i; 1 .. lines.length) { 52 | buff.Data[(i*2)] = DebugVertexLayout(lines[i-1], color.GLfColor()); 53 | buff.Data[(i*2)+1] = DebugVertexLayout(lines[i], color.GLfColor()); 54 | } 55 | } 56 | 57 | private static void create_buffer_points(Vector2[] points, Color color) { 58 | buff.Data.length = points.length; 59 | foreach(i; 0 .. points.length) { 60 | buff.Data[i] = DebugVertexLayout(points[i], color.GLfColor()); 61 | } 62 | } 63 | 64 | /** 65 | Resets the matrix for the debugging primitives. 66 | */ 67 | public static void ResetCamera() { 68 | cm = new Camera2D(Vector2(0, 0)); 69 | } 70 | 71 | /** 72 | Sets the matrix for the debugging primitives. 73 | */ 74 | public static void SetCamera(Camera2D cam) { 75 | cm = cam; 76 | } 77 | 78 | /** 79 | Draws dots based on specified points and color. 80 | */ 81 | public static void DrawDots(Vector2[] dot_points, Color color) { 82 | create_buffer_points(dot_points, color); 83 | buff.UpdateBuffer(); 84 | shader.Attach(); 85 | shader.SetUniform(matr_indx, cm.Project(Renderer.Window.ClientBounds.Width, Renderer.Window.ClientBounds.Height) * cm.Matrix); 86 | buff.Draw(0, DrawType.Points); 87 | buff.Unbind(); 88 | shader.Detach(); 89 | } 90 | 91 | /** 92 | Draws lines based on specified points and color. 93 | */ 94 | public static void DrawLines(Vector2[] line_points, Color color) { 95 | if (line_points.length == 1) { 96 | DrawDots(line_points, color); 97 | return; 98 | } 99 | create_buffer_line(line_points, color); 100 | buff.UpdateBuffer(); 101 | shader.Attach(); 102 | shader.SetUniform(matr_indx, cm.Project(Renderer.Window.ClientBounds.Width, Renderer.Window.ClientBounds.Height) * cm.Matrix); 103 | buff.Draw(0, DrawType.Lines); 104 | buff.Unbind(); 105 | shader.Detach(); 106 | } 107 | 108 | /** 109 | Draws a line rectangle (2 triangles), with the specified color. 110 | */ 111 | public static void DrawRectangle(Rectangle rect, Color color = Color.White) { 112 | create_buffer(rect, color); 113 | buff.UpdateBuffer(); 114 | shader.Attach(); 115 | shader.SetUniform(matr_indx, cm.Project(Renderer.Window.ClientBounds.Width, Renderer.Window.ClientBounds.Height) * cm.Matrix); 116 | buff.Draw(0, DrawType.LineStrip); 117 | buff.Unbind(); 118 | shader.Detach(); 119 | } 120 | 121 | /** 122 | Draws a filled rectangle, with the specified color. 123 | */ 124 | public static void DrawRectangleFilled(Rectangle rect, Color color = Color.White) { 125 | create_buffer(rect, color); 126 | buff.UpdateBuffer(); 127 | shader.Attach(); 128 | shader.SetUniform(matr_indx, cm.Project(Renderer.Window.ClientBounds.Width, Renderer.Window.ClientBounds.Height) * cm.Matrix); 129 | buff.Draw(); 130 | shader.Detach(); 131 | buff.Unbind(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/polyplex/core/render/gl/objects.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.gl.objects; 2 | import polyplex.core.render.shapes; 3 | import polyplex.core.color; 4 | import polyplex.math; 5 | 6 | import bindbc.sdl; 7 | import bindbc.opengl; 8 | import bindbc.opengl.gl; 9 | import polyplex.utils.logging; 10 | import polyplex.utils.strutils; 11 | import std.stdio; 12 | import std.conv; 13 | import std.format; 14 | import std.traits; 15 | 16 | enum DrawType { 17 | LineStrip = GL_LINE_STRIP, 18 | TriangleStrip = GL_TRIANGLE_STRIP, 19 | Triangles = GL_TRIANGLES, 20 | Points = GL_POINTS, 21 | LineLoop = GL_LINE_LOOP, 22 | Lines = GL_LINES, 23 | } 24 | 25 | enum OptimizeMode { 26 | Mode3D, 27 | Mode2D 28 | } 29 | 30 | enum BufferMode { 31 | Static = GL_STATIC_DRAW, 32 | Dynamic = GL_DYNAMIC_DRAW 33 | } 34 | 35 | 36 | alias XBuffer = GLfloat[]; 37 | 38 | /** 39 | Layout is the way data is layed out in a buffer object. 40 | */ 41 | enum Layout { 42 | /** 43 | A layout where each element is seperated out into multiple buffers. 44 | [XXX], [YYY], [ZZZ] 45 | */ 46 | Seperated, 47 | 48 | /** 49 | A layout where each element is clustered into larger groups in one buffer. 50 | [XXXYYYZZZ] 51 | */ 52 | Batched, 53 | 54 | /** 55 | A layout where each element is clustered into smaller groups in one buffer. 56 | [XYZXYZXYZ] 57 | */ 58 | Interleaved 59 | } 60 | 61 | //Vertex Array Object contains state information to be sent to the GPU 62 | class XVertexArray(T, Layout layout) { 63 | 64 | private GLuint id; 65 | public @property uint Id() { return cast(uint)id; } 66 | 67 | this() { 68 | glGenVertexArrays(1, &id); 69 | } 70 | 71 | ~this() { 72 | glDeleteVertexArrays(1, &id); 73 | } 74 | 75 | void Bind() { 76 | glBindVertexArray(id); 77 | } 78 | 79 | void Unbind() { 80 | glBindVertexArray(0); 81 | } 82 | } 83 | 84 | // Make sure valid types are in the struct 85 | // TODO: Add type support for ints, and such. 86 | enum ValidBufferType(T) = (is(T == float)) || (IsVector!T && is(T.Type == float)); 87 | 88 | struct VertexBuffer(T, Layout layout) { 89 | XVertexArray!(T, layout) vao; 90 | private GLuint[] gl_buffers; 91 | public T[] Data; 92 | 93 | 94 | this(T input) { 95 | this([input]); 96 | } 97 | 98 | this(T[] input) { 99 | vao = new XVertexArray!(T, layout)(); 100 | this.Data = input; 101 | 102 | vao.Bind(); 103 | 104 | // Generate GL buffers. 105 | static if(layout == Layout.Seperated) { 106 | int struct_member_count = cast(int)__traits(allMembers, T).length; 107 | gl_buffers.length = struct_member_count; 108 | glGenBuffers(struct_member_count, gl_buffers.ptr); 109 | } else { 110 | gl_buffers.length = 1; 111 | glGenBuffers(1, gl_buffers.ptr); 112 | UpdateBuffer(); 113 | } 114 | } 115 | 116 | ~this() { 117 | foreach(int iterator, string member; __traits(allMembers, T)) { 118 | glDisableVertexAttribArray(iterator); 119 | } 120 | glDeleteBuffers(cast(GLsizei)gl_buffers.length, gl_buffers.ptr); 121 | destroy(vao); 122 | } 123 | 124 | public void UpdateAttribPointers() { 125 | if (Data.length == 0) return; 126 | foreach(int iterator, string member; __traits(allMembers, T)) { 127 | // Use a mixin to get the offset values. 128 | mixin(q{int field_size = T.{0}.sizeof/4;}.Format(member)); 129 | mixin(q{void* field_t = cast(void*)T.{0}.offsetof;}.Format(member)); 130 | 131 | // Check if it's a valid type for the VBO buffer. 132 | mixin(q{ alias M = T.{0}; }.Format(member)); 133 | static assert(ValidBufferType!(typeof(M)), "Invalid buffer value <{0}>, may only contain: float, vector2, vector3 and vector4s! (contains {1})".Format(member, typeof(M).stringof)); 134 | 135 | if (layout == Layout.Seperated) { 136 | Bind(iterator+1); 137 | UpdateBuffer(iterator+1); 138 | glVertexAttribPointer(iterator, field_size, GL_FLOAT, GL_FALSE, 0, null); 139 | glEnableVertexAttribArray(iterator); 140 | } else { 141 | Bind(); 142 | glVertexAttribPointer(cast(GLuint)iterator, field_size, GL_FLOAT, GL_FALSE, T.sizeof, field_t); 143 | glEnableVertexAttribArray(iterator); 144 | } 145 | } 146 | } 147 | 148 | public void Bind(int index = 0) { 149 | vao.Bind(); 150 | glBindBuffer(GL_ARRAY_BUFFER, gl_buffers[index]); 151 | } 152 | 153 | public void Unbind() { 154 | glBindBuffer(GL_ARRAY_BUFFER, 0); 155 | vao.Unbind(); 156 | } 157 | 158 | public void UpdateBuffer(int index = 0, BufferMode mode = BufferMode.Dynamic) { 159 | Bind(index); 160 | glBufferData(GL_ARRAY_BUFFER, Data.length * T.sizeof, Data.ptr, mode); 161 | UpdateAttribPointers(); 162 | } 163 | 164 | public void UpdateSubData(int index = 0, GLintptr offset = 0, GLsizeiptr size = 0) { 165 | Bind(index); 166 | glBufferSubData(GL_ARRAY_BUFFER, offset, T.sizeof*size, cast(void*)Data.ptr); 167 | UpdateAttribPointers(); 168 | } 169 | 170 | public void Draw(int amount = 0, DrawType dt = DrawType.Triangles) { 171 | vao.Bind(); 172 | if (amount == 0) glDrawArrays(dt, 0, cast(GLuint)this.Data.length); 173 | else glDrawArrays(dt, 0, amount); 174 | uint err = glGetError(); 175 | import std.format; 176 | if (err != GL_NO_ERROR) { 177 | if (err == 0x500) throw new Exception("GLSL Invalid Enum Error ("~format!"%x"(err)~")!"); 178 | if (err == 0x502) throw new Exception("GLSL Invalid Operation Error ("~format!"%x"(err)~")! (Are you sending the right types? Uniforms count!)"); 179 | throw new Exception("Unhandled GLSL Exception."); 180 | } 181 | } 182 | } 183 | 184 | struct IndexBuffer { 185 | public GLuint[] Indices; 186 | public GLuint Id; 187 | 188 | this(GLuint[] indices) { 189 | glGenBuffers(1, &Id); 190 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Id); 191 | this.Indices = indices; 192 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.sizeof, indices.ptr, GL_DYNAMIC_DRAW); 193 | } 194 | 195 | public void Bind() { 196 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Id); 197 | } 198 | 199 | public void Unbind() { 200 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 201 | } 202 | 203 | public void UpdateBuffer(int index = 0, BufferMode mode = BufferMode.Dynamic) { 204 | Bind(); 205 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, Indices.sizeof, Indices.ptr, mode); 206 | } 207 | 208 | public void UpdateSubData(int index = 0, GLintptr offset = 0, GLsizeiptr size = 0) { 209 | Bind(); 210 | glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, size, cast(void*)Indices.ptr); 211 | } 212 | } 213 | 214 | public enum FBOTextureType { 215 | Color, 216 | Depth, 217 | Stencil, 218 | DepthStencil 219 | } 220 | 221 | /// WORK IN PROGRESS! 222 | 223 | class FrameBuffer { 224 | // Internal managment value to make sure that the IsComplete function can revert to the userbound FBO. 225 | private static GLuint current_attch; 226 | 227 | private GLuint id; 228 | 229 | private GLuint[FBOTextureType] render_textures; 230 | 231 | private int width; 232 | private int height; 233 | 234 | public int Width() { return width; } 235 | public int Height() { return height; } 236 | 237 | public bool IsComplete() { 238 | Bind(); 239 | bool status = (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); 240 | glBindFramebuffer(GL_FRAMEBUFFER, current_attch); 241 | return status; 242 | } 243 | 244 | this() { 245 | glGenFramebuffers(1, &id); 246 | } 247 | 248 | ~this() { 249 | glDeleteFramebuffers(1, &id); 250 | } 251 | 252 | public void SetTexture(FBOTextureType type) { 253 | if (render_textures[type] != 0) { 254 | // Delete previous texture. 255 | glBindTexture(GL_TEXTURE, render_textures[type]); 256 | glDeleteTextures(1, &render_textures[type]); 257 | } 258 | 259 | render_textures[type] = 0; 260 | glGenTextures(1, &render_textures[type]); 261 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, null); 262 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 263 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 264 | 265 | } 266 | 267 | public void Rebuild(int width, int height) { 268 | this.width = width; 269 | this.height = height; 270 | } 271 | 272 | public void Bind() { 273 | // Make sure IsComplete state can be reversed to this again. 274 | current_attch = id; 275 | glBindFramebuffer(GL_FRAMEBUFFER, id); 276 | } 277 | 278 | public void Unbind() { 279 | // Make sure IsComplete state can be reversed to this again. 280 | current_attch = id; 281 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 282 | } 283 | 284 | 285 | } 286 | 287 | /* 288 | class IBO : BufferObject { 289 | this(Layout layout) { 290 | super(GL_ELEMENT_ARRAY_BUFFER, layout, null); 291 | } 292 | 293 | public override void Draw(int amount = 0) { 294 | throw new Exception("You can't draw an IBO, attach the IBO to a VBO instead."); 295 | } 296 | }*/ 297 | -------------------------------------------------------------------------------- /source/polyplex/core/render/gl/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.gl; 2 | public import polyplex.core.render.gl.batch; 3 | public import polyplex.core.render.gl.debug2d; 4 | public import polyplex.core.render.gl.renderbuf; 5 | import polyplex.core.render; 6 | import polyplex.core.render.gl.gloo; 7 | static import win = polyplex.core.window; 8 | import polyplex.core.color; 9 | import polyplex.utils; 10 | import polyplex.math; 11 | import polyplex; 12 | 13 | import bindbc.sdl; 14 | import bindbc.opengl; 15 | import bindbc.opengl.gl; 16 | import std.conv; 17 | import std.stdio; 18 | 19 | public import polyplex.core.render.gl.shader; 20 | 21 | // TODO: Remove SDL dependency from this. 22 | 23 | public class Renderer { 24 | static: 25 | private: 26 | win.Window window; 27 | Rectanglei scissorRect; 28 | 29 | package(polyplex): 30 | void setWindow(win.Window parent) { 31 | this.window = parent; 32 | } 33 | 34 | public: 35 | win.Window Window() { 36 | return window; 37 | } 38 | 39 | void Init() { 40 | // Create the neccesary rendering backend. 41 | Window.CreateContext(GraphicsBackend.OpenGL); 42 | 43 | loadOpenGL(); 44 | 45 | SpriteBatch.InitializeSpritebatch(); 46 | Debugging2D.PrepDebugging(); 47 | 48 | //Default settings for sprite clamping and wrapping 49 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 50 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 51 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 52 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 53 | 54 | Logger.Info("OpenGL version: {0}", to!string(glGetString(GL_VERSION))); 55 | GL.Enable (Capability.Blending); 56 | GL.BlendFunc (GL.SourceAlpha, GL.OneMinusSourceAlpha); 57 | 58 | // TODO: reimplement this. 59 | //Crash if system has unsupported opengl version. 60 | //if (glver < GLVersion.gl30) throw new Error("Sorry, your graphics card does not support Open GL 3 or above."); 61 | Logger.Info("OpenGL initialized..."); 62 | } 63 | 64 | @property Rectanglei ScissorRectangle() { 65 | return scissorRect; 66 | } 67 | 68 | @property void ScissorRectangle(Rectanglei rect) { 69 | scissorRect = rect; 70 | glScissor( 71 | scissorRect.X, 72 | (Renderer.Window.ClientBounds.Height-scissorRect.Y)-scissorRect.Height, 73 | scissorRect.Width, 74 | scissorRect.Height); 75 | } 76 | 77 | @property VSyncState VSync() { 78 | return Window.VSync; 79 | } 80 | 81 | @property void VSync(VSyncState state) { 82 | Window.VSync = state; 83 | } 84 | 85 | void AdjustViewport() { 86 | glViewport(0, 0, Window.ClientBounds.Width, Window.ClientBounds.Height); 87 | } 88 | 89 | void ClearColor(Color color) { 90 | glClearColor(color.Rf, color.Gf, color.Bf, color.Af); 91 | glClear(GL_COLOR_BUFFER_BIT); 92 | ClearDepth(); 93 | } 94 | 95 | void ClearDepth() { 96 | glClear(GL_DEPTH_BUFFER_BIT); 97 | } 98 | 99 | void SwapBuffers() { 100 | Window.SwapBuffers(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /source/polyplex/core/render/gl/renderbuf.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.gl.renderbuf; 2 | import core = polyplex.core.render; 3 | import polyplex.core.render.gl.gloo; 4 | import polyplex.core.render.gl.gloo : RBO = Renderbuffer, FBO = Framebuffer; 5 | import core.memory : GC; 6 | 7 | enum Attachment { 8 | Color0=GL_COLOR_ATTACHMENT0, 9 | Color1=GL_COLOR_ATTACHMENT1, 10 | Color2=GL_COLOR_ATTACHMENT2, 11 | Color3=GL_COLOR_ATTACHMENT3, 12 | Color4=GL_COLOR_ATTACHMENT4, 13 | Color5=GL_COLOR_ATTACHMENT5, 14 | Color6=GL_COLOR_ATTACHMENT6, 15 | Color7=GL_COLOR_ATTACHMENT7, 16 | Color8=GL_COLOR_ATTACHMENT8, 17 | Color9=GL_COLOR_ATTACHMENT9, 18 | Color10=GL_COLOR_ATTACHMENT10, 19 | Color11=GL_COLOR_ATTACHMENT11, 20 | Color12=GL_COLOR_ATTACHMENT12, 21 | Color13=GL_COLOR_ATTACHMENT13, 22 | Color14=GL_COLOR_ATTACHMENT14, 23 | Color15=GL_COLOR_ATTACHMENT15, 24 | Depth=GL_DEPTH_ATTACHMENT 25 | } 26 | 27 | public class Framebuffer { 28 | private: 29 | int width; 30 | int height; 31 | FBO fbo; 32 | RBO rbo; 33 | Texture[] renderTextures; 34 | GLenum[] drawBufs; 35 | 36 | void bufferTexture() { 37 | fbo.Bind(); 38 | 39 | // Prepare textures. 40 | foreach(to; renderTextures) { 41 | if (to is null) continue; 42 | to.Bind(TextureType.Tex2D); 43 | to.Image2D(TextureType.Tex2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 44 | to.SetParameter(TextureType.Tex2D, TextureParameter.MagFilter, GL.Nearest); 45 | to.SetParameter(TextureType.Tex2D, TextureParameter.MinFilter, GL.Nearest); 46 | to.Unbind(); 47 | } 48 | 49 | rbo.Bind(); 50 | rbo.Storage(GL_DEPTH_COMPONENT, width, height); 51 | fbo.Renderbuffer(FramebufferType.Multi, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo.Id); 52 | 53 | // Attach textures. 54 | foreach(i, to; renderTextures) { 55 | to.Bind(TextureType.Tex2D); 56 | fbo.Texture(GL_FRAMEBUFFER, drawBufs[i], to.Id, 0); 57 | to.Unbind(); 58 | } 59 | GL.DrawBuffers(cast(int)drawBufs.length, drawBufs.ptr); 60 | } 61 | 62 | void updateTextureBuffer() { 63 | 64 | } 65 | 66 | public: 67 | @property int Width() { 68 | return width; 69 | } 70 | 71 | @property int Height() { 72 | return height; 73 | } 74 | 75 | this(int width, int height, int colorAttachments = 1) { 76 | this.width = width; 77 | this.height = height; 78 | 79 | fbo = new FBO(); 80 | rbo = new RBO(); 81 | 82 | // Add color attachment textures 83 | foreach(i; 0..colorAttachments) { 84 | drawBufs ~= GL_COLOR_ATTACHMENT0+cast(uint)i; 85 | renderTextures ~= new Texture(); 86 | } 87 | 88 | // Setup framebuffer 89 | bufferTexture(); 90 | fbo.Unbind(); 91 | } 92 | ~this() { 93 | destroy(fbo); 94 | destroy(renderTextures); 95 | destroy(rbo); 96 | } 97 | 98 | Texture[] OutTextures() { 99 | return renderTextures; 100 | } 101 | 102 | /** 103 | Resize destroys the current framebuffer and recreates it with the specified size. 104 | */ 105 | void Resize(int width, int height) { 106 | immutable(size_t) colorAttachments = renderTextures.length; 107 | destroy(rbo); 108 | destroy(fbo); 109 | destroy(renderTextures); 110 | destroy(drawBufs); 111 | GC.collect(); 112 | 113 | 114 | this.width = width; 115 | this.height = height; 116 | 117 | fbo = new FBO(); 118 | rbo = new RBO(); 119 | 120 | // TODO: Optimize this. 121 | renderTextures = new Texture[colorAttachments]; 122 | foreach(i; 0..colorAttachments) { 123 | drawBufs ~= GL_COLOR_ATTACHMENT0+cast(uint)i; 124 | renderTextures[i] = new Texture(); 125 | } 126 | 127 | // Setup framebuffer 128 | bufferTexture(); 129 | fbo.Unbind(); 130 | } 131 | 132 | void Begin() { 133 | fbo.Bind(); 134 | GL.Viewport(0, 0, width, height); 135 | } 136 | 137 | void End() { 138 | GL.BindFramebuffer(GL_FRAMEBUFFER, 0); 139 | fbo.Unbind(); 140 | } 141 | } -------------------------------------------------------------------------------- /source/polyplex/core/render/gl/shader.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.gl.shader; 2 | import polyplex.core.render; 3 | import bindbc.sdl; 4 | import bindbc.opengl; 5 | import polyplex.math; 6 | import polyplex.utils.logging; 7 | import std.conv; 8 | 9 | 10 | class Shader { 11 | private GLuint shaderprogram; 12 | private GLuint vertexshader = 0; 13 | private GLuint fragmentshader = 0; 14 | private GLuint geometryshader = 0; 15 | private ShaderCode shadersource; 16 | 17 | this(ShaderCode code) { 18 | shadersource = code; 19 | compile_shaders(); 20 | link_shaders(); 21 | } 22 | 23 | ~this() { 24 | glUseProgram(0); 25 | glDetachShader(shaderprogram, vertexshader); 26 | if (geometryshader != 0) glDetachShader(shaderprogram, geometryshader); 27 | glDetachShader(shaderprogram, fragmentshader); 28 | glDeleteProgram(shaderprogram); 29 | glDeleteShader(vertexshader); 30 | if (geometryshader != 0) glDeleteShader(geometryshader); 31 | glDeleteShader(fragmentshader); 32 | } 33 | 34 | /** 35 | Vertex Shader Id. 36 | */ 37 | public @property GLchar* VertGL() { return cast(GLchar*)(shadersource.Vertex.ptr); } 38 | 39 | /** 40 | Fragment Shader Id. 41 | */ 42 | public @property GLchar* FragGL() { return cast(GLchar*)(shadersource.Fragment.ptr); } 43 | 44 | /** 45 | Geometry Shader Id. 46 | */ 47 | public @property GLchar* GeoGL() { return cast(GLchar*)(shadersource.Geometry.ptr); } 48 | 49 | /** 50 | Gets whenever this shader is attached. 51 | */ 52 | public @property bool Attached() { 53 | int i; 54 | glGetIntegerv(GL_CURRENT_PROGRAM, &i); 55 | if (i == shaderprogram) return true; 56 | return false; 57 | } 58 | 59 | //Uniform stuff. 60 | public void SetUniform(int location, float value) { if (!Attached) Attach(); glUniform1f(cast(GLint)location, cast(GLfloat)value); } 61 | public void SetUniform(int location, Vector2 value) { if (!Attached) Attach(); glUniform2f(cast(GLint)location, cast(GLfloat)value.X, cast(GLfloat)value.Y); } 62 | public void SetUniform(int location, Vector3 value) { if (!Attached) Attach(); glUniform3f(cast(GLint)location, cast(GLfloat)value.X, cast(GLfloat)value.Y, cast(GLfloat)value.Z); } 63 | public void SetUniform(int location, Vector4 value) { if (!Attached) Attach(); glUniform4f(cast(GLint)location, cast(GLfloat)value.X, cast(GLfloat)value.Y, cast(GLfloat)value.Z, cast(GLfloat)value.W); } 64 | public void SetUniform(int location, int value) { if (!Attached) Attach(); glUniform1i(cast(GLint)location, cast(GLint)value); } 65 | public void SetUniform(int location, Vector2i value) { if (!Attached) Attach(); glUniform2i(cast(GLint)location, cast(GLint)value.X, cast(GLint)value.Y); } 66 | public void SetUniform(int location, Vector3i value) { if (!Attached) Attach(); glUniform3i(cast(GLint)location, cast(GLint)value.X, cast(GLint)value.Y, cast(GLint)value.Z); } 67 | public void SetUniform(int location, Vector4i value) { if (!Attached) Attach(); glUniform4i(cast(GLint)location, cast(GLint)value.X, cast(GLint)value.Y, cast(GLint)value.Z, cast(GLint)value.W); } 68 | public void SetUniform(int location, Matrix2x2 value) { if (!Attached) Attach(); glUniformMatrix2fv(location, 1, GL_TRUE, value.ptr); } 69 | public void SetUniform(int location, Matrix3x3 value) { if (!Attached) Attach(); glUniformMatrix3fv(location, 1, GL_TRUE, value.ptr); } 70 | public void SetUniform(int location, Matrix4x4 value) { if (!Attached) Attach(); glUniformMatrix4fv(location, 1, GL_TRUE, value.ptr); } 71 | 72 | /** 73 | GetUniform gets the position of a uniform. 74 | */ 75 | public int GetUniform(string name) { 76 | import std.string; 77 | return glGetUniformLocation(shaderprogram, name.toStringz); 78 | } 79 | 80 | /** 81 | HasUniform checks whenever the shader contains a unform with name "name" 82 | Returns true if it exists, false otherwise. 83 | */ 84 | public bool HasUniform(string name) { 85 | auto u = GetUniform(name); 86 | if (u == -1) return false; 87 | return true; 88 | } 89 | 90 | /** 91 | Attach attaches the shader. 92 | */ 93 | public void Attach() { 94 | glUseProgram(this.shaderprogram); 95 | } 96 | 97 | /** 98 | Detatch detatches the shader. (Binds shader 0) 99 | */ 100 | public void Detach() { 101 | glUseProgram(0); 102 | } 103 | 104 | //Shader linking 105 | private void link_shaders() { 106 | glLinkProgram(shaderprogram); 107 | int c; 108 | glGetProgramiv(shaderprogram, GL_LINK_STATUS, &c); 109 | if (c == 0) { 110 | log_shader(shaderprogram, LogType.Program); 111 | return; 112 | } 113 | c = 0; 114 | glValidateProgram(shaderprogram); 115 | glGetProgramiv(shaderprogram, GL_VALIDATE_STATUS, &c); 116 | if (c == 0) { 117 | log_shader(shaderprogram, LogType.Program); 118 | return; 119 | } 120 | //TODO: Add logging 121 | //writeln("Compilation completed."); 122 | } 123 | 124 | //Compilation of shaders 125 | private void compile_shaders() { 126 | compile_shader(shadersource, ShaderType.Vertex); 127 | if (!(shadersource.Geometry is null)) compile_shader(shadersource, ShaderType.Geometry); 128 | compile_shader(shadersource, ShaderType.Fragment); 129 | shaderprogram = glCreateProgram(); 130 | glAttachShader(shaderprogram, vertexshader); 131 | if (!(shadersource.Geometry is null)) glAttachShader(shaderprogram, geometryshader); 132 | glAttachShader(shaderprogram, fragmentshader); 133 | } 134 | 135 | private void compile_shader(ShaderCode code, ShaderType type) { 136 | if (type == ShaderType.Vertex) { 137 | vertexshader = glCreateShader(GL_VERTEX_SHADER); 138 | 139 | //Get source 140 | int l = cast(int)shadersource.Vertex.length; 141 | GLchar* cs = VertGL; 142 | glShaderSource(vertexshader, 1, &cs, &l); 143 | 144 | //Compile 145 | glCompileShader(vertexshader); 146 | int c; 147 | glGetShaderiv(vertexshader, GL_COMPILE_STATUS, &c); 148 | if (c == 0) { 149 | log_shader(vertexshader); 150 | return; 151 | } 152 | } else if (type == ShaderType.Geometry) { 153 | geometryshader = glCreateShader(GL_GEOMETRY_SHADER); 154 | 155 | //Get source 156 | int l = cast(int)shadersource.Geometry.length; 157 | GLchar* cs = GeoGL; 158 | glShaderSource(geometryshader, 1, &cs, &l); 159 | 160 | //Compile 161 | glCompileShader(geometryshader); 162 | int c; 163 | glGetShaderiv(geometryshader, GL_COMPILE_STATUS, &c); 164 | if (c == 0) { 165 | log_shader(geometryshader); 166 | return; 167 | } 168 | } else { 169 | fragmentshader = glCreateShader(GL_FRAGMENT_SHADER); 170 | 171 | //Get source 172 | int l = cast(int)shadersource.Fragment.length; 173 | GLchar* cs = FragGL; 174 | glShaderSource(fragmentshader, 1, &cs, &l); 175 | 176 | //Compile 177 | glCompileShader(fragmentshader); 178 | int c; 179 | glGetShaderiv(fragmentshader, GL_COMPILE_STATUS, &c); 180 | if (c != GL_TRUE) { 181 | log_shader(fragmentshader); 182 | return; 183 | } 184 | } 185 | } 186 | 187 | enum LogType { 188 | Program, 189 | Shader 190 | } 191 | 192 | private void log_shader(GLuint port, LogType type = LogType.Shader) { 193 | if (type == LogType.Shader) { 194 | int maxlen = 512; 195 | char[] logmsg; 196 | logmsg.length = maxlen; 197 | glGetShaderInfoLog(port, maxlen, &maxlen, logmsg.ptr); 198 | throw new Error("GLSLShaderError (shader: " ~ to!string(port) ~ ")" ~ to!string(logmsg[0..maxlen])); 199 | } 200 | int maxlen = 512; 201 | char[] logmsg; 202 | logmsg.length = maxlen; 203 | glGetProgramInfoLog(port, maxlen, &maxlen, logmsg.ptr); 204 | throw new Error("GLSLProgramError (program: " ~ to!string(port) ~ ")" ~ to!string(logmsg[0..maxlen])); 205 | } 206 | } 207 | 208 | -------------------------------------------------------------------------------- /source/polyplex/core/render/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render; 2 | public import polyplex.core.render.camera; 3 | public import polyplex.core.render.shapes; 4 | public import polyplex.core.render.common; 5 | public import polyplex.core.render.enums; 6 | 7 | // If OpenGL is chosen as a backend, import opengl 8 | version(OpenGL) { 9 | public import polyplex.core.render.gl; 10 | } 11 | 12 | version(OpenGL_ES) { 13 | public import polyplex.core.render.gl; 14 | } 15 | 16 | version(Vulkan) { 17 | public import polyplex.core.render.vk; 18 | } -------------------------------------------------------------------------------- /source/polyplex/core/render/shapes.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.render.shapes; 2 | import polyplex.math; 3 | import std.stdio; 4 | 5 | public abstract class Shape { 6 | public abstract float[][] GetVertices(); 7 | } 8 | 9 | class Square : Shape { 10 | private float[][] verts; 11 | public @property float[][] Vertices() { return verts; } 12 | 13 | this(Rectangle rect) { 14 | Vector2[] vecs = new Vector2[4]; 15 | vecs[0] = Vector2(rect.X, rect.Height); 16 | vecs[1] = Vector2(rect.X, rect.Y); 17 | vecs[2] = Vector2(rect.Width, rect.Y); 18 | vecs[3] = Vector2(rect.Width, rect.Height); 19 | Polygon pg = new Polygon(vecs); 20 | writeln(this.verts); 21 | this.verts = pg.Vertices; 22 | } 23 | 24 | public override float[][] GetVertices() { 25 | return this.verts; 26 | } 27 | } 28 | 29 | class FSquare : Shape { 30 | private float[][] verts; 31 | public @property float[][] Vertices() { return verts; } 32 | 33 | this(Vector4 rect) { 34 | Vector2[] vecs = new Vector2[4]; 35 | vecs[0] = Vector2(rect.X, rect.W); 36 | vecs[1] = Vector2(rect.X, rect.Y); 37 | vecs[2] = Vector2(rect.Z, rect.W); 38 | vecs[3] = Vector2(rect.Z, rect.Y); 39 | Polygon pg = new Polygon(vecs); 40 | this.verts = pg.Vertices; 41 | } 42 | 43 | public override float[][] GetVertices() { 44 | return this.verts; 45 | } 46 | } 47 | 48 | class Polygon : Shape { 49 | private float[][] verts; 50 | public @property float[][] Vertices() { return verts; } 51 | this(Vector2[] points) { 52 | verts.length = points.length; 53 | for (int i = 0; i < points.length; i++) { 54 | verts[i].length = 2; 55 | verts[i][0] = points[i].X; 56 | verts[i][1] = points[i].Y; 57 | } 58 | } 59 | 60 | public override float[][] GetVertices() { 61 | return Vertices; 62 | } 63 | } -------------------------------------------------------------------------------- /source/polyplex/core/time.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.time; 2 | import std.format; 3 | import polyplex.utils.strutils; 4 | 5 | /** 6 | An ingame timespan 7 | */ 8 | class GameTimeSpan { 9 | private: 10 | double ticks; 11 | 12 | public: 13 | /** 14 | Gets the base value (a tick) 15 | */ 16 | @property double BaseValue() { 17 | return ticks; 18 | } 19 | 20 | /** 21 | Sets the base value (a tick) 22 | */ 23 | @property void BaseValue(double ticks) { 24 | this.ticks = ticks; 25 | } 26 | 27 | /** 28 | Gets the amount of milliseconds as a long 29 | */ 30 | @property ulong LMilliseconds() { 31 | return cast(ulong)Milliseconds; 32 | } 33 | 34 | /** 35 | Gets the amount of seconds as a long 36 | */ 37 | @property ulong LSeconds() { 38 | return cast(ulong)Seconds; 39 | } 40 | 41 | /** 42 | Gets the amount of minutes as a long 43 | */ 44 | @property ulong LMinutes() { 45 | return cast(ulong)Minutes; 46 | } 47 | 48 | /** 49 | Gets the amount of hours as a long 50 | */ 51 | @property ulong LHours() { 52 | return cast(ulong)Hours; 53 | } 54 | 55 | /** 56 | Gets the amount of milliseconds as a double 57 | */ 58 | @property double Milliseconds() { 59 | return ticks; 60 | } 61 | 62 | /** 63 | Gets the amount of milliseconds as a double 64 | */ 65 | @property double Seconds() { 66 | return ticks / 1000; 67 | } 68 | 69 | /** 70 | Gets the amount of milliseconds as a double 71 | */ 72 | @property double Minutes() { 73 | return Seconds / 60; 74 | } 75 | 76 | /** 77 | Gets the amount of milliseconds as a double 78 | */ 79 | @property double Hours() { 80 | return Minutes / 60; 81 | } 82 | 83 | /** 84 | Constructs a new game timespan instance 85 | */ 86 | this(ulong ticks) { 87 | this.ticks = ticks; 88 | } 89 | 90 | static { 91 | 92 | /** 93 | Create a new time span from specified seconds 94 | */ 95 | GameTimeSpan FromSeconds(ulong seconds) { 96 | return new GameTimeSpan(seconds * 1000); 97 | } 98 | 99 | /** 100 | Create a new time span from specified minutes 101 | */ 102 | GameTimeSpan FromMinutes(ulong minutes) { 103 | return FromSeconds(minutes * 60); 104 | } 105 | 106 | /** 107 | Create a new time span from specified hours 108 | */ 109 | GameTimeSpan FromHours(ulong hours) { 110 | return FromMinutes(hours * 60); 111 | } 112 | } 113 | 114 | /// Binary op 115 | GameTimeSpan opBinary(string op)(GameTimeSpan other) { 116 | return new GameTime(mixin(q{this.ticks %s other.ticks}.format(op))); 117 | } 118 | 119 | /** 120 | Calculates the ratio of this timespan compared to the other timespan 121 | */ 122 | float RatioOf(GameTimeSpan other) { 123 | return cast(float) this.ticks / cast(float) other.ticks; 124 | } 125 | 126 | /** 127 | Returns a human-friendly timespan string 128 | */ 129 | override 130 | string toString() { 131 | return "%d:%d:%d:%d".format(LHours, LMinutes % 60, LSeconds % 60, LMilliseconds % 60); 132 | } 133 | 134 | /** 135 | Format time using the polyplex formatter 136 | */ 137 | deprecated 138 | string FormatTime(string formatstring) { 139 | return Format(formatstring, LHours, LMinutes % 60, LSeconds % 60, LMilliseconds % 60); 140 | } 141 | } 142 | 143 | /** 144 | A container for game time 145 | */ 146 | class GameTime { 147 | public: 148 | /** 149 | Creates a new gametime instance 150 | */ 151 | this(GameTimeSpan total, GameTimeSpan delta) { 152 | TotalTime = total; 153 | DeltaTime = delta; 154 | } 155 | 156 | /** 157 | The total time the game has been running 158 | */ 159 | GameTimeSpan TotalTime; 160 | 161 | /** 162 | The time between this and the last frame 163 | */ 164 | GameTimeSpan DeltaTime; 165 | 166 | package(polyplex.core): 167 | 168 | /* 169 | Internal tools to update the delta and total time a bit more cleanly 170 | */ 171 | 172 | void updateDelta(double delta) { 173 | DeltaTime.BaseValue = delta; 174 | } 175 | 176 | void updateTotal(double total) { 177 | TotalTime.BaseValue = total; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /source/polyplex/core/window.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.window; 2 | import polyplex.core.render; 3 | import polyplex.math; 4 | import polyplex; 5 | import events; 6 | 7 | public class BoundsEventArgs : EventArgs { 8 | public : 9 | int X; 10 | int Y; 11 | int Width; 12 | int Height; 13 | } 14 | 15 | public class WindowBounds { 16 | private : 17 | Rectanglei winRect; 18 | Window owner; 19 | protected : 20 | void updateWindowBounds(Rectanglei rect) { 21 | winRect = rect; 22 | } 23 | public : 24 | 25 | Event!BoundsEventArgs windowResizeRequestEvent; 26 | Event!BoundsEventArgs windowPositionRequestEvent; 27 | this(Window owner, Rectanglei winRect) { 28 | windowResizeRequestEvent = new Event!BoundsEventArgs; 29 | windowPositionRequestEvent = new Event!BoundsEventArgs; 30 | this.owner = owner; 31 | this.winRect = winRect; 32 | } 33 | 34 | @property int X() { 35 | return winRect.X; 36 | } 37 | 38 | @property int Y() { 39 | return winRect.Y; 40 | } 41 | 42 | @property int Width() { 43 | return winRect.Width; 44 | } 45 | 46 | @property int Height() { 47 | return winRect.Height; 48 | } 49 | 50 | @property Vector2i Center() { 51 | return Vector2i(winRect.Width/2, winRect.Height/2); 52 | } 53 | 54 | void ResizeWindow(int width, int height) { 55 | winRect.Width = width; 56 | winRect.Height = height; 57 | BoundsEventArgs args = new BoundsEventArgs(); 58 | args.Width = width; 59 | args.Height = height; 60 | windowResizeRequestEvent(cast(void*)this, args); 61 | } 62 | 63 | void MoveWindow(int x, int y) { 64 | winRect.X = x; 65 | winRect.Y = y; 66 | BoundsEventArgs args = new BoundsEventArgs(); 67 | args.X = x; 68 | args.Y = y; 69 | windowPositionRequestEvent(cast(void*)this, args); 70 | } 71 | } 72 | 73 | public abstract class Window { 74 | protected : 75 | string SurfaceName; 76 | GraphicsBackend ActiveBackend; 77 | GraphicsContext ActiveContext; 78 | 79 | void updateBounds(Rectanglei rect) { 80 | ClientBounds.updateWindowBounds(rect); 81 | } 82 | 83 | void replaceOn(Game game) { 84 | game.forceWindowChange(this); 85 | } 86 | 87 | public : 88 | WindowBounds ClientBounds; 89 | bool AutoFocus = true; 90 | 91 | // Base Constructor. 92 | this(string name) { 93 | this.SurfaceName = name; 94 | } 95 | 96 | ~this() { 97 | DestroyContext(); 98 | } 99 | 100 | // Allow Resizing 101 | abstract @property bool AllowResizing(); 102 | abstract @property void AllowResizing(bool allow); 103 | 104 | // VSync 105 | abstract @property VSyncState VSync(); 106 | abstract @property void VSync(VSyncState value); 107 | 108 | // Borderless Window Mode 109 | abstract @property bool Borderless(); 110 | abstract @property void Borderless(bool i); 111 | 112 | // Fullscreen 113 | abstract @property bool Fullscreen(); 114 | abstract @property void Fullscreen(bool i); 115 | 116 | // Client Bounds 117 | abstract @property bool Visible(); 118 | 119 | // Window Title 120 | abstract @property string Title(); 121 | abstract @property void Title(string value); 122 | 123 | abstract void Close(); 124 | abstract void Show(); 125 | abstract void UpdateState(); 126 | abstract void SwapBuffers(); 127 | abstract void Focus(); 128 | abstract void SetIcon(); 129 | 130 | abstract GraphicsContext CreateContext(GraphicsBackend backend); 131 | public abstract void DestroyContext(); 132 | } 133 | 134 | public struct GraphicsContext { 135 | void* ContextPtr; 136 | } 137 | -------------------------------------------------------------------------------- /source/polyplex/core/windows/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.windows; 2 | 3 | public import polyplex.core.windows.sdlwindow; 4 | -------------------------------------------------------------------------------- /source/polyplex/core/windows/sdlwindow.d: -------------------------------------------------------------------------------- 1 | module polyplex.core.windows.sdlwindow; 2 | import polyplex.core.window; 3 | import polyplex; 4 | import polyplex.utils.sdlbool; 5 | public import polyplex.core.render; 6 | static import polyplex; 7 | 8 | import bindbc.sdl; 9 | import bindbc.opengl; 10 | import polyplex.math; 11 | import polyplex.utils.logging; 12 | import std.stdio; 13 | import std.string; 14 | import std.conv; 15 | 16 | 17 | public enum WindowPosition { 18 | Center = -1, 19 | Undefined = 0 20 | } 21 | 22 | public class SDLGameWindow : Window { 23 | private string start_title; 24 | private SDL_Window* window; 25 | private Rectanglei startBounds; 26 | 27 | /* 28 | Gets whenever the window is visible 29 | */ 30 | public override @property bool Visible() { return (this.window !is null); } 31 | 32 | // Resizing 33 | public override @property bool AllowResizing() { 34 | SDL_WindowFlags flags = SDL_GetWindowFlags(window); 35 | return ((flags & SDL_WINDOW_RESIZABLE) > 0); 36 | } 37 | public override @property void AllowResizing(bool allow) { SDL_SetWindowResizable(this.window, ToSDL(allow)); } 38 | 39 | // VSync 40 | public override @property VSyncState VSync() { 41 | if (ActiveBackend == GraphicsBackend.OpenGL) return cast(VSyncState)SDL_GL_GetSwapInterval(); 42 | return VSyncState.VSync; 43 | } 44 | 45 | public override @property void VSync(VSyncState state) { 46 | if (ActiveBackend == GraphicsBackend.OpenGL) SDL_GL_SetSwapInterval(state); 47 | } 48 | 49 | // Borderless 50 | public override @property bool Borderless() { 51 | SDL_WindowFlags flags = SDL_GetWindowFlags(window); 52 | return ((flags & SDL_WINDOW_BORDERLESS) > 0); 53 | } 54 | public override @property void Borderless(bool i) { SDL_SetWindowBordered(this.window, ToSDL(!i)); } 55 | 56 | // Title 57 | public override @property string Title() { return to!string(SDL_GetWindowTitle(this.window)); } 58 | public override @property void Title(string value) { return SDL_SetWindowTitle(this.window, value.toStringz); } 59 | 60 | //Brightness 61 | public @property float Brightness() { return SDL_GetWindowBrightness(this.window); } 62 | public @property void Brightness(float b) { SDL_SetWindowBrightness(this.window, b); } 63 | 64 | //Fullscreen 65 | public override @property bool Fullscreen() { 66 | SDL_WindowFlags flags = SDL_GetWindowFlags(window); 67 | return ((flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_FULLSCREEN_DESKTOP)) > 0); 68 | } 69 | 70 | public override @property void Fullscreen(bool i) { 71 | if (!i) { 72 | //Windowed. 73 | SDL_SetWindowFullscreen(this.window, 0); 74 | return; 75 | } 76 | if (Borderless) { 77 | //Fullscreen Borderless 78 | SDL_SetWindowFullscreen(this.window, SDL_WINDOW_FULLSCREEN_DESKTOP); 79 | return; 80 | } 81 | //Fullscreen 82 | SDL_SetWindowFullscreen(this.window, SDL_WINDOW_FULLSCREEN); 83 | } 84 | 85 | /** 86 | Returns the position of the window. 87 | */ 88 | public @property Vector2 Position() { 89 | return Vector2(ClientBounds.X, ClientBounds.Y); 90 | } 91 | 92 | /** 93 | Allows you to set the position of the window. 94 | */ 95 | public @property void Position(Vector2 pos) { 96 | SDL_SetWindowPosition(this.window, cast(int)pos.X, cast(int)pos.Y); 97 | } 98 | 99 | this(string name, Rectanglei bounds, bool focus = true) { 100 | super(name); 101 | if (!SDL_Init(SDL_INIT_EVERYTHING) < 0) { 102 | Logger.Fatal("Initialization of SDL2 failed!...\n{0}", SDL_GetError()); 103 | } 104 | 105 | ClientBounds = new WindowBounds(this, bounds); 106 | 107 | ClientBounds.windowResizeRequestEvent ~= (sender, data) { 108 | BoundsEventArgs dat = cast(BoundsEventArgs)data; 109 | SDL_SetWindowSize(this.window, dat.Width, dat.Height); 110 | }; 111 | 112 | ClientBounds.windowPositionRequestEvent ~= (sender, data) { 113 | BoundsEventArgs dat = cast(BoundsEventArgs)data; 114 | SDL_SetWindowPosition(this.window, dat.X, dat.Y); 115 | }; 116 | 117 | //Set info. 118 | this.startBounds = bounds; 119 | this.start_title = name; 120 | 121 | this.AutoFocus = focus; 122 | 123 | 124 | 125 | //Cap info. 126 | if (this.startBounds.X == WindowPosition.Center) this.startBounds.X = SDL_WINDOWPOS_CENTERED; 127 | if (this.startBounds.Y == WindowPosition.Center) this.startBounds.Y = SDL_WINDOWPOS_CENTERED; 128 | if (this.startBounds.X == WindowPosition.Undefined) this.startBounds.X = SDL_WINDOWPOS_UNDEFINED; 129 | if (this.startBounds.Y == WindowPosition.Undefined) this.startBounds.Y = SDL_WINDOWPOS_UNDEFINED; 130 | if (this.startBounds.Width == WindowPosition.Undefined) this.startBounds.Width = 640; 131 | if (this.startBounds.Height == WindowPosition.Undefined) this.startBounds.Height = 480; 132 | } 133 | 134 | this (Rectanglei bounds, bool focus = true) { 135 | this("My Game", bounds, focus); 136 | } 137 | 138 | this(bool focus = true) { 139 | this(Rectanglei(WindowPosition.Undefined, WindowPosition.Undefined, 640, 480), focus); 140 | } 141 | 142 | ~this() { 143 | if (this.window != null) Close(); 144 | SDL_Quit(); 145 | } 146 | 147 | override GraphicsContext CreateContext(GraphicsBackend backend) { 148 | ActiveBackend = backend; 149 | version(OpenGL) { 150 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 151 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 152 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); 153 | } 154 | version(OpenGL_ES) { 155 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); 156 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); 157 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 158 | SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); 159 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 160 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); 161 | } 162 | SDL_GLContext context = SDL_GL_CreateContext(this.window); 163 | if (context == null) throw new Error(to!string(SDL_GetError())); 164 | ActiveContext = GraphicsContext(context); 165 | return ActiveContext; 166 | } 167 | 168 | override void DestroyContext() { 169 | if (ActiveBackend == GraphicsBackend.OpenGL) { 170 | SDL_GL_DeleteContext(ActiveContext.ContextPtr); 171 | Logger.Log("Deleted OpenGL context."); 172 | return; 173 | } 174 | // TODO: Destroy vulkan context. 175 | } 176 | 177 | override void SwapBuffers() { 178 | if (ActiveBackend == GraphicsBackend.OpenGL) SDL_GL_SwapWindow(this.window); 179 | } 180 | 181 | /** 182 | Triggers an window info update. 183 | */ 184 | override void UpdateState() { 185 | int x, y, width, height; 186 | SDL_GetWindowPosition(this.window, &x, &y); 187 | SDL_GetWindowSize(this.window, &width, &height); 188 | updateBounds(Rectanglei(x, y, width, height)); 189 | } 190 | 191 | /** 192 | Closes the window. 193 | */ 194 | override void Close() { 195 | SDL_DestroyWindow(this.window); 196 | //Explicitly destroy window. 197 | destroy(this.window); 198 | } 199 | 200 | /** 201 | Puts the window in focus (ONLY WORKS ON SOME PLATFORMS!) 202 | */ 203 | override void Focus() { 204 | SDL_RaiseWindow(this.window); 205 | } 206 | 207 | /** 208 | TODO 209 | Sets the icon for the window. 210 | */ 211 | override void SetIcon() { 212 | //TODO: When rendering is there, set icon. 213 | } 214 | 215 | /** 216 | Shows the window. 217 | */ 218 | override void Show() { 219 | Logger.Debug("Spawning window..."); 220 | this.window = SDL_CreateWindow(this.start_title.toStringz, this.startBounds.X, this.startBounds.Y, this.startBounds.Width, this.startBounds.Height, SDL_WINDOW_OPENGL); 221 | if (this.window == null) { 222 | destroy(this.window); 223 | Logger.Fatal("Window creation error: {0}", SDL_GetError()); 224 | } 225 | // Enable VSync by default. 226 | VSync = VSyncState.VSync; 227 | 228 | // Focus window 229 | if (AutoFocus) this.Focus(); 230 | } 231 | } -------------------------------------------------------------------------------- /source/polyplex/math/linear/matrix2x2.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.linear.matrix2x2; 2 | 3 | public class Matrix2x2 { 4 | private float[2][2] data; 5 | 6 | /** 7 | Pointer to the underlying array data. 8 | */ 9 | public float* ptr() { return data[0].ptr; } 10 | } 11 | -------------------------------------------------------------------------------- /source/polyplex/math/linear/matrix3x3.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.linear.matrix3x3; 2 | 3 | public class Matrix3x3 { 4 | private float[3][3] data; 5 | 6 | /** 7 | Pointer to the underlying array data. 8 | */ 9 | public float* ptr() { return data[0].ptr; } 10 | } 11 | -------------------------------------------------------------------------------- /source/polyplex/math/linear/matrix4x4.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.linear.matrix4x4; 2 | import polyplex.math.linear; 3 | import polyplex.math; 4 | import std.conv; 5 | 6 | // TODO: Redo implementation 7 | public struct Matrix4x4 { 8 | float[4][4] data; 9 | 10 | 11 | this(float[4][4] data) { 12 | this.data = data; 13 | } 14 | 15 | private static float[4][4] clear(float val) { 16 | float[4][4] dat; 17 | foreach ( x; 0 .. 4 ) { 18 | foreach ( y; 0 .. 4 ) { 19 | dat[x][y] = val; 20 | } 21 | } 22 | return dat; 23 | } 24 | 25 | float opIndex(size_t x, size_t y) { 26 | return data[y][x]; 27 | } 28 | 29 | float opIndex(int x, int y) { 30 | return data[y][x]; 31 | } 32 | 33 | void opIndexAssign(float value, size_t x, size_t y) { 34 | data[y][x] = value; 35 | } 36 | 37 | void opIndex(float value, int x, int y) { 38 | data[y][x] = value; 39 | } 40 | 41 | public static Matrix4x4 Identity() { 42 | float[4][4] data = clear(0); 43 | foreach ( i; 0 .. 4 ) { 44 | data[i][i] = 1f; 45 | } 46 | return Matrix4x4(data); 47 | } 48 | 49 | public Vector3 Column(int index) { 50 | return Vector3(this[0,index], this[1,index], this[2,index]); 51 | } 52 | 53 | public Vector3 ToScaling() { 54 | return Vector3(Column(0).Length, Column(1).Length, Column(2).Length); 55 | } 56 | 57 | public static Matrix4x4 Scaling(Vector3 scale_vec) { 58 | Matrix4x4 dims = Matrix4x4.Identity; 59 | dims[0,0] = scale_vec.X; 60 | dims[1,1] = scale_vec.Y; 61 | dims[2,2] = scale_vec.Z; 62 | return dims; 63 | } 64 | 65 | /*public static Matrix4x4 Scaling(Vector3 scale_vec) { 66 | float[4][4] dims = Matrix4x4.Identity.data; 67 | foreach( x; 0 .. 4-1 ) { 68 | foreach( y; 0 .. 4-1 ) { 69 | if (x == y) dims[x][y] = scale_vec.data[x]; 70 | else dims[x][y] = 0; 71 | } 72 | } 73 | dims[4-1][4-1] = 1; 74 | return Matrix4x4(dims); 75 | }*/ 76 | 77 | public static Matrix4x4 RotationX(float x_rot) { 78 | Matrix4x4 dims = Matrix4x4.Identity; 79 | dims[1,1] = Mathf.Cos(x_rot); 80 | dims[1,2] = Mathf.Sin(x_rot); 81 | dims[2,1] = -Mathf.Sin(x_rot); 82 | dims[2,2] = Mathf.Cos(x_rot); 83 | return dims; 84 | } 85 | 86 | public static Matrix4x4 RotationY(float y_rot) { 87 | Matrix4x4 dims = Matrix4x4.Identity; 88 | dims[0,0] = Mathf.Cos(y_rot); 89 | dims[2,0] = Mathf.Sin(y_rot); 90 | dims[0,2] = -Mathf.Sin(y_rot); 91 | dims[2,2] = Mathf.Cos(y_rot); 92 | return dims; 93 | } 94 | 95 | public static Matrix4x4 RotationZ(float z_rot) { 96 | Matrix4x4 dims = Matrix4x4.Identity; 97 | dims[0,0] = Mathf.Cos(z_rot); 98 | dims[1,0] = -Mathf.Sin(z_rot); 99 | dims[0,1] = Mathf.Sin(z_rot); 100 | dims[1,1] = Mathf.Cos(z_rot); 101 | return dims; 102 | } 103 | 104 | public static Matrix4x4 FromQuaternion(Quaternion quat) { 105 | float sqx = quat.X*quat.X; 106 | float sqy = quat.Y*quat.Y; 107 | float sqz = quat.Z*quat.Z; 108 | float sqw = quat.W*quat.W; 109 | float n = 1f/(sqx+sqy+sqz+sqw); 110 | 111 | Matrix4x4 mat = Matrix4x4.Identity; 112 | 113 | if (sqx == 0 && sqy == 0 && sqz == 0 && sqw == 0) 114 | return mat; 115 | 116 | mat[0,0] = ( sqx - sqy - sqz + sqw) * n; 117 | mat[1,1] = (-sqx + sqy - sqz + sqw) * n; 118 | mat[2,2] = (-sqx - sqy + sqz + sqw) * n; 119 | 120 | double tx = quat.X*quat.Y; 121 | double ty = quat.Z*quat.W; 122 | mat[1,0] = 2.0 * (tx + ty) * n; 123 | mat[0,1] = 2.0 * (tx - ty) * n; 124 | 125 | tx = quat.X*quat.Z; 126 | ty = quat.Y*quat.W; 127 | mat[2,0] = 2.0 * (tx - ty) * n; 128 | mat[0,2] = 2.0 * (tx + ty) * n; 129 | 130 | tx = quat.Y*quat.Z; 131 | ty = quat.X*quat.W; 132 | mat[2,1] = 2.0 * (tx + ty) * n; 133 | mat[1,2] = 2.0 * (tx - ty) * n; 134 | 135 | return mat; 136 | } 137 | public Vector3 ToTranslation() { 138 | return Vector3(this[3,0], this[3,1], this[3,2]); 139 | } 140 | 141 | public float XComponent() { 142 | return this[3, 0]; 143 | } 144 | 145 | public float YComponent() { 146 | return this[3, 1]; 147 | } 148 | 149 | public float ZComponent() { 150 | return this[3, 2]; 151 | } 152 | 153 | public Vector3 TranslatedVector() { 154 | return Vector3(XComponent, YComponent, ZComponent); 155 | } 156 | 157 | public static Matrix4x4 Translation(Vector3 trans_vec) { 158 | return Matrix4x4([ 159 | [1f, 0f, 0f, trans_vec.X], 160 | [0f, 1f, 0f, trans_vec.Y], 161 | [0f, 0f, 1f, trans_vec.Z], 162 | [0f, 0f, 0f, 1f] 163 | ]); 164 | } 165 | 166 | public static Matrix4x4 Orthographic(float left, float right, float bottom, float top, float znear, float zfar) { 167 | return Matrix4x4([ 168 | [2/(right-left), 0f, 0f, -(right+left)/(right-left)], 169 | [0f, 2/(top-bottom), 0f, -(top+bottom)/(top-bottom)], 170 | [0f, 0f, -2/(zfar-znear), -(zfar+znear)/(zfar-znear)], 171 | [0f, 0f, 0f, 1f] 172 | ]); 173 | } 174 | 175 | public static Matrix4x4 OrthographicInverse(float left, float right, float bottom, float top, float znear, float zfar) { 176 | return Matrix4x4([ 177 | [(right-left)/2f, 0, 0, 0], 178 | [0, (top-bottom)/2f, 0, 0], 179 | [0, 0, (zfar-znear)/-2f, 0], 180 | [(right+left)/2f, (top+bottom)/2f, (zfar+znear)/2f, 1f] 181 | ]); 182 | } 183 | 184 | public Matrix4x4 Inverse() { 185 | Matrix4x4 mat; 186 | foreach(x; 0 .. 4) { 187 | foreach(y; 0 .. 4) { 188 | mat[x,y] = this[y,x]; 189 | } 190 | } 191 | return mat; 192 | } 193 | 194 | private static float[6] prepPerspective(float width, float height, float fov, float znear, float zfar) { 195 | float aspect = width/height; 196 | float top = znear * Mathf.Tan(Mathf.ToRadians(fov)); 197 | float bottom = -top; 198 | float right = top * aspect; 199 | float left = -right; 200 | return [left, right, bottom, top, znear, zfar]; 201 | } 202 | 203 | public static Matrix4x4 Perspective(float width, float height, float fov, float znear, float zfar) { 204 | float[6] persp_data = prepPerspective(width, height, fov, znear, zfar); 205 | return perspective(persp_data[0], persp_data[1], persp_data[2], persp_data[3], persp_data[4], persp_data[5]); 206 | } 207 | 208 | private static Matrix4x4 perspective(float left, float right, float bottom, float top, float near, float far) { 209 | return Matrix4x4([ 210 | [(2f*near)/(right-left), 0, (right+left)/(right-left), 0], 211 | [0, (2f*near)/(top-bottom), -(2f*far*near)/(far-near), 0], 212 | [0, (top+bottom)/(top-bottom), -(far+near)/(far-near), 0], 213 | [0, 0, -1f, 0] 214 | ]); 215 | } 216 | 217 | public Matrix4x4 opBinary(string op: "*")(Matrix4x4 other) { 218 | float[4][4] dim = clear(0); 219 | foreach ( row; 0 .. 4 ) { 220 | foreach ( column; 0 .. 4 ) { 221 | foreach ( i; 0 .. 4 ) { 222 | dim[row][column] += this.data[row][i] * other.data[i][column]; 223 | } 224 | } 225 | } 226 | return Matrix4x4(dim); 227 | } 228 | 229 | public Matrix4x4 Transpose() pure const nothrow { 230 | Matrix4x4 temp = this; 231 | static foreach( x; 0 .. 4 ) { 232 | static foreach ( y; 0 .. 4 ) { 233 | temp.data[x][y] = this.data[y][x]; 234 | } 235 | } 236 | return temp; 237 | } 238 | 239 | public Matrix4x4 Translate (Vector3 trans_vec) { 240 | this = Matrix4x4.Translation(trans_vec) * this; 241 | return this; 242 | } 243 | 244 | public Matrix4x4 Scale(Vector3 scale_vec) { 245 | this = Matrix4x4.Scaling(scale_vec) * this; 246 | return this; 247 | } 248 | 249 | public Matrix4x4 RotateX(float rot) { 250 | this = Matrix4x4.RotationX(rot) * this; 251 | return this; 252 | } 253 | 254 | public Matrix4x4 RotateY(float rot) { 255 | return Matrix4x4.RotationY(rot) * this; 256 | } 257 | 258 | public Matrix4x4 RotateZ(float rot) { 259 | return Matrix4x4.RotationZ(rot) * this; 260 | } 261 | 262 | public Matrix4x4 Rotate(Quaternion rot) { 263 | return Matrix4x4.FromQuaternion(rot) * this; 264 | } 265 | 266 | public string ToString() { 267 | string dim = "["; 268 | foreach( x; 0 .. 4 ) { 269 | dim ~= "\n\t["; 270 | foreach ( y; 0 .. 4 ) { 271 | dim ~= this.data[x][y].text; 272 | if (y != 4-1) dim ~= ", "; 273 | } 274 | dim ~= "]"; 275 | if (x != 4-1) dim ~= ","; 276 | } 277 | return dim ~ "\n]"; 278 | } 279 | 280 | /** 281 | Pointer to the underlying array data. 282 | */ 283 | public float* ptr() return { return data[0].ptr; } 284 | } 285 | -------------------------------------------------------------------------------- /source/polyplex/math/linear/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.linear; 2 | 3 | public import polyplex.math.linear.vectors; 4 | public import polyplex.math.linear.matrix2x2; 5 | public import polyplex.math.linear.matrix3x3; 6 | public import polyplex.math.linear.matrix4x4; 7 | public import polyplex.math.linear.quaternion; 8 | public import polyplex.math.linear.transform; 9 | 10 | // Unit tests 11 | unittest { 12 | // ================ Vectors =================== 13 | 14 | // Test IsVector 15 | assert(IsVector!(Vector2)); 16 | assert(IsVector!(Vector2i)); 17 | assert(IsVector!(Vector3)); 18 | assert(IsVector!(Vector3i)); 19 | assert(IsVector!(Vector4)); 20 | assert(IsVector!(Vector4i)); 21 | assert(!IsVector!(float)); 22 | 23 | // ================ Vector2 =================== 24 | 25 | // Test IsVector2T 26 | assert(IsVector2T!(Vector2)); 27 | assert(IsVector2T!(Vector2i)); 28 | 29 | assert(!IsVector2T!(Vector4)); 30 | assert(!IsVector2T!(float)); 31 | assert(!IsVector2T!(string)); 32 | 33 | // Test Construct 34 | assert(Vector2(1) == Vector2(1, 1)); 35 | assert(Vector2(1, 0) == Vector2(1, 0)); 36 | 37 | // Test Add 38 | assert((Vector2(1)+Vector2(2, 3)) == Vector2(3, 4)); 39 | assert((Vector2(1)+Vector2(2, 32)) != Vector2(3, 4)); 40 | 41 | // Test Subtract 42 | assert((Vector2(32)-Vector2(1, 1)) == Vector2(31, 31)); 43 | assert((Vector2(32)-Vector2(0, 0)) != Vector2(31, 31)); 44 | 45 | // ================ Vector3 =================== 46 | 47 | // Test IsVector2T 48 | assert(IsVector3T!(Vector3)); 49 | assert(IsVector3T!(Vector3i)); 50 | 51 | // ================ Vector4 =================== 52 | 53 | // Test IsVector4T 54 | assert(IsVector4T!(Vector4)); 55 | assert(IsVector4T!(Vector4i)); 56 | 57 | // =============== Matricies ================== 58 | 59 | // Test Matrix4x4 60 | Matrix4x4 mt4 = Matrix4x4.Identity; 61 | Matrix4x4 mt4_o = Matrix4x4([ 62 | [1, 0, 0, 2], 63 | [0, 1, 0, 5], 64 | [0, 0, 1, 3], 65 | [0, 0, 0, 1] 66 | ]); 67 | 68 | Matrix4x4 orth_mat = Matrix4x4.Orthographic(-1f, 1f, -1f, 1f, -1f, 1f); 69 | Matrix4x4 comp_orth = Matrix4x4([ 70 | [1f, 0f, 0f, -0f], 71 | [0f, 1f, 0f, -0f], 72 | [0f, 0f, -1f, -0f], 73 | [0f, 0f, 0f, 1f] 74 | ]); 75 | assert(orth_mat == comp_orth, "orth_mat != comp_orth \n" ~ orth_mat.ToString ~ ",\n" ~ comp_orth.ToString); 76 | } 77 | -------------------------------------------------------------------------------- /source/polyplex/math/linear/quaternion.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.linear.quaternion; 2 | import polyplex.math; 3 | 4 | public struct Quaternion { 5 | private float[4] data; 6 | 7 | /** 8 | Creates a new Quaternion, in which initial values are X, Y, Z and W. 9 | */ 10 | public this(float x, float y, float z, float w) { 11 | data[0] = x; 12 | data[1] = y; 13 | data[2] = z; 14 | data[3] = w; 15 | } 16 | 17 | /** 18 | Creates a new Quaternion, in which initial values are X, Y, Z and 0. 19 | */ 20 | public this(float x, float y, float z) { 21 | data[0] = x; 22 | data[1] = y; 23 | data[2] = z; 24 | data[3] = 0; 25 | } 26 | 27 | /** 28 | Creates a new Quaternion, in which initial values are X, Y, 0 and 0. 29 | */ 30 | public this(float x, float y) { 31 | data[0] = x; 32 | data[1] = y; 33 | data[2] = 0; 34 | data[3] = 0; 35 | } 36 | 37 | /** 38 | Creates a new Quaternion, in which all initial values are X. 39 | */ 40 | public this(float x) { 41 | data[0] = x; 42 | data[1] = x; 43 | data[2] = x; 44 | data[3] = x; 45 | } 46 | 47 | /** 48 | The X component. 49 | */ 50 | public float X() { return data[0]; } 51 | public void X(float value) { data[0] = value; } 52 | 53 | /** 54 | The Y component. 55 | */ 56 | public float Y() { return data[1]; } 57 | public void Y(float value) { data[1] = value; } 58 | 59 | /** 60 | The Z component. 61 | */ 62 | public float Z() { return data[2]; } 63 | public void Z(float value) { data[2] = value; } 64 | 65 | /** 66 | The W component. 67 | */ 68 | public float W() { return data[3]; } 69 | public void W(float value) { data[3] = value; } 70 | 71 | public void Reorient(Vector3 axis, float angle) { 72 | this.X = axis.X; 73 | this.Y = axis.Y; 74 | this.Z = axis.Z; 75 | this.W = angle; 76 | } 77 | 78 | public Vector3 ForwardDirection() { 79 | return Vector3( 80 | 2 * (X*Z + W*Y), 81 | 2 * (Y*Z - W*X), 82 | 1 - 2 * (X*X + Y*Y)); 83 | } 84 | 85 | public Vector3 UpDirection() { 86 | return Vector3( 87 | 2 * (X*Y + W*Z), 88 | 1 - 2 * (X*X + Z*Z), 89 | 2 * (Y*Z - W*X)); 90 | } 91 | 92 | public Vector3 LeftDirection() { 93 | return Vector3( 94 | 1 - 2 * (Y*Y + Z*Z), 95 | 2 * (X*Y + W*Z), 96 | 2 * (X*Z - W*Y)); 97 | } 98 | 99 | public void Rotate(T)(Vector3 axis, T theta) { 100 | X = Mathf.Sin(cast(real)theta/2) * axis.X; 101 | Y = Mathf.Sin(cast(real)theta/2) * axis.Y; 102 | Z = Mathf.Sin(cast(real)theta/2) * axis.Z; 103 | W = Mathf.Cos(cast(real)theta/2); 104 | } 105 | 106 | public static Quaternion Rotated(T)(Vector3 axis, T theta) { 107 | Quaternion q; 108 | q.Rotate(axis, theta); 109 | return q; 110 | } 111 | 112 | /*public Vector3 Rotate(T)(Vector3 axis, T angle) { 113 | Vector3 vec; 114 | vec.X = X; 115 | vec.Y = Y; 116 | vec.Z = Z; 117 | 118 | float vd = 2.0f * vec.Dot(axis); 119 | Vector3 vdv = Vector3(vec.X * vd, vec.Y * vd, vec.Z * vd); 120 | 121 | float ad = W*W - vec.Dot(vec); 122 | Vector3 adv = Vector3(axis.X * ad, axis.Y * ad, axis.Z * ad); 123 | 124 | float vc = 2.0f * W; 125 | Vector3 vcv = vec.Cross(axis); 126 | vcv.X *= vc; 127 | vcv.Y *= vc; 128 | vcv.Z *= vc; 129 | 130 | return vdv * adv + vcv ; 131 | }*/ 132 | 133 | public static Quaternion EulerToQuaternion(Vector3 euler) { 134 | Quaternion quat; 135 | 136 | // pitch 137 | double cp = Mathf.Cos(euler.X * 0.5); 138 | double sp = Mathf.Cos(euler.X * 0.5); 139 | 140 | // roll 141 | double cr = Mathf.Cos(euler.Y * 0.5); 142 | double sr = Mathf.Cos(euler.Y * 0.5); 143 | 144 | // yaw 145 | double cy = Mathf.Cos(euler.Z * 0.5); 146 | double sy = Mathf.Cos(euler.Z * 0.5); 147 | 148 | 149 | // convert to quaternion. 150 | quat.X = cy * sr * cp - sy * cr * sp; 151 | quat.Y = cy * cr * sp + sy * sr * cp; 152 | quat.Z = sy * cr * cp - cy * sr * sp; 153 | quat.W = cy * cr * cp + sy * sr * sp; 154 | return quat; 155 | } 156 | 157 | public static Quaternion FromMatrix(Matrix4x4 mat) { 158 | Quaternion qt = Quaternion.Identity; 159 | float tr = mat[0,0] + mat[1,1] + mat[2,2]; 160 | 161 | if (tr > 0) { 162 | float S = Mathf.Sqrt(tr+1.0) * 2; 163 | qt.X = (mat[2,1] - mat[1,2]) / S; 164 | qt.Y = (mat[0,2] - mat[2,0]) / S; 165 | qt.Z = (mat[1,0] - mat[0,1]) / S; 166 | qt.W = 0.25 * S; 167 | return qt; 168 | } 169 | 170 | if ((mat[0,0] > mat[1,1])&(mat[0,0] > mat[2,2])) { 171 | float S = Mathf.Sqrt(1.0 + mat[0,0] - mat[1,1] - mat[2,2]) * 2; 172 | qt.X = 0.25 * S; 173 | qt.Y = (mat[0,1] + mat[1,0]) / S; 174 | qt.Z = (mat[0,2] + mat[2,0]) / S; 175 | qt.W = (mat[2,1] - mat[1,2]) / S; 176 | return qt; 177 | } 178 | 179 | if (mat[1,1] > mat[2,2]) { 180 | float S = Mathf.Sqrt(1.0 + mat[1,1] - mat[0,0] - mat[2,2]) * 2; 181 | qt.X = (mat[0,1] + mat[1,0]) / S; 182 | qt.Y = 0.25 * S; 183 | qt.Z = (mat[1,2] - mat[2,1]) / S; 184 | qt.W = (mat[0,2] + mat[2,0]) / S; 185 | return qt; 186 | } 187 | 188 | float S = Mathf.Sqrt(1.0 + mat[2,2] - mat[0,0] - mat[1,1]) * 2; 189 | qt.X = (mat[0,2] + mat[2,0]) / S; 190 | qt.Y = (mat[1,2] - mat[2,1]) / S; 191 | qt.Z = 0.25 * S; 192 | qt.W = (mat[1,0] + mat[0,1]) / S; 193 | return qt; 194 | } 195 | 196 | // Binary actions. 197 | public @trusted nothrow Quaternion opBinary(string op, T2)(T2 other) if (is (T2 : Quaternion)) { 198 | // Operation on these, (due to being smallest size) can be done faster this way. 199 | mixin(q{ 200 | return Quaternion( 201 | this.X * other.W + this.Y * other.Z + this.Z * other.Y - this.W * other.X, 202 | -this.X * this.Z - this.Y * other.W + this.Z * other.X + this.W * other.Y, 203 | this.X * this.Y - this.Y * other.X + this.Z * other.W + this.W * other.Z, 204 | -this.X * this.X - this.Y * other.Y + this.Z * other.Z + this.W * other.W); 205 | }.Format(op) 206 | ); 207 | } 208 | 209 | public @trusted Quaternion Normalized() { 210 | import polyplex.math; 211 | double n = Mathf.Sqrt(X*X+Y*Y+Z*Z+W*W); 212 | return Quaternion(X/n, Y/n, Z/n, W/n); 213 | } 214 | 215 | public static Quaternion Identity() { 216 | return Quaternion(0f, 0f, 0f, 0f); 217 | } 218 | 219 | public string toString() { 220 | import polyplex.utils.strutils; 221 | return "<{0}, {1}, {2}, {3}>".Format(X, Y, Z, W); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /source/polyplex/math/linear/transform.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.linear.transform; 2 | import polyplex.math; 3 | 4 | public class Transform { 5 | public Transform Parent; 6 | public Vector3 LocalScale; 7 | public Quaternion LocalRotation; 8 | public Vector3 LocalPosition; 9 | 10 | public Transform LocalTransform() { 11 | return new Transform(LocalScale, LocalRotation, LocalPosition); 12 | } 13 | 14 | this(Transform parent) { 15 | this.Parent = parent; 16 | LocalScale = Vector3.One; 17 | LocalRotation = Quaternion.Identity; 18 | LocalPosition = Vector3.Zero; 19 | } 20 | 21 | this(Vector3 scale, Quaternion rotation, Vector3 position) { 22 | this(null); 23 | LocalScale = scale; 24 | LocalRotation = rotation; 25 | LocalPosition = position; 26 | } 27 | 28 | this() { 29 | this(null); 30 | } 31 | 32 | private Matrix4x4 lscale() { 33 | return Matrix4x4.Scaling(LocalScale); 34 | } 35 | 36 | private Matrix4x4 lrot() { 37 | return Matrix4x4.FromQuaternion(LocalRotation); 38 | } 39 | 40 | private Matrix4x4 ltrans() { 41 | return Matrix4x4.Translation(LocalPosition); 42 | } 43 | 44 | public Matrix4x4 TRS() { 45 | if (Parent is null) 46 | return (ltrans * lrot * lscale); 47 | return Parent.TRS*(ltrans * lrot * lscale); 48 | } 49 | 50 | public Vector3 Scale() { 51 | if (Parent is null) return LocalScale; 52 | return (TRS).ToScaling(); 53 | } 54 | 55 | public Quaternion Rotation() { 56 | if (Parent is null) return LocalRotation; 57 | return Rotation.FromMatrix(TRS); 58 | } 59 | 60 | public Vector3 Position() { 61 | if (Parent is null) return LocalPosition; 62 | return TRS.ToTranslation(); 63 | } 64 | 65 | public Vector3 Up() { 66 | return Rotation.UpDirection; 67 | } 68 | 69 | public Vector3 Down() { 70 | Vector3 up = Up; 71 | return Vector3(-up.X, -up.Y, -up.Z); 72 | } 73 | 74 | public Vector3 Forward() { 75 | return Rotation.ForwardDirection; 76 | } 77 | 78 | public Vector3 Back() { 79 | Vector3 forward = Forward; 80 | return Vector3(-forward.X, -forward.Y, -forward.Z); 81 | } 82 | 83 | public Vector3 Left() { 84 | return Rotation.LeftDirection; 85 | } 86 | 87 | public Vector3 Right() { 88 | Vector3 left = Left; 89 | return Vector3(-left.X, -left.Y, -left.Z); 90 | } 91 | } 92 | 93 | public class Transform2D { 94 | public Transform2D Parent; 95 | public Vector2 LocalScale; 96 | public float LocalRotation; 97 | public Vector2 LocalPosition; 98 | 99 | public Transform2D LocalTransform() { 100 | return new Transform2D(LocalScale, LocalRotation, LocalPosition); 101 | } 102 | 103 | this(Transform2D parent) { 104 | this.Parent = parent; 105 | LocalScale = Vector2.One; 106 | LocalRotation = 0f; 107 | LocalPosition = Vector2.Zero; 108 | } 109 | 110 | 111 | this(Vector2 scale, float rotation, Vector2 position) { 112 | LocalScale = scale; 113 | LocalRotation = rotation; 114 | LocalPosition = position; 115 | } 116 | 117 | this() { 118 | this(null); 119 | } 120 | 121 | private Matrix4x4 lscale() { 122 | return Matrix4x4.Scaling(Vector3(LocalScale)); 123 | } 124 | 125 | private Matrix4x4 lrot() { 126 | return Matrix4x4.RotationZ(this.LocalRotation); 127 | } 128 | 129 | private Matrix4x4 ltrans() { 130 | return Matrix4x4.Translation(Vector3(LocalPosition)); 131 | } 132 | 133 | public Matrix4x4 TRS() { 134 | if (Parent is null) 135 | return (ltrans * lrot * lscale); 136 | return Parent.TRS*(ltrans * lrot * lscale); 137 | } 138 | 139 | public Matrix4x4 MatrixScale() { 140 | return lscale; 141 | } 142 | 143 | public Matrix4x4 MatrixRotation() { 144 | return lrot; 145 | } 146 | 147 | public Matrix4x4 MatrixPosition() { 148 | return ltrans; 149 | } 150 | 151 | public Vector2 Scale() { 152 | if (Parent is null) return LocalScale; 153 | return Vector2(TRS.ToScaling()); 154 | } 155 | 156 | public float Rotation() { 157 | if (Parent is null) return LocalRotation; 158 | return Parent.Rotation+LocalRotation; 159 | } 160 | 161 | public Vector2 Position() { 162 | if (Parent is null) return LocalPosition; 163 | return Vector2(TRS.ToTranslation()); 164 | } 165 | 166 | public void Rotate(float amount) { 167 | LocalRotation = amount; 168 | } 169 | 170 | public Vector2 Up() { 171 | return Vector2( 172 | Vector2.Up.X * Mathf.Cos(Rotation) - Vector2.Up.Y * Mathf.Sin(Rotation), 173 | Vector2.Up.X * Mathf.Sin(Rotation) + Vector2.Up.Y * Mathf.Cos(Rotation) 174 | ); 175 | } 176 | 177 | public Vector2 Down() { 178 | Vector2 up = Up; 179 | return Vector2(-up.X, -up.Y); 180 | } 181 | 182 | public Vector2 Left() { 183 | return Vector2( 184 | Vector2.Left.X * Mathf.Cos(Rotation) - Vector2.Left.Y * Mathf.Sin(Rotation), 185 | Vector2.Left.X * Mathf.Sin(Rotation) + Vector2.Left.Y * Mathf.Cos(Rotation) 186 | ); 187 | } 188 | 189 | public Vector2 Right() { 190 | Vector2 left = Left; 191 | return Vector2(-left.X, -left.Y); 192 | } 193 | } -------------------------------------------------------------------------------- /source/polyplex/math/mathf.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.mathf; 2 | import std.traits; // for some introspection (ei isScalar) 3 | static import std.math; // for creating public aliases 4 | // public imports of math constants 5 | public import std.math : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, LN10, 6 | LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; 7 | /// TAU = PI*2 8 | public enum real TAU = 6.28318530717958647692; 9 | 10 | // public aliases of std library math functions to capitalized aliases 11 | public alias Abs = std.math.abs; 12 | public alias Sin = std.math.sin; 13 | public alias Cos = std.math.cos; 14 | public alias Tan = std.math.tan; 15 | public alias Sqrt = std.math.sqrt; 16 | public alias ASin = std.math.asin; 17 | public alias ACos = std.math.acos; 18 | public alias ATan = std.math.atan; 19 | public alias ATan2 = std.math.atan2; 20 | public alias SinH = std.math.sinh; 21 | public alias CosH = std.math.cosh; 22 | public alias TanH = std.math.tanh; 23 | public alias ASinH = std.math.asinh; 24 | public alias ACosH = std.math.acosh; 25 | public alias ATanH = std.math.atanh; 26 | public alias Ceil = std.math.ceil; 27 | public alias Floor = std.math.floor; 28 | public alias Round = std.math.round; 29 | public alias Truncate = std.math.trunc; 30 | public alias Pow = std.math.pow; 31 | public alias Exp = std.math.exp; 32 | public alias Exp2 = std.math.exp2; 33 | public alias LogNatural = std.math.log; 34 | public alias LogBase2 = std.math.log2; 35 | public alias LogBase10 = std.math.log10; 36 | public alias Fmod = std.math.fmod; 37 | public alias Remainder = std.math.remainder; 38 | public alias IsFinite = std.math.isFinite; 39 | public alias IsIdentical = std.math.isIdentical; 40 | public alias IsInfinity = std.math.isInfinity; 41 | public alias IsNaN = std.math.isNaN; 42 | public alias IsPowerOf2 = std.math.isPowerOf2; 43 | public alias Sign = std.math.sgn; 44 | 45 | 46 | /// Checks if two floating-point scalars are approximately 47 | /// equal to each other by some epsilon 48 | bool ApproxEquals(T)(T a, T b, T eps) pure nothrow if (__traits(isFloating, T)) { 49 | return Abs(a-b) < eps; 50 | } 51 | unittest { 52 | assert(ApproxEquals(1f, 1.001f, 0.1f)); 53 | assert(!ApproxEquals(-1f, 1.001f, 0.1f)); 54 | assert(ApproxEquals(10f, 10f, 0.001f)); 55 | assert(!ApproxEquals(10f, 10.01f, 0.001f)); 56 | } 57 | 58 | /// Converts quantity of degrees to radians 59 | T ToRadians(T)(T degrees) pure nothrow { 60 | return (cast(T)(PI)*degrees)/cast(T)180; 61 | } 62 | /// Converts quantity of radians to degrees 63 | T ToDegrees(T)(T radians) pure nothrow { 64 | return (180 * radians)/cast(T)PI; 65 | } 66 | unittest { 67 | assert(ApproxEquals(ToRadians(90f), ToRadians(ToDegrees(PI_2)), 0.1f)); 68 | assert(ApproxEquals(ToRadians(360f+45f)-TAU, PI_4, 0.01f)); 69 | } 70 | 71 | /// Minimum of two scalar elements 72 | T Min(T)(T scalar_a, T scalar_b) pure nothrow if (__traits(isScalar, T)) { 73 | return ( scalar_a < scalar_b ? scalar_a : scalar_b ); 74 | } 75 | unittest { 76 | assert(Min(5, 10) == 5); 77 | assert(Min(3, -5) == -5); 78 | assert(Min(25.0f, 3) == 3); 79 | assert(!__traits(compiles, Min("a", "b"))); 80 | assert(__traits(compiles, Min('a', 'b'))); 81 | } 82 | 83 | /// Maximum of two scalar elements 84 | T Max(T)(T scalar_a, T scalar_b) pure nothrow if (__traits(isScalar, T)) { 85 | return ( scalar_a > scalar_b ? scalar_a : scalar_b ); 86 | } 87 | unittest { 88 | assert(Max(5, 10) == 10); 89 | assert(Max(3, -5) == 3); 90 | assert(Max(25.0f, 3) == 25.0f); 91 | assert(!__traits(compiles, Max("a", "b"))); 92 | assert(__traits(compiles, Max('a', 'b'))); 93 | } 94 | 95 | /// Clamps scalar elements 96 | T Clamp(T)(T x, T min, T max) pure nothrow if (__traits(isScalar, T)) { 97 | if ( x < min ) return min; 98 | if ( x > max ) return max; 99 | return x; 100 | } 101 | unittest { 102 | assert(Clamp(3, 0, 10) == 3); 103 | assert(Clamp(3, 5, 10) == 5); 104 | assert(Clamp(3, -2, 2) == 2); 105 | } 106 | 107 | /// Steps scalar `a` on edge `edge`. Returns 0 if a < edge, otherwise 1 108 | T Step(T)(T edge, T a) pure nothrow if (__traits(isScalar, T)) { 109 | return (a >= edge); 110 | } 111 | 112 | /// -- All interpolation methods based from 113 | /// http://paulbourke.net/miscellaneous/interpolation/ 114 | 115 | /** Linear interpolation on scalar elements. Interpolates between two scalar 116 | values `x` and `y` using a linear gradient `a` 117 | Params: 118 | x = The minimum element to interpolate by. The result of this function 119 | equals `x` when `a = 0`. 120 | y = The maximum element to interpolate by. The result of this function 121 | equals `x` when `a = 1`. 122 | a = The gradient to interpolate between `x` and `y`. Should be a value 123 | between `0f` and `1f`, although this function will clamp it to that 124 | range. For example, the result of this function will equal (x+y)/2 125 | when `a = 0.5` 126 | **/ 127 | T Lerp(T)(T x, T y, float a) pure nothrow if (__traits(isScalar, T)) { 128 | a = Clamp(a, 0f, 1f); 129 | return cast(T)(x*(1.0f - a) + y*a); 130 | } 131 | 132 | /// Alias for linear interpolation, to follow the same GLSL convention 133 | alias Mix = Lerp; 134 | 135 | unittest { 136 | assert(Linear(0f, 1f, 0.5f) == 0.5f); 137 | assert(Linear(0f, 2f, 0.5f) == 1.0f); 138 | assert(Linear(0f, 2f, 0.0f) == 0.0f); 139 | assert(Linear(0f, 2f, 1.0f) == 2.0f); 140 | assert(Linear(0f, 2f, 3.0f) == 2.0f); 141 | assert(Linear(0f, 2f, -1.0f) == 0.0f); 142 | } 143 | 144 | /** Cosine interpolation on scalar elements. It interpolates between 145 | two scalars `x` and `y` using a cosine-weighted `a` (this function 146 | applies the cosine-weight). Check `Linear` for more 147 | details. 148 | **/ 149 | T Cosine(T)(T x, T y, float a) pure nothrow if (__traits(isScalar, T)) { 150 | return Lerp(x, y, (1.0f - Cos(a*PI))/2.0f); 151 | } 152 | unittest { 153 | assert(__traits(compiles, Cosine(0, 1, 0.5f))); 154 | assert(__traits(compiles, Cosine(0f, 1f, 0.5f))); 155 | } 156 | 157 | /** Cube interpolation using Catmull-Rom splines on scalar elements. 158 | Interpolates between two scalars `y` and `z` using a gradient `a` that 159 | takes `x <-> y` and `z <-> w` into account in order to offer better 160 | continuity between segments. Check `LinearInerpolation` for more details. 161 | **/ 162 | T Spline(T)(T x, T y, T z, T w, float a) pure nothrow if (__traits(isScalar, T)) { 163 | a = Clamp(a, 0f, 1f); 164 | // Use slope between last and next point as derivative at current point 165 | T cx = cast(T)(-0.5*x + 1.5*y - 1.5*z + 0.5*w); 166 | T cy = cast(T)(x - 2.5*y + 2*z - 0.5*w); 167 | T cz = cast(T)(-0.5*x + 0.5*z); 168 | T cw = y; 169 | return cast(T)(cx*a*a*a + cy*a*a + cz*a + cw); 170 | } 171 | unittest { 172 | assert(__traits(compiles, Cubic(0, 1, 2, 3, 0.5f))); 173 | assert(__traits(compiles, Cubic(0f, 1f, 2f, 3f, 0.5f))); 174 | } 175 | 176 | /** Hermite interpolation on scalar elements as described by GLSL smoothstep. 177 | Interpolates between `x` and `y` using gradient `a` that allows 178 | smooth transitions as the gradient approaches `1` or `0`. See 179 | `LinearInterpolation` for more details. 180 | **/ 181 | T SmoothStep(T)(T x, T y, float a) pure nothrow if (__traits(isScalar, T)) { 182 | T t = cast(T)Clamp((a - x)/(y - x), 0f, 1f); 183 | return t*t*cast(T)(3f - 2f*t); 184 | } 185 | unittest { 186 | assert(__traits(compiles, Smoothstep(0, 1, 0.5f))); 187 | assert(__traits(compiles, Smoothstep(0f, 1f, 0.5f))); 188 | } 189 | 190 | /** Hermite interpolation on scalar elements. Similar to `CubicInterpolation` 191 | except that you have control over the tension (tightening of the 192 | curvature) and bias (twists the curve about known points). 193 | Params: 194 | Tension = any value from -1 to 1. -1 is low tension, 1 is high tension 195 | Bias = any value from -1 to 1. 0 is no bias, -1 is biased towards first, 196 | segment, 1 is biased towards the last segment 197 | **/ 198 | T Hermite(T)(T x, T y, T z, T w, float a, float tension, float bias) pure nothrow if (__traits(isScalar, T)) { 199 | a = Clamp(a, 0f, 1f); 200 | float a2 = a*a, a3 = a2*a; 201 | tension = (1f - tension)/2f; 202 | 203 | float m_y = (y-z)*(1f+bias)*tension + (z-y)*(1f-bias)*tension; 204 | float m_z = (z-y)*(1f+bias)*tension + (w-z)*(1f-bias)*tension; 205 | float a_x = 2f*a3 - 3f*a2 + 1f; 206 | float a_y = a3 - 2f*a2 + a; 207 | float a_z = a3 - a2; 208 | float a_w = -2f*a3 + 3f*a2; 209 | 210 | return cast(T)(a_x*y + a_y*m_y + a_z*m_z + a_w*z); 211 | } 212 | 213 | unittest { 214 | assert(__traits(compiles, Hermite(0, 1, 2, 3, 0.5f, 0.2f, 0f))); 215 | assert(__traits(compiles, Hermite(0f, 1f, 2f, 3f, 0.5f, 0.2f, 0f))); 216 | } 217 | -------------------------------------------------------------------------------- /source/polyplex/math/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.math; 2 | 3 | public static import Mathf = polyplex.math.mathf; 4 | public import polyplex.math.linear; 5 | public import polyplex.math.rectangle; 6 | public import polyplex.math.random; -------------------------------------------------------------------------------- /source/polyplex/math/random.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.random; 2 | import rnd = std.random; 3 | 4 | public class Random { 5 | private rnd.Random random; 6 | private int seed; 7 | 8 | this() { 9 | import core.stdc.time; 10 | this.seed = cast(int)time(null); 11 | random = rnd.Random(this.seed); 12 | } 13 | 14 | this (int seed) { 15 | this.seed = seed; 16 | random = rnd.Random(seed); 17 | } 18 | 19 | public int Next() { 20 | advance_seed(); 21 | return rnd.uniform!int(random); 22 | } 23 | 24 | public int Next(int max) { 25 | advance_seed(); 26 | return rnd.uniform(0, max, random); 27 | } 28 | 29 | public int Next(int min, int max) { 30 | advance_seed(); 31 | return rnd.uniform(min, max, random); 32 | } 33 | 34 | public float NextFloat() { 35 | advance_seed(); 36 | return rnd.uniform01!float(random); 37 | } 38 | 39 | public double NextDouble() { 40 | advance_seed(); 41 | return rnd.uniform01!double(random); 42 | } 43 | 44 | private void advance_seed() { 45 | this.seed++; 46 | random.seed(this.seed); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/polyplex/math/rectangle.d: -------------------------------------------------------------------------------- 1 | module polyplex.math.rectangle; 2 | import polyplex.math; 3 | import std.traits; 4 | 5 | /// Base template for all AABB-types. 6 | /// Params: 7 | /// type = all values get stored as this type 8 | struct RectangleT(T) if (isNumeric!T) { 9 | public: 10 | union { 11 | struct { 12 | /// X coordinate of the top-left corner of the rectangle 13 | T X = 0; 14 | 15 | /// Y coordinate of the top-left corner of the rectangle 16 | T Y = 0; 17 | 18 | /// The width of the rectangle 19 | T Width = 0; 20 | 21 | /// The height of the rectangle 22 | T Height = 0; 23 | 24 | } 25 | 26 | /// Rectangle represented as an array, useful for passing data to the graphics API 27 | T[4] data; 28 | } 29 | 30 | /// Constructor 31 | this(W, H)(W width, H height) if (isNumeric!W && isNumeric!H) { 32 | this.Width = cast(T)width; 33 | this.Height = cast(T)height; 34 | } 35 | 36 | /// Constructor 37 | this(X, Y, W, H)(X x, Y y, W width, H height) if (isNumeric!X && isNumeric!Y && isNumeric!W && isNumeric!H) { 38 | this.X = cast(T)x; 39 | this.Y = cast(T)y; 40 | this.Width = cast(T)width; 41 | this.Height = cast(T)height; 42 | } 43 | 44 | @trusted RectangleT!T opBinary(string op, T)(T other) if (isNumeric!(T)) { 45 | import std.format; 46 | mixin(q{ 47 | return Rectangle( 48 | X %1$s cast(T)other, 49 | Y %1$s cast(T)other, 50 | Width %1$s cast(T)other, 51 | Height %1$s cast(T)other); 52 | }.format(op)); 53 | } 54 | 55 | @trusted RectangleT!T opBinary(string op, T)(T other) if (IsRectangleT!T) { 56 | import std.format; 57 | mixin(q{ 58 | return Rectangle( 59 | X %1$s cast(T)other.X, 60 | Y %1$s cast(T)other.Y, 61 | Width %1$s cast(T)other.Width, 62 | Height %1$s cast(T)other.Height); 63 | }.format(op)); 64 | } 65 | 66 | /** 67 | Gets the X coordinate of the left side of the rectangle 68 | */ 69 | T Left() { return this.X; } 70 | 71 | /** 72 | Gets the X coordinate of the right side of the rectangle 73 | */ 74 | T Right() { return this.X + this.Width; } 75 | 76 | /** 77 | Gets the Y coordinate of the top of the rectangle 78 | */ 79 | T Top() { return this.Y; } 80 | 81 | /** 82 | Gets the Y coordinate of the bottom of the rectangle 83 | */ 84 | T Bottom() { return this.Y + this.Height; } 85 | 86 | /** 87 | Gets the center of the rectangle (as floats) 88 | */ 89 | Vector2 Center() { return Vector2(this.X + (this.Width/2), this.Y + (this.Height/2)); } 90 | 91 | /** 92 | Gets wether this rectangle intersects another 93 | */ 94 | bool Intersects(T)(T other) if (IsRectangleT!T) { 95 | bool v = (other.Left >= this.Right || other.Right <= this.Left || other.Top >= this.Bottom || other.Bottom <= this.Top); 96 | return !v; 97 | } 98 | 99 | /** 100 | Gets wether a 2D vector is intersecting this rectangle 101 | */ 102 | bool Intersects(U)(U other) if (IsVector2T!U) { 103 | bool v = (other.X >= this.Right || other.X < this.Left || other.Y >= this.Bottom || other.Y < this.Top); 104 | return !v; 105 | } 106 | 107 | /** 108 | Returns a displaced version of this rectangle 109 | */ 110 | RectangleT!T Displace(T x, T y) { 111 | return RectangleT!T(this.X+x, this.Y+y, this.Width, this.Height); 112 | } 113 | 114 | /** 115 | Returns an expanded version of this rectangle 116 | */ 117 | RectangleT!T Expand(T)(T x, T y) { 118 | return RectangleT!T(this.X-x, this.Y-y, this.Width+(x*2), this.Height+(y*2)); 119 | } 120 | } 121 | 122 | // Checks wether a type is a rectangle type 123 | enum IsRectangleT(T) = is(T : RectangleT!U, U...); 124 | 125 | alias Rectangle = RectangleT!float; 126 | alias Rectanglei = RectangleT!int; -------------------------------------------------------------------------------- /source/polyplex/package.d: -------------------------------------------------------------------------------- 1 | module polyplex; 2 | 3 | import bindbc.sdl; 4 | import bindbc.opengl; 5 | import openal; 6 | 7 | import polyplex.utils.logging; 8 | import std.stdio; 9 | import std.conv; 10 | static import std.file; 11 | static import std.process; 12 | 13 | public import polyplex.core; 14 | public import polyplex.math; 15 | public import polyplex.utils.logging; 16 | 17 | 18 | public static GraphicsBackend ChosenBackend = GraphicsBackend.OpenGL; 19 | private static bool core_init = false; 20 | private static bool vk_init = false; 21 | private static bool gl_init = false; 22 | 23 | 24 | public enum GraphicsBackend { 25 | OpenGL 26 | } 27 | 28 | private string get_arch() { 29 | version(X86) return "i386"; 30 | version(X86_64) return "amd64"; 31 | version(ARM) return "arm"; 32 | version(AArch64) return "arm64"; 33 | } 34 | 35 | private string get_system_lib(string libname, bool s = true) { 36 | string lstr = "libs/"~get_arch()~"/lib"~libname~".so"; 37 | 38 | string plt = "linux/bsd"; 39 | version(Windows) { 40 | lstr = "libs/"~get_arch()~"/"~libname~".dll"; 41 | plt = "win32"; 42 | } 43 | 44 | version(FreeBSD) { 45 | lstr = "libs/"~get_arch()~"/lib"~libname~"-freebsd.so"; 46 | plt = "linux/bsd"; 47 | } 48 | 49 | version(OpenBSD) { 50 | lstr = "libs/"~get_arch()~"/lib"~libname~"-openbsd.so"; 51 | plt = "linux/bsd"; 52 | } 53 | 54 | version(OSX) { 55 | // lstr = "libs/"~get_arch()~"/lib"~libname~".dylib"; 56 | // plt = "darwin/osx"; 57 | } 58 | Logger.Info("Binding library {0}: [{1} on {2}] from {3}", libname, plt, get_arch(), lstr); 59 | return lstr; 60 | } 61 | 62 | private string trimexe(string input) { 63 | string i = input; 64 | version(Windows) { 65 | while (i[i.length-1] != '\\') { 66 | i.length--; 67 | } 68 | return i; 69 | } 70 | else { 71 | while (i[i.length-1] != '/') { 72 | i.length--; 73 | } 74 | return i; 75 | } 76 | } 77 | 78 | /** 79 | De-init libraries 80 | */ 81 | public void UnInitLibraries() { 82 | unloadSDL(); 83 | unloadOpenGL(); 84 | unloadOAL(); 85 | core_init = false; 86 | } 87 | 88 | /* 89 | InitLibraries loads the Derelict libraries for Vulkan, SDL and OpenGL 90 | */ 91 | public void InitLibraries() { 92 | /*if (!core_init) { 93 | if (std.file.exists("libs/")) { 94 | // Load bundled libraries. 95 | Logger.Info("Binding to runtime libraries..."); 96 | string path_sep = ":"; 97 | string sys_sep = "/"; 98 | version(Windows) { 99 | path_sep = ";"; 100 | sys_sep = "\\"; 101 | } 102 | string path = std.process.environment["PATH"]; 103 | string path_begin = std.file.thisExePath(); 104 | path_begin = trimexe(path_begin); 105 | std.process.environment["PATH"] = path_begin ~ "libs" ~ sys_sep ~ get_arch() ~ path_sep ~ path; 106 | Logger.Debug("Updated PATH to {0}", std.process.environment["PATH"]); 107 | loadSDL(get_system_lib("SDL2").dup.ptr); 108 | } else { 109 | // Load system libraries 110 | Logger.Info("Binding to system libraries...."); 111 | SDLSupport support = loadSDL(); 112 | if (support == SDLSupport.noLibrary) Logger.Fatal("SDL2 not found!"); 113 | } 114 | loadOAL(); 115 | SDL_version linked; 116 | SDL_version compiled; 117 | SDL_GetVersion(&linked); 118 | SDL_VERSION(&compiled); 119 | Logger.Debug("SDL (compiled against): {0}.{1}.{2}", to!string(compiled.major), to!string(compiled.minor), to!string(compiled.patch)); 120 | Logger.Debug("SDL (linked): {0}.{1}.{2}", to!string(linked.major), to!string(linked.minor), to!string(linked.patch)); 121 | core_init = true; 122 | }*/ 123 | loadOpenGL(); 124 | //gl_init = true; 125 | } 126 | 127 | version(OSX) { 128 | static assert(0, "macOS is NOT supported with Polyplex; and won't be for the forseeable future, sorry."); 129 | } 130 | 131 | shared static this() { 132 | bool success = loadOAL(); 133 | Logger.Info("Bound OpenAL {0}!", success ? "successfully" : "unsuccessfully"); 134 | 135 | 136 | import bindbc.sdl; 137 | SDLSupport support = loadSDL(); 138 | if (support == SDLSupport.noLibrary) Logger.Fatal("SDL2 not found!"); 139 | SDL_version linked; 140 | SDL_version compiled; 141 | SDL_GetVersion(&linked); 142 | SDL_VERSION(&compiled); 143 | Logger.Info("SDL (compiled against): {0}.{1}.{2}", to!string(compiled.major), to!string(compiled.minor), to!string(compiled.patch)); 144 | Logger.Info("SDL (linked): {0}.{1}.{2}", to!string(linked.major), to!string(linked.minor), to!string(linked.patch)); 145 | } -------------------------------------------------------------------------------- /source/polyplex/utils/logging.d: -------------------------------------------------------------------------------- 1 | module polyplex.utils.logging; 2 | import polyplex.utils.strutils; 3 | import std.conv; 4 | import std.array; 5 | import std.stdio; 6 | import core.vararg; 7 | 8 | public enum LogType { 9 | Off = 0x00, 10 | Info = 0x01, 11 | Success = 0x02, 12 | Warning = 0x04, 13 | Error = 0x08, 14 | Fatal = 0x10, 15 | Recover = 0x20, 16 | Debug = 0x40, 17 | VerboseDebug = 0x80 18 | } 19 | 20 | public static LogType LogLevel = LogType.Info | LogType.Recover | LogType.Success | LogType.Warning | LogType.Error | LogType.Fatal; 21 | public class Logger { 22 | 23 | /** 24 | Allows you to put a log of LogType type with an simple string message. 25 | */ 26 | public static void Log(string message, LogType type = LogType.Info) { 27 | Log(message, type, null); 28 | } 29 | 30 | /** 31 | Log a verbose debug message. 32 | */ 33 | public static void VerboseDebug (string message) { 34 | Logger.VerboseDebug(message, null); 35 | } 36 | 37 | /** 38 | Log a debug message. 39 | */ 40 | public static void Debug (string message) { 41 | Logger.Debug(message, null); 42 | } 43 | 44 | /** 45 | Log a success message. 46 | */ 47 | public static void Success (string message) { 48 | Logger.Success(message, null); 49 | } 50 | 51 | /** 52 | Log an info message. 53 | */ 54 | public static void Info (string message) { 55 | Logger.Info(message, null); 56 | } 57 | 58 | /** 59 | Log a warning message. 60 | */ 61 | public static void Warn (string message) { 62 | Logger.Warn(message, null); 63 | } 64 | 65 | /** 66 | Log a Error message. 67 | */ 68 | public static void Err (string message) { 69 | Logger.Err(message, null); 70 | } 71 | 72 | /** 73 | Log a Fatal Error message. 74 | */ 75 | public static void Fatal (string message) { 76 | Logger.Fatal(message, null); 77 | } 78 | 79 | /** 80 | Log an Error Recovery message. 81 | */ 82 | public static void Recover (string message) { 83 | Logger.Recover(message, null); 84 | } 85 | 86 | /** 87 | Log a verbose debug message. 88 | */ 89 | public static void VerboseDebug(T...) (string message, T args) { 90 | Logger.Log(message, LogType.VerboseDebug, args); 91 | } 92 | 93 | /** 94 | Log a debug message. 95 | */ 96 | public static void Debug(T...) (string message, T args) { 97 | Logger.Log(message, LogType.Debug, args); 98 | } 99 | 100 | /** 101 | Log an info message. 102 | */ 103 | public static void Info(T...) (string message, T args) { 104 | Logger.Log(message, LogType.Info, args); 105 | } 106 | 107 | /** 108 | Log a success message. 109 | */ 110 | public static void Success(T...) (string message, T args) { 111 | Logger.Log(message, LogType.Success, args); 112 | } 113 | 114 | /** 115 | Log a warning message. 116 | */ 117 | public static void Warn(T...) (string message, T args) { 118 | Logger.Log(message, LogType.Warning, args); 119 | } 120 | 121 | /** 122 | Log an Error message. 123 | */ 124 | public static void Err(T...) (string message, T args) { 125 | Logger.Log(message, LogType.Error, args); 126 | } 127 | 128 | /** 129 | Log a Fatal Error message. 130 | */ 131 | public static void Fatal(T...) (string message, T args) { 132 | Logger.Log(message, LogType.Fatal, args); 133 | } 134 | 135 | /** 136 | Log a Error Recovery message. 137 | */ 138 | public static void Recover(T...) (string message, T args) { 139 | Logger.Log(message, LogType.Recover, args); 140 | } 141 | 142 | /* Raw impl */ 143 | 144 | public static void Log(T...) (string message, LogType type, T args) { 145 | bool color = false; 146 | if ((LogLevel != LogType.Off && (LogLevel & type)) || (type == LogType.Fatal || type == LogType.Recover)) { 147 | import colorize : fg, color, cwriteln; 148 | string stxt = to!string(type); 149 | if (type == LogType.VerboseDebug) stxt = stxt.color(fg.cyan); 150 | if (type == LogType.Debug) stxt = stxt.color(fg.blue); 151 | if (type == LogType.Info) stxt = stxt.color(fg.light_black); 152 | if (type == LogType.Success) stxt = stxt.color(fg.light_green); 153 | if (type == LogType.Warning) stxt = stxt.color(fg.yellow); 154 | if (type == LogType.Error) stxt = stxt.color(fg.light_red); 155 | if (type == LogType.Fatal) stxt = stxt.color(fg.red); 156 | if (type == LogType.Recover) stxt = stxt.color(fg.light_blue); 157 | string txt = "<".color(fg.light_black) ~ stxt ~ ">".color(fg.light_black); 158 | 159 | cwriteln(txt, " ", Format(message, args)); 160 | } 161 | if (type == LogType.Fatal) throw new Exception(Format(message, args)); 162 | 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /source/polyplex/utils/mathutils.d: -------------------------------------------------------------------------------- 1 | module polyplex.utils.mathutils; 2 | 3 | class Math { 4 | /** 5 | Returns the smallest of the two specified doubles. 6 | */ 7 | public static double Min(double a, double b) { 8 | if (a < b) return a; 9 | return b; 10 | } 11 | 12 | /** 13 | Returns the biggest of the two specified doubles. 14 | */ 15 | public static double Max(double a, double b) { 16 | if (a > b) return a; 17 | return b; 18 | } 19 | 20 | /** 21 | Returns the smallest of the two specified integers. 22 | */ 23 | public static int Min(int a, int b) { return cast(int)Min(cast(double)a, cast(double)b); } 24 | 25 | /** 26 | Returns the biggest of the two specified integers. 27 | */ 28 | public static int Max(int a, int b) { return cast(int)Max(cast(double)a, cast(double)b); } 29 | } -------------------------------------------------------------------------------- /source/polyplex/utils/package.d: -------------------------------------------------------------------------------- 1 | module polyplex.utils; 2 | public import polyplex.utils.logging; 3 | public import polyplex.utils.sdlbool; 4 | public import polyplex.utils.strutils; -------------------------------------------------------------------------------- /source/polyplex/utils/sdlbool.d: -------------------------------------------------------------------------------- 1 | module polyplex.utils.sdlbool; 2 | import bindbc.sdl; 3 | 4 | /** 5 | Converts an SDL_bool boolean in to an D boolean. 6 | */ 7 | bool FromSDL(SDL_bool b) { 8 | if (b == SDL_bool.SDL_TRUE) return true; 9 | return false; 10 | } 11 | 12 | /** 13 | Converts an D boolean in to an SDL_bool boolean. 14 | */ 15 | SDL_bool ToSDL(bool b) { 16 | if (b) return SDL_bool.SDL_TRUE; 17 | return SDL_bool.SDL_FALSE; 18 | } -------------------------------------------------------------------------------- /source/polyplex/utils/strutils.d: -------------------------------------------------------------------------------- 1 | module polyplex.utils.strutils; 2 | import std.conv; 3 | import std.array; 4 | import std.stdio; 5 | import core.vararg; 6 | 7 | /** 8 | C# style text formatting. 9 | Add {(id)} in text to specify replacement points, specified arguments (after sequential id) will replace the text with a to!string variant. 10 | */ 11 | public static string Format(T...)(string format, T args) { 12 | string result = format; 13 | static foreach(i; 0 .. args.length) { 14 | result = result.replace("{" ~ i.text ~ "}", args[i].text); 15 | } 16 | return result; 17 | } 18 | 19 | /* 20 | public static string Format(string message, ...) { 21 | string[] formatstr; 22 | for (int i = 0; i < _arguments.length; i++) { 23 | formatstr ~= va_arg!string(_argptr); 24 | } 25 | return FormatStr(message, formatstr); 26 | } 27 | 28 | public static string FormatStr(string base, string[] format) { 29 | string o = base; 30 | for(int i = 0; i < format.length; i++) { 31 | o = replace(o, "{"~to!string(i)~"}", format[i]); 32 | } 33 | return o; 34 | }*/ 35 | --------------------------------------------------------------------------------