Compare commits
248 Commits
v0.3-beta5
...
ea133c6f8c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea133c6f8c | ||
|
|
d0fdaf30ab | ||
|
|
8062f6e3fa | ||
|
|
7c3cf0b1bd | ||
|
|
52ddbbc174 | ||
|
|
5f81c35677 | ||
|
|
635ef6270f | ||
|
|
649dd5f31d | ||
|
|
34fcc3f659 | ||
|
|
4caa268adc | ||
|
|
a79552a6f3 | ||
|
|
cd90405865 | ||
|
|
e69659b2ad | ||
|
|
d90e382037 | ||
|
|
ba6f5bb837 | ||
|
|
39e9dc3303 | ||
|
|
a1f0dfd9f2 | ||
|
|
2a21484137 | ||
|
|
50410958f0 | ||
|
|
6fb583e463 | ||
|
|
91c8dc3dcc | ||
|
|
2166a3c6d8 | ||
|
|
1556b247fe | ||
|
|
23d9feb357 | ||
|
|
c693871621 | ||
|
|
2ba3611c56 | ||
|
|
602e7105ab | ||
|
|
7199aa7ef6 | ||
|
|
a972c31d9a | ||
|
|
02f314997d | ||
|
|
4769daedf4 | ||
|
|
4478c07acf | ||
|
|
52d4aeb3d0 | ||
|
|
91279c9f38 | ||
|
|
0572803627 | ||
|
|
1fef7b4880 | ||
|
|
e09b044de4 | ||
|
|
7d6f783c40 | ||
|
|
9d365f61a7 | ||
|
|
082697c3cd | ||
|
|
788aa11470 | ||
|
|
a303e82b90 | ||
|
|
b06d03c4e6 | ||
|
|
a6b8abff6d | ||
|
|
bdc317c3c5 | ||
|
|
71c9147a2c | ||
|
|
79d706a415 | ||
|
|
5fa144f146 | ||
|
|
244e67074d | ||
|
|
4b456cf49c | ||
|
|
9a67a6ce03 | ||
|
|
df19129228 | ||
|
|
80de771d2a | ||
|
|
7c32273971 | ||
|
|
9d5dbb4674 | ||
|
|
8d7ccae2bc | ||
|
|
1e06a1ce8a | ||
|
|
d24fff5bdc | ||
|
|
e34005093c | ||
|
|
3dc8b1214b | ||
|
|
5d2da1b4fb | ||
|
|
f786bda9dd | ||
|
|
3f789210a6 | ||
|
|
a7e7ac43a6 | ||
|
|
5dc72037ec | ||
|
|
e5cb69df43 | ||
|
|
3e68af6a5b | ||
|
|
8e6a760fe7 | ||
|
|
a4b7a41a15 | ||
|
|
aa9e03506b | ||
|
|
40ac08c7e5 | ||
|
|
323c457809 | ||
|
|
decc1f563f | ||
|
|
dcde2a380d | ||
|
|
63823ed4b1 | ||
|
|
d7c83b0bc7 | ||
|
|
e5892c0fae | ||
|
|
23a8c400ba | ||
|
|
bfbba75f17 | ||
|
|
27e699841e | ||
|
|
fac8c6584e | ||
|
|
d868e8b803 | ||
|
|
9e447d51a7 | ||
|
|
4dfa234bc3 | ||
|
|
47863175a3 | ||
|
|
8730261a78 | ||
|
|
703ce66c42 | ||
|
|
92d67968f5 | ||
|
|
d68bd13d2a | ||
|
|
a84335646d | ||
|
|
d46973f12d | ||
|
|
d4360b3662 | ||
|
|
06225bd35a | ||
|
|
e68238cbce | ||
|
|
83e197b5d6 | ||
|
|
1c0b73987d | ||
|
|
afe6a43dab | ||
|
|
47a5a53e23 | ||
|
|
b9ae08051a | ||
|
|
d7f4aa2007 | ||
|
|
ca85107063 | ||
|
|
fdcec19d56 | ||
|
|
89c7205347 | ||
|
|
9b41e56135 | ||
| 710f658540 | |||
|
|
332e3869de | ||
|
|
febd1de0ef | ||
|
|
81ab7cd4de | ||
|
|
a5750e4959 | ||
|
|
59c7834c9a | ||
|
|
71ada76a00 | ||
|
|
6c4551ebef | ||
| 9fc7e4b1eb | |||
|
|
9e59c158b2 | ||
|
|
e464307625 | ||
|
|
888312c578 | ||
| a838294435 | |||
|
|
049806d9e2 | ||
|
|
c1693524d7 | ||
|
|
a063f10d33 | ||
|
|
6e0b5e27c1 | ||
|
|
18e0e02c76 | ||
|
|
9381091110 | ||
| deb69fe28d | |||
|
|
3085b765e5 | ||
|
|
412405c1a1 | ||
|
|
7495c4ad04 | ||
|
|
0fce4b632f | ||
|
|
aa56248e34 | ||
|
|
0a2e16ab2c | ||
|
|
34d53c82cb | ||
| 75ee07a04d | |||
|
|
d2d710ead6 | ||
|
|
6f4adf5aad | ||
|
|
42f872a557 | ||
|
|
a30791afc3 | ||
|
|
8bd8c0eede | ||
|
|
f52a5eaee5 | ||
| e68a9b6f07 | |||
|
|
ff5b04bb97 | ||
|
|
8dbb75cbef | ||
|
|
bc54bc57b6 | ||
|
|
9611fc31bf | ||
|
|
a5fed31f4e | ||
|
|
625d4f80af | ||
| 173b3ddbc9 | |||
|
|
0f96bf7db0 | ||
|
|
3770deca55 | ||
|
|
4e297a8030 | ||
|
|
1336ea00e4 | ||
|
|
b8d43e38b7 | ||
|
|
6b595d2146 | ||
|
|
1082a2903a | ||
|
|
3480734a44 | ||
|
|
82966e99c3 | ||
|
|
d558faeef0 | ||
|
|
8dc59a562e | ||
|
|
2fa9ba40fa | ||
|
|
a7ee1d7861 | ||
|
|
bf6c61927e | ||
|
|
817ffd5c13 | ||
|
|
50f6010ed1 | ||
|
|
ef966d8190 | ||
|
|
faef1ddc8f | ||
|
|
ea38ebb89d | ||
|
|
b28759e0c8 | ||
| 2fc763ae5d | |||
|
|
ffd808e6a0 | ||
|
|
dd96db170e | ||
|
|
7fa547c307 | ||
| b2d0838f90 | |||
|
|
cf8ba16eb1 | ||
|
|
42375cb2b8 | ||
|
|
fe162ed215 | ||
|
|
dda116f00f | ||
|
|
2d3aeeb47d | ||
|
|
784c768c57 | ||
|
|
c18e7ed244 | ||
|
|
9df6bb9989 | ||
|
|
f5873c97bc | ||
|
|
fabdad056e | ||
|
|
71ecd51cde | ||
|
|
0e82a8758c | ||
|
|
e78df19112 | ||
|
|
49775b9578 | ||
|
|
6a3c6ecac0 | ||
|
|
90cf2ebef5 | ||
|
|
799a905a9c | ||
|
|
985f73c39d | ||
|
|
b5db5bbdc3 | ||
|
|
438acde2e2 | ||
|
|
0e1f40ad30 | ||
|
|
6cf6568a57 | ||
|
|
dafc113038 | ||
|
|
923f3d3696 | ||
|
|
db4132bf31 | ||
|
|
c58018dd51 | ||
|
|
c7d0034f9b | ||
|
|
ed5ea72e66 | ||
|
|
dc3ad825dc | ||
|
|
40cba83003 | ||
| a1b3f73787 | |||
|
|
4243d6b2ba | ||
| 33b3ad2889 | |||
|
|
adab1df480 | ||
|
|
711fa830a3 | ||
|
|
c434a3406b | ||
|
|
769b5043e3 | ||
|
|
713c62d807 | ||
|
|
c3f6e34518 | ||
|
|
4d0f6ab9fc | ||
|
|
594aa2620f | ||
|
|
199b535f70 | ||
|
|
9fbfbd5cda | ||
|
|
c5c4c4d95c | ||
|
|
53c51c2062 | ||
|
|
e4eb9972e6 | ||
|
|
7dbfe23059 | ||
|
|
61d5410f22 | ||
|
|
2cb0416548 | ||
| 83f3e297ce | |||
|
|
8fb01dc9a8 | ||
|
|
61de3c6dbf | ||
|
|
3c718c38e4 | ||
|
|
d18c3e298d | ||
|
|
33934bfb53 | ||
|
|
3e2d107687 | ||
|
|
312b95728d | ||
|
|
5013443302 | ||
|
|
a8ac8f5966 | ||
|
|
a5032386e6 | ||
|
|
264255290d | ||
|
|
a5839bede2 | ||
|
|
4ebf24316a | ||
|
|
f2acab4496 | ||
| 929069c1b6 | |||
|
|
3f2b38f7b3 | ||
|
|
56fb5aebea | ||
|
|
6c201596b0 | ||
|
|
50466c5902 | ||
| ae1231c47a | |||
|
|
366ac1d552 | ||
| 302353f716 | |||
|
|
7f550b629f | ||
|
|
6b2252e6d9 | ||
|
|
e1741440f2 | ||
|
|
c56f290921 | ||
|
|
86e975f929 |
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
*.sav
|
*.sav
|
||||||
*.love
|
*.love
|
||||||
|
*.zip
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
dist/**/cambridge.exe
|
dist/**/cambridge.exe
|
||||||
dist/**/libs
|
dist/**/libs
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ Coding conventions
|
|||||||
Use tabs to indent, spaces to align.
|
Use tabs to indent, spaces to align.
|
||||||
|
|
||||||
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
|
* Specifically, spaces should not appear at the beginning of a line, and tabs should not appear _except_ at the beginning of a line.
|
||||||
* The sole exception is in a multiline `if` statement; the initial `if` should have four spaces before it to align it with an `elseif` on the next line. For example:
|
* If you're aligning multiline if-statements, the initial "if", "elseif" or "else" should be flush left with the indentation level, with spaces padding the gap to the next word as necessary. For example:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
---- 4 spaces
|
if self.level < 900 then return 12
|
||||||
if self.level < 900 then return 12
|
elseif self.level < 1200 then return 8
|
||||||
elseif self.level < 1200 then return 8
|
else return 6
|
||||||
else return 6 end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.
|
Comments at the end of lines of code must be one line long. Multi-line comments must appear in their own block.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2018-2019 Joe Zeng
|
Copyright (c) 2018-2021 Joe Zeng, Ishaan Bhardwaj
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
58
README.md
@@ -5,24 +5,12 @@ Cambridge
|
|||||||
|
|
||||||
Welcome to Cambridge, the next open-source falling-block game engine!
|
Welcome to Cambridge, the next open-source falling-block game engine!
|
||||||
|
|
||||||
The project is written and maintained exclusively by [SashLilac](https://github.com/SashLilac), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
|
The project is written and maintained exclusively by [Milla](https://github.com/MillaBasset), [joezeng](https://github.com/joezeng) and [Oshisaure](https://github.com/oshisaure)!
|
||||||
|
|
||||||
The Discord server has been reopened! https://discord.gg/AADZUmgsph
|
The Discord server has been reopened! https://discord.gg/AADZUmgsph
|
||||||
|
|
||||||
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
|
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
|
||||||
|
|
||||||
Credits
|
|
||||||
-------
|
|
||||||
|
|
||||||
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for being my co-dev!
|
|
||||||
- [joezeng](https://github.com/joezeng) for the original project, and for offering to help with the expansion!
|
|
||||||
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
|
|
||||||
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
|
|
||||||
|
|
||||||
More special thanks can be found in-game, under the "Credits" menu.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Playing the game
|
Playing the game
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@@ -32,19 +20,19 @@ You do not need LÖVE on Windows, as it comes bundled with the program.
|
|||||||
|
|
||||||
#### Stable release
|
#### Stable release
|
||||||
|
|
||||||
To get the stable release, simply download either `cambridge-win32.zip` (32-bit) or `cambridge-windows.zip` (64-bit) in the [latest release](https://github.com/sashlilac/cambridge/releases/latest).
|
To get the stable release, simply download either `cambridge-win32.zip` (32-bit) or `cambridge-windows.zip` (64-bit) in the [latest release](https://github.com/MillaBasset/cambridge/releases/latest).
|
||||||
|
|
||||||
All assets needed are bundled with the executable.
|
All assets needed are bundled with the executable.
|
||||||
|
|
||||||
#### Bleeding edge
|
#### Bleeding edge
|
||||||
|
|
||||||
If you want the bleeding edge version, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
|
If you want the bleeding edge version, download [this](https://github.com/MillaBasset/cambridge/archive/master.zip). Extract the ZIP to a folder of your choosing.
|
||||||
|
|
||||||
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
|
If you're on Windows, you can double-click `start.bat` to run the game. If that doesn't work, open a Command Prompt where you extracted Cambridge and run:
|
||||||
|
|
||||||
dist\windows\love.exe .
|
dist\windows\love.exe .
|
||||||
|
|
||||||
Alternatively, if you're on a 32-bit system, run this instead:
|
If that doesn't work, run this instead, still using Command Prompt where you extracted Cambridge:
|
||||||
|
|
||||||
dist\win32\love.exe .
|
dist\win32\love.exe .
|
||||||
|
|
||||||
@@ -54,7 +42,7 @@ Then, check the mod pack section at the bottom of this page.
|
|||||||
|
|
||||||
### macOS, Linux
|
### macOS, Linux
|
||||||
|
|
||||||
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11, because it won't work with earlier versions!**
|
If you haven't already, install `love` with your favourite package manager (Homebrew on macOS, your system's default on Linux). **Make sure you're using LÖVE 11.3, because it won't work with earlier or later versions!**
|
||||||
|
|
||||||
#### Downloading a release
|
#### Downloading a release
|
||||||
|
|
||||||
@@ -66,7 +54,7 @@ You can download the .love file in the latest release, and run it with:
|
|||||||
|
|
||||||
Clone the repository in git:
|
Clone the repository in git:
|
||||||
|
|
||||||
git clone https://github.com/SashLilac/cambridge
|
git clone https://github.com/MillaBasset/cambridge
|
||||||
|
|
||||||
Alternatively, download the source code ZIP in the latest release.
|
Alternatively, download the source code ZIP in the latest release.
|
||||||
|
|
||||||
@@ -78,7 +66,7 @@ It should run automatically!
|
|||||||
|
|
||||||
## Installing modpacks
|
## Installing modpacks
|
||||||
|
|
||||||
For instructions on how to install modpacks, go to [this](https://github.com/SashLilac/cambridge-modpack) mod pack to get a taste of the mod potential.
|
For instructions on how to install modpacks, go to [this](https://github.com/MillaBasset/cambridge-modpack) mod pack to get a taste of the mod potential.
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
@@ -90,3 +78,33 @@ community, as well as borrowed from other places, either with licensing
|
|||||||
or as placeholders until suitable material can be found that is properly
|
or as placeholders until suitable material can be found that is properly
|
||||||
licensed. Their original sources, and copyright notices if applicable, are
|
licensed. Their original sources, and copyright notices if applicable, are
|
||||||
listed in the file SOURCES.
|
listed in the file SOURCES.
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
- [Lilla Oshisaure](https://www.youtube.com/user/LeSpyroshisaure) for being my co-dev!
|
||||||
|
- [joezeng](https://github.com/joezeng) for the original project, and for offering to help with the expansion!
|
||||||
|
- [The Tetra Legends Discord](http://discord.com/invite/7hMx5r2) for supporting me and playtesting!
|
||||||
|
- [The Absolute Plus](https://discord.gg/6Gf2awJ) for being another source of motivation!
|
||||||
|
|
||||||
|
More special thanks can be found in-game, under the "Credits" menu.
|
||||||
|
|
||||||
|
Other Notable Games
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- [TGMsim](https://github.com/2Tie/TGMsim) by 2Tie
|
||||||
|
- [Multimino](https://gamejolt.com/games/multimino/556683) by Axel Fox
|
||||||
|
- [Tetra Legends](https://tetralegends.app) by Dr Ocelot
|
||||||
|
- [ZTrix](https://discord.gg/MGhqCBDGNH) by Electra
|
||||||
|
- [Shiromino](https://github.com/shiromino/shiromino) by Felicity/nightmareci/kdex
|
||||||
|
- [Cursed Blocks](https://github.com/Manabender/Cursed-Blocks) by Manabender
|
||||||
|
- [Picoris 2](https://www.lexaloffle.com/bbs/?tid=41733) by MarkGamed
|
||||||
|
- [Tetra Online](https://github.com/Juan-Cartes/Tetra-Offline) by Mine
|
||||||
|
- [Techmino](https://discord.gg/6Yuww44tq8) by MrZ
|
||||||
|
- [Example Block Game](https://github.com/oshisaure/example-block-game) by Oshisaure
|
||||||
|
- [Master of Blocks](https://discord.gg/72FZ49mjWh) by Phoenix Flare
|
||||||
|
- [Spirit Drop](https://rayblastgames.com/spiritdrop.php) by RayRay26
|
||||||
|
- [Puzzle Trial](https://kagamine-rin.itch.io/puzzle-trial) by Rin
|
||||||
|
- [stackfuse](https://github.com/sinefuse/stackfuse) by sinefuse
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
48
SOURCES.md
@@ -170,3 +170,51 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
discord-rpc (https://github.com/discord/discord-rpc)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Copyright 2017 Discord, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
lua-discordRPC (https://github.com/pfirsich/lua-discordRPC)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Joel Schumacher
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -7,5 +7,11 @@
|
|||||||
@del dist\win32\SOURCES.md
|
@del dist\win32\SOURCES.md
|
||||||
@del dist\win32\LICENSE.md
|
@del dist\win32\LICENSE.md
|
||||||
@rmdir /Q /S dist\win32\libs
|
@rmdir /Q /S dist\win32\libs
|
||||||
|
@del dist\other\cambridge.love
|
||||||
|
@del dist\other\SOURCES.md
|
||||||
|
@del dist\other\LICENSE.md
|
||||||
|
@rmdir /Q /S dist\other\libs
|
||||||
|
@rmdir /Q /S dist\other
|
||||||
@del dist\cambridge-windows.zip
|
@del dist\cambridge-windows.zip
|
||||||
@del dist\cambridge-win32.zip
|
@del dist\cambridge-win32.zip
|
||||||
|
@del dist\cambridge-other.zip
|
||||||
62
funcs.lua
@@ -1,10 +1,20 @@
|
|||||||
function copy(t)
|
function copy(t)
|
||||||
-- returns deep copy of t (as opposed to the shallow copy you get from var = t)
|
-- returns top-layer shallow copy of t
|
||||||
if type(t) ~= "table" then return t end
|
if type(t) ~= "table" then return t end
|
||||||
local meta = getmetatable(t)
|
|
||||||
local target = {}
|
local target = {}
|
||||||
for k, v in pairs(t) do target[k] = v end
|
for k, v in next, t do target[k] = v end
|
||||||
setmetatable(target, meta)
|
setmetatable(target, getmetatable(t))
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
function deepcopy(t)
|
||||||
|
-- returns infinite-layer deep copy of t
|
||||||
|
if type(t) ~= "table" then return t end
|
||||||
|
local target = {}
|
||||||
|
for k, v in next, t do
|
||||||
|
target[deepcopy(k)] = deepcopy(v)
|
||||||
|
end
|
||||||
|
setmetatable(target, deepcopy(getmetatable(t)))
|
||||||
return target
|
return target
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -50,6 +60,9 @@ function formatTime(frames)
|
|||||||
min = math.floor(frames/3600)
|
min = math.floor(frames/3600)
|
||||||
sec = math.floor(frames/60) % 60
|
sec = math.floor(frames/60) % 60
|
||||||
hund = math.floor(frames/.6) % 100
|
hund = math.floor(frames/.6) % 100
|
||||||
|
if frames == 15641 then
|
||||||
|
hund = math.ceil(frames/.6) % 100
|
||||||
|
end
|
||||||
str = string.format("%02d:%02d.%02d", min, sec, hund)
|
str = string.format("%02d:%02d.%02d", min, sec, hund)
|
||||||
return str
|
return str
|
||||||
end
|
end
|
||||||
@@ -87,6 +100,47 @@ function table.contains(table, element)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function table.keys(table)
|
||||||
|
local target = {}
|
||||||
|
for key in pairs(table) do
|
||||||
|
target[#target+1] = key
|
||||||
|
end
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.numkeys(table)
|
||||||
|
local count = 0
|
||||||
|
for k in pairs(table) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
function equals(x, y)
|
||||||
|
if type(x) ~= "table" or type(y) ~= "table" then
|
||||||
|
return x == y
|
||||||
|
else
|
||||||
|
for k in pairs(x) do
|
||||||
|
if not equals(x[k], y[k]) then return false end
|
||||||
|
end
|
||||||
|
for k in pairs(y) do
|
||||||
|
if not equals(x[k], y[k]) then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.equalvalues(t1, t2)
|
||||||
|
if table.numkeys(t1) ~= table.numkeys(t2) then
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
for _, v in pairs(t2) do
|
||||||
|
if not table.contains(t1, v) then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function clamp(x, min, max)
|
function clamp(x, min, max)
|
||||||
if max < min then
|
if max < min then
|
||||||
min, max = max, min
|
min, max = max, min
|
||||||
|
|||||||
@@ -2,18 +2,19 @@
|
|||||||
-- If this variable is true, then strict type checking is performed for all
|
-- If this variable is true, then strict type checking is performed for all
|
||||||
-- operations. This may result in slower code, but it will allow you to catch
|
-- operations. This may result in slower code, but it will allow you to catch
|
||||||
-- errors and bugs earlier.
|
-- errors and bugs earlier.
|
||||||
local strict = false
|
local strict = true
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
local bigint = {}
|
local bigint = {}
|
||||||
|
setmetatable(bigint, {__call = function(_, arg) return bigint.new(arg) end})
|
||||||
|
|
||||||
local mt = {
|
local mt = {
|
||||||
__add = function(lhs, rhs)
|
__add = function(lhs, rhs)
|
||||||
return bigint.add(lhs, rhs)
|
return bigint.add(lhs, rhs)
|
||||||
end,
|
end,
|
||||||
__unm = function()
|
__unm = function(arg)
|
||||||
return bigint.negate(self)
|
return bigint.negate(arg)
|
||||||
end,
|
end,
|
||||||
__sub = function(lhs, rhs)
|
__sub = function(lhs, rhs)
|
||||||
return bigint.subtract(lhs, rhs)
|
return bigint.subtract(lhs, rhs)
|
||||||
@@ -30,8 +31,8 @@ local mt = {
|
|||||||
__pow = function(lhs, rhs)
|
__pow = function(lhs, rhs)
|
||||||
return bigint.exponentiate(lhs, rhs)
|
return bigint.exponentiate(lhs, rhs)
|
||||||
end,
|
end,
|
||||||
__tostring = function()
|
__tostring = function(arg)
|
||||||
return bigint.unserialize(self, "s")
|
return bigint.unserialize(arg, "s")
|
||||||
end,
|
end,
|
||||||
__eq = function(lhs, rhs)
|
__eq = function(lhs, rhs)
|
||||||
return bigint.compare(lhs, rhs, "==")
|
return bigint.compare(lhs, rhs, "==")
|
||||||
@@ -76,7 +77,7 @@ function bigint.new(num)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return bigint.strip(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check the type of a big
|
-- Check the type of a big
|
||||||
@@ -96,6 +97,14 @@ function bigint.check(big, force)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Strip leading zeroes from a big, but don't remove the last zero
|
||||||
|
function bigint.strip(big)
|
||||||
|
while (#big.digits > 1) and (big.digits[1] == 0) do
|
||||||
|
table.remove(big.digits, 1)
|
||||||
|
end
|
||||||
|
return big
|
||||||
|
end
|
||||||
|
|
||||||
-- Return a new big with the same digits but with a positive sign (absolute
|
-- Return a new big with the same digits but with a positive sign (absolute
|
||||||
-- value)
|
-- value)
|
||||||
function bigint.abs(big)
|
function bigint.abs(big)
|
||||||
@@ -329,12 +338,7 @@ function bigint.subtract_raw(big1, big2)
|
|||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
-- Strip leading zeroes if any, but not if 0 is the only digit
|
return bigint.strip(result)
|
||||||
while (#result.digits > 1) and (result.digits[1] == 0) do
|
|
||||||
table.remove(result.digits, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- FRONTEND: Addition and subtraction operations, accounting for signs
|
-- FRONTEND: Addition and subtraction operations, accounting for signs
|
||||||
@@ -364,6 +368,7 @@ function bigint.add(big1, big2)
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function bigint.subtract(big1, big2)
|
function bigint.subtract(big1, big2)
|
||||||
-- Type checking is done by bigint.compare in bigint.add
|
-- Type checking is done by bigint.compare in bigint.add
|
||||||
-- Subtracting is like adding a negative
|
-- Subtracting is like adding a negative
|
||||||
@@ -460,7 +465,7 @@ end
|
|||||||
function bigint.exponentiate(big, power)
|
function bigint.exponentiate(big, power)
|
||||||
-- Type checking for big done by bigint.multiply
|
-- Type checking for big done by bigint.multiply
|
||||||
assert(bigint.compare(power, bigint.new(0), ">="),
|
assert(bigint.compare(power, bigint.new(0), ">="),
|
||||||
" negative powers are not supported")
|
"negative powers are not supported")
|
||||||
local exp = power:clone()
|
local exp = power:clone()
|
||||||
|
|
||||||
if (bigint.compare(exp, bigint.new(0), "==")) then
|
if (bigint.compare(exp, bigint.new(0), "==")) then
|
||||||
@@ -530,12 +535,7 @@ function bigint.divide_raw(big1, big2)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Remove leading zeros from result
|
return bigint.strip(result), dividend
|
||||||
while (result.digits[1] == 0) do
|
|
||||||
table.remove(result.digits, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
return result, dividend
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
60
libs/cpml/LICENSE.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Licenses
|
||||||
|
|
||||||
|
CPML is Copyright (c) 2016 Colby Klein <shakesoda@gmail.com>.
|
||||||
|
|
||||||
|
CPML is Copyright (c) 2016 Landon Manning <lmanning17@gmail.com>.
|
||||||
|
|
||||||
|
Code in vec3.lua is derived from hump.vector. (c) 2010-2013 Matthias Richter. MIT.
|
||||||
|
|
||||||
|
Portions of mat4.lua are from LuaMatrix, (c) 2010 Michael Lutz. MIT.
|
||||||
|
|
||||||
|
Code in simplex.lua is (c) 2011 Stefan Gustavson. MIT.
|
||||||
|
|
||||||
|
Code in bound2.lua and bound3.lua are (c) 2018 Andi McClure. MIT.
|
||||||
|
|
||||||
|
Code in quat.lua is from Andrew Stacey and covered under the CC0 license.
|
||||||
|
|
||||||
|
Code in octree.lua is derived from UnityOctree. (c) 2014 Nition. BSD-2-Clause.
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
# The BSD License (BSD-2-Clause)
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
17
libs/cpml/_private_precond.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
-- Preconditions for cpml functions.
|
||||||
|
local precond = {}
|
||||||
|
|
||||||
|
|
||||||
|
function precond.typeof(t, expected, msg)
|
||||||
|
if type(t) ~= expected then
|
||||||
|
error(("%s: %s (<%s> expected)"):format(msg, type(t), expected), 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function precond.assert(cond, msg, ...)
|
||||||
|
if not cond then
|
||||||
|
error(msg:format(...), 3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return precond
|
||||||
16
libs/cpml/_private_utils.lua
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
-- Functions exported by utils.lua but needed by vec2 or vec3 (which utils.lua requires)
|
||||||
|
|
||||||
|
local private = {}
|
||||||
|
local floor = math.floor
|
||||||
|
local ceil = math.ceil
|
||||||
|
|
||||||
|
function private.round(value, precision)
|
||||||
|
if precision then return private.round(value / precision) * precision end
|
||||||
|
return value >= 0 and floor(value+0.5) or ceil(value-0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
function private.is_nan(a)
|
||||||
|
return a ~= a
|
||||||
|
end
|
||||||
|
|
||||||
|
return private
|
||||||
199
libs/cpml/bound2.lua
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
--- A 2 component bounding box.
|
||||||
|
-- @module bound2
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local vec2 = require(modules .. "vec2")
|
||||||
|
|
||||||
|
local bound2 = {}
|
||||||
|
local bound2_mt = {}
|
||||||
|
|
||||||
|
-- Private constructor.
|
||||||
|
local function new(min, max)
|
||||||
|
return setmetatable({
|
||||||
|
min=min, -- min: vec2, minimum value for each component
|
||||||
|
max=max, -- max: vec2, maximum value for each component
|
||||||
|
}, bound2_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||||
|
local status, ffi
|
||||||
|
if type(jit) == "table" and jit.status() then
|
||||||
|
status, ffi = pcall(require, "ffi")
|
||||||
|
if status then
|
||||||
|
ffi.cdef "typedef struct { cpml_vec2 min, max; } cpml_bound2;"
|
||||||
|
new = ffi.typeof("cpml_bound2")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
bound2.zero = new(vec2.zero, vec2.zero)
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param min Can be of two types: </br>
|
||||||
|
-- vec2 min, minimum value for each component
|
||||||
|
-- nil Create bound at single point 0,0
|
||||||
|
-- @tparam vec2 max, maximum value for each component
|
||||||
|
-- @treturn bound2 out
|
||||||
|
function bound2.new(min, max)
|
||||||
|
if min and max then
|
||||||
|
return new(min:clone(), max:clone())
|
||||||
|
elseif min or max then
|
||||||
|
error("Unexpected nil argument to bound2.new")
|
||||||
|
else
|
||||||
|
return new(vec2.zero, vec2.zero)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone a bound.
|
||||||
|
-- @tparam bound2 a bound to be cloned
|
||||||
|
-- @treturn bound2 out
|
||||||
|
function bound2.clone(a)
|
||||||
|
return new(a.min, a.max)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Construct a bound covering one or two points
|
||||||
|
-- @tparam vec2 a Any vector
|
||||||
|
-- @tparam vec2 b Any second vector (optional)
|
||||||
|
-- @treturn vec2 Minimum bound containing the given points
|
||||||
|
function bound2.at(a, b) -- "bounded by". b may be nil
|
||||||
|
if b then
|
||||||
|
return bound2.new(a,b):check()
|
||||||
|
else
|
||||||
|
return bound2.zero:with_center(a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Extend bound to include point
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 point to include
|
||||||
|
-- @treturn bound2 Bound covering current min, current max and new point
|
||||||
|
function bound2.extend(a, center)
|
||||||
|
return bound2.new(a.min:component_min(center), a.max:component_max(center))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Extend bound to entirety of other bound
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam bound2 bound to cover
|
||||||
|
-- @treturn bound2 Bound covering current min and max of each bound in the pair
|
||||||
|
function bound2.extend_bound(a, b)
|
||||||
|
return a:extend(b.min):extend(b.max)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get size of bounding box as a vector
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @treturn vec2 Vector spanning min to max points
|
||||||
|
function bound2.size(a)
|
||||||
|
return a.max - a.min
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Resize bounding box from minimum corner
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 new size
|
||||||
|
-- @treturn bound2 resized bound
|
||||||
|
function bound2.with_size(a, size)
|
||||||
|
return bound2.new(a.min, a.min + size)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @treturn vec2 Vector spanning center to max point
|
||||||
|
function bound2.radius(a)
|
||||||
|
return a:size()/2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get center of bounding box
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @treturn bound2 Point in center of bound
|
||||||
|
function bound2.center(a)
|
||||||
|
return (a.min + a.max)/2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move bounding box to new center
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 new center
|
||||||
|
-- @treturn bound2 Bound with same size as input but different center
|
||||||
|
function bound2.with_center(a, center)
|
||||||
|
return bound2.offset(a, center - a:center())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Resize bounding box from center
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 new size
|
||||||
|
-- @treturn bound2 resized bound
|
||||||
|
function bound2.with_size_centered(a, size)
|
||||||
|
local center = a:center()
|
||||||
|
local rad = size/2
|
||||||
|
return bound2.new(center - rad, center + rad)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert possibly-invalid bounding box to valid one
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @treturn bound2 bound with all components corrected for min-max property
|
||||||
|
function bound2.check(a)
|
||||||
|
if a.min.x > a.max.x or a.min.y > a.max.y then
|
||||||
|
return bound2.new(vec2.component_min(a.min, a.max), vec2.component_max(a.min, a.max))
|
||||||
|
end
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Shrink bounding box with fixed margin
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 a margin
|
||||||
|
-- @treturn bound2 bound with margin subtracted from all edges. May not be valid, consider calling check()
|
||||||
|
function bound2.inset(a, v)
|
||||||
|
return bound2.new(a.min + v, a.max - v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Expand bounding box with fixed margin
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 a margin
|
||||||
|
-- @treturn bound2 bound with margin added to all edges. May not be valid, consider calling check()
|
||||||
|
function bound2.outset(a, v)
|
||||||
|
return bound2.new(a.min - v, a.max + v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Offset bounding box
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 offset
|
||||||
|
-- @treturn bound2 bound with same size, but position moved by offset
|
||||||
|
function bound2.offset(a, v)
|
||||||
|
return bound2.new(a.min + v, a.max + v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test if point in bound
|
||||||
|
-- @tparam bound2 a bound
|
||||||
|
-- @tparam vec2 point to test
|
||||||
|
-- @treturn boolean true if point in bounding box
|
||||||
|
function bound2.contains(a, v)
|
||||||
|
return a.min.x <= v.x and a.min.y <= v.y
|
||||||
|
and a.max.x >= v.x and a.max.y >= v.y
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Round all components of all vectors to nearest int (or other precision).
|
||||||
|
-- @tparam vec3 a bound to round.
|
||||||
|
-- @tparam precision Digits after the decimal (round number if unspecified)
|
||||||
|
-- @treturn vec3 Rounded bound
|
||||||
|
function bound2.round(a, precision)
|
||||||
|
return bound2.new(a.min:round(precision), a.max:round(precision))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam bound2 a bound to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function bound2.to_string(a)
|
||||||
|
return string.format("(%s-%s)", a.min, a.max)
|
||||||
|
end
|
||||||
|
|
||||||
|
bound2_mt.__index = bound2
|
||||||
|
bound2_mt.__tostring = bound2.to_string
|
||||||
|
|
||||||
|
function bound2_mt.__call(_, a, b)
|
||||||
|
return bound2.new(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||||
|
ffi.metatype(new, bound2_mt)
|
||||||
|
end, function() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, bound2_mt)
|
||||||
199
libs/cpml/bound3.lua
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
--- A 3-component axis-aligned bounding box.
|
||||||
|
-- @module bound3
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
|
||||||
|
local bound3 = {}
|
||||||
|
local bound3_mt = {}
|
||||||
|
|
||||||
|
-- Private constructor.
|
||||||
|
local function new(min, max)
|
||||||
|
return setmetatable({
|
||||||
|
min=min, -- min: vec3, minimum value for each component
|
||||||
|
max=max -- max: vec3, maximum value for each component
|
||||||
|
}, bound3_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||||
|
local status, ffi
|
||||||
|
if type(jit) == "table" and jit.status() then
|
||||||
|
status, ffi = pcall(require, "ffi")
|
||||||
|
if status then
|
||||||
|
ffi.cdef "typedef struct { cpml_vec3 min, max; } cpml_bound3;"
|
||||||
|
new = ffi.typeof("cpml_bound3")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
bound3.zero = new(vec3.zero, vec3.zero)
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param min Can be of two types: </br>
|
||||||
|
-- vec3 min, minimum value for each component
|
||||||
|
-- nil Create bound at single point 0,0,0
|
||||||
|
-- @tparam vec3 max, maximum value for each component
|
||||||
|
-- @treturn bound3 out
|
||||||
|
function bound3.new(min, max)
|
||||||
|
if min and max then
|
||||||
|
return new(min:clone(), max:clone())
|
||||||
|
elseif min or max then
|
||||||
|
error("Unexpected nil argument to bound3.new")
|
||||||
|
else
|
||||||
|
return new(vec3.zero, vec3.zero)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone a bound.
|
||||||
|
-- @tparam bound3 a bound to be cloned
|
||||||
|
-- @treturn bound3 out
|
||||||
|
function bound3.clone(a)
|
||||||
|
return new(a.min, a.max)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Construct a bound covering one or two points
|
||||||
|
-- @tparam vec3 a Any vector
|
||||||
|
-- @tparam vec3 b Any second vector (optional)
|
||||||
|
-- @treturn vec3 Minimum bound containing the given points
|
||||||
|
function bound3.at(a, b) -- "bounded by". b may be nil
|
||||||
|
if b then
|
||||||
|
return bound3.new(a,b):check()
|
||||||
|
else
|
||||||
|
return bound3.zero:with_center(a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Extend bound to include point
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 point to include
|
||||||
|
-- @treturn bound3 Bound covering current min, current max and new point
|
||||||
|
function bound3.extend(a, center)
|
||||||
|
return bound3.new(a.min:component_min(center), a.max:component_max(center))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Extend bound to entirety of other bound
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam bound3 bound to cover
|
||||||
|
-- @treturn bound3 Bound covering current min and max of each bound in the pair
|
||||||
|
function bound3.extend_bound(a, b)
|
||||||
|
return a:extend(b.min):extend(b.max)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get size of bounding box as a vector
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @treturn vec3 Vector spanning min to max points
|
||||||
|
function bound3.size(a)
|
||||||
|
return a.max - a.min
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Resize bounding box from minimum corner
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 new size
|
||||||
|
-- @treturn bound3 resized bound
|
||||||
|
function bound3.with_size(a, size)
|
||||||
|
return bound3.new(a.min, a.min + size)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @treturn vec3 Vector spanning center to max point
|
||||||
|
function bound3.radius(a)
|
||||||
|
return a:size()/2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get center of bounding box
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @treturn bound3 Point in center of bound
|
||||||
|
function bound3.center(a)
|
||||||
|
return (a.min + a.max)/2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move bounding box to new center
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 new center
|
||||||
|
-- @treturn bound3 Bound with same size as input but different center
|
||||||
|
function bound3.with_center(a, center)
|
||||||
|
return bound3.offset(a, center - a:center())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Resize bounding box from center
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 new size
|
||||||
|
-- @treturn bound3 resized bound
|
||||||
|
function bound3.with_size_centered(a, size)
|
||||||
|
local center = a:center()
|
||||||
|
local rad = size/2
|
||||||
|
return bound3.new(center - rad, center + rad)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert possibly-invalid bounding box to valid one
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @treturn bound3 bound with all components corrected for min-max property
|
||||||
|
function bound3.check(a)
|
||||||
|
if a.min.x > a.max.x or a.min.y > a.max.y or a.min.z > a.max.z then
|
||||||
|
return bound3.new(vec3.component_min(a.min, a.max), vec3.component_max(a.min, a.max))
|
||||||
|
end
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Shrink bounding box with fixed margin
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 a margin
|
||||||
|
-- @treturn bound3 bound with margin subtracted from all edges. May not be valid, consider calling check()
|
||||||
|
function bound3.inset(a, v)
|
||||||
|
return bound3.new(a.min + v, a.max - v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Expand bounding box with fixed margin
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 a margin
|
||||||
|
-- @treturn bound3 bound with margin added to all edges. May not be valid, consider calling check()
|
||||||
|
function bound3.outset(a, v)
|
||||||
|
return bound3.new(a.min - v, a.max + v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Offset bounding box
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 offset
|
||||||
|
-- @treturn bound3 bound with same size, but position moved by offset
|
||||||
|
function bound3.offset(a, v)
|
||||||
|
return bound3.new(a.min + v, a.max + v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test if point in bound
|
||||||
|
-- @tparam bound3 a bound
|
||||||
|
-- @tparam vec3 point to test
|
||||||
|
-- @treturn boolean true if point in bounding box
|
||||||
|
function bound3.contains(a, v)
|
||||||
|
return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
|
||||||
|
and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Round all components of all vectors to nearest int (or other precision).
|
||||||
|
-- @tparam vec3 a bound to round.
|
||||||
|
-- @tparam precision Digits after the decimal (round number if unspecified)
|
||||||
|
-- @treturn vec3 Rounded bound
|
||||||
|
function bound3.round(a, precision)
|
||||||
|
return bound3.new(a.min:round(precision), a.max:round(precision))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam bound3 a bound to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function bound3.to_string(a)
|
||||||
|
return string.format("(%s-%s)", a.min, a.max)
|
||||||
|
end
|
||||||
|
|
||||||
|
bound3_mt.__index = bound3
|
||||||
|
bound3_mt.__tostring = bound3.to_string
|
||||||
|
|
||||||
|
function bound3_mt.__call(_, a, b)
|
||||||
|
return bound3.new(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||||
|
ffi.metatype(new, bound3_mt)
|
||||||
|
end, function() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, bound3_mt)
|
||||||
557
libs/cpml/bvh.lua
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
-- https://github.com/benraziel/bvh-tree
|
||||||
|
|
||||||
|
--- BVH Tree
|
||||||
|
-- @module bvh
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local intersect = require(modules .. "intersect")
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local EPSILON = 1e-6
|
||||||
|
local BVH = {}
|
||||||
|
local BVHNode = {}
|
||||||
|
local Node
|
||||||
|
|
||||||
|
BVH.__index = BVH
|
||||||
|
BVHNode.__index = BVHNode
|
||||||
|
|
||||||
|
local function new(triangles, maxTrianglesPerNode)
|
||||||
|
local tree = setmetatable({}, BVH)
|
||||||
|
local trianglesArray = {}
|
||||||
|
|
||||||
|
for _, triangle in ipairs(triangles) do
|
||||||
|
local p1 = triangle[1]
|
||||||
|
local p2 = triangle[2]
|
||||||
|
local p3 = triangle[3]
|
||||||
|
|
||||||
|
table.insert(trianglesArray, p1.x or p1[1])
|
||||||
|
table.insert(trianglesArray, p1.y or p1[2])
|
||||||
|
table.insert(trianglesArray, p1.z or p1[3])
|
||||||
|
|
||||||
|
table.insert(trianglesArray, p2.x or p2[1])
|
||||||
|
table.insert(trianglesArray, p2.y or p2[2])
|
||||||
|
table.insert(trianglesArray, p2.z or p2[3])
|
||||||
|
|
||||||
|
table.insert(trianglesArray, p3.x or p3[1])
|
||||||
|
table.insert(trianglesArray, p3.y or p3[2])
|
||||||
|
table.insert(trianglesArray, p3.z or p3[3])
|
||||||
|
end
|
||||||
|
|
||||||
|
tree._trianglesArray = trianglesArray
|
||||||
|
tree._maxTrianglesPerNode = maxTrianglesPerNode or 10
|
||||||
|
tree._bboxArray = tree.calcBoundingBoxes(trianglesArray)
|
||||||
|
|
||||||
|
-- clone a helper array
|
||||||
|
tree._bboxHelper = {}
|
||||||
|
for _, bbox in ipairs(tree._bboxArray) do
|
||||||
|
table.insert(tree._bboxHelper, bbox)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create the root node, add all the triangles to it
|
||||||
|
local triangleCount = #triangles
|
||||||
|
local extents = tree:calcExtents(1, triangleCount, EPSILON)
|
||||||
|
tree._rootNode = Node(extents[1], extents[2], 1, triangleCount, 1)
|
||||||
|
|
||||||
|
tree._nodes_to_split = { tree._rootNode }
|
||||||
|
while #tree._nodes_to_split > 0 do
|
||||||
|
local node = table.remove(tree._nodes_to_split)
|
||||||
|
tree:splitNode(node)
|
||||||
|
end
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH:intersectAABB(aabb)
|
||||||
|
local nodesToIntersect = { self._rootNode }
|
||||||
|
local trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)
|
||||||
|
local intersectingTriangles = {}
|
||||||
|
|
||||||
|
-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the box.
|
||||||
|
-- note: these triangles may not intersect the box themselves
|
||||||
|
while #nodesToIntersect > 0 do
|
||||||
|
local node = table.remove(nodesToIntersect)
|
||||||
|
|
||||||
|
local node_aabb = {
|
||||||
|
min = node._extentsMin,
|
||||||
|
max = node._extentsMax
|
||||||
|
}
|
||||||
|
|
||||||
|
if intersect.aabb_aabb(aabb, node_aabb) then
|
||||||
|
if node._node0 then
|
||||||
|
table.insert(nodesToIntersect, node._node0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if node._node1 then
|
||||||
|
table.insert(nodesToIntersect, node._node1)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=node._startIndex, node._endIndex do
|
||||||
|
table.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- insert all node triangles, don't bother being more specific yet.
|
||||||
|
local triangle = { vec3(), vec3(), vec3() }
|
||||||
|
|
||||||
|
for i=1, #trianglesInIntersectingNodes do
|
||||||
|
local triIndex = trianglesInIntersectingNodes[i]
|
||||||
|
|
||||||
|
-- print(triIndex, #self._trianglesArray)
|
||||||
|
triangle[1].x = self._trianglesArray[1+(triIndex-1)*9]
|
||||||
|
triangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]
|
||||||
|
triangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]
|
||||||
|
triangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]
|
||||||
|
triangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]
|
||||||
|
triangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]
|
||||||
|
triangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]
|
||||||
|
triangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]
|
||||||
|
triangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]
|
||||||
|
|
||||||
|
table.insert(intersectingTriangles, {
|
||||||
|
triangle = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },
|
||||||
|
triangleIndex = triIndex
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return intersectingTriangles
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH:intersectRay(rayOrigin, rayDirection, backfaceCulling)
|
||||||
|
local nodesToIntersect = { self._rootNode }
|
||||||
|
local trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)
|
||||||
|
local intersectingTriangles = {}
|
||||||
|
|
||||||
|
local invRayDirection = vec3(
|
||||||
|
1 / rayDirection.x,
|
||||||
|
1 / rayDirection.y,
|
||||||
|
1 / rayDirection.z
|
||||||
|
)
|
||||||
|
|
||||||
|
-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the ray.
|
||||||
|
-- note: these triangles may not intersect the ray themselves
|
||||||
|
while #nodesToIntersect > 0 do
|
||||||
|
local node = table.remove(nodesToIntersect)
|
||||||
|
|
||||||
|
if BVH.intersectNodeBox(rayOrigin, invRayDirection, node) then
|
||||||
|
if node._node0 then
|
||||||
|
table.insert(nodesToIntersect, node._node0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if node._node1 then
|
||||||
|
table.insert(nodesToIntersect, node._node1)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=node._startIndex, node._endIndex do
|
||||||
|
table.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- go over the list of candidate triangles, and check each of them using ray triangle intersection
|
||||||
|
local triangle = { vec3(), vec3(), vec3() }
|
||||||
|
local ray = {
|
||||||
|
position = vec3(rayOrigin.x, rayOrigin.y, rayOrigin.z),
|
||||||
|
direction = vec3(rayDirection.x, rayDirection.y, rayDirection.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i=1, #trianglesInIntersectingNodes do
|
||||||
|
local triIndex = trianglesInIntersectingNodes[i]
|
||||||
|
|
||||||
|
-- print(triIndex, #self._trianglesArray)
|
||||||
|
triangle[1].x = self._trianglesArray[1+(triIndex-1)*9]
|
||||||
|
triangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]
|
||||||
|
triangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]
|
||||||
|
triangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]
|
||||||
|
triangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]
|
||||||
|
triangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]
|
||||||
|
triangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]
|
||||||
|
triangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]
|
||||||
|
triangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]
|
||||||
|
|
||||||
|
local intersectionPoint, intersectionDistance = intersect.ray_triangle(ray, triangle, backfaceCulling)
|
||||||
|
|
||||||
|
if intersectionPoint then
|
||||||
|
table.insert(intersectingTriangles, {
|
||||||
|
triangle = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },
|
||||||
|
triangleIndex = triIndex,
|
||||||
|
intersectionPoint = intersectionPoint,
|
||||||
|
intersectionDistance = intersectionDistance
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return intersectingTriangles
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH.calcBoundingBoxes(trianglesArray)
|
||||||
|
local p1x, p1y, p1z
|
||||||
|
local p2x, p2y, p2z
|
||||||
|
local p3x, p3y, p3z
|
||||||
|
local minX, minY, minZ
|
||||||
|
local maxX, maxY, maxZ
|
||||||
|
|
||||||
|
local bboxArray = {}
|
||||||
|
|
||||||
|
for i=1, #trianglesArray / 9 do
|
||||||
|
p1x = trianglesArray[1+(i-1)*9]
|
||||||
|
p1y = trianglesArray[1+(i-1)*9+1]
|
||||||
|
p1z = trianglesArray[1+(i-1)*9+2]
|
||||||
|
p2x = trianglesArray[1+(i-1)*9+3]
|
||||||
|
p2y = trianglesArray[1+(i-1)*9+4]
|
||||||
|
p2z = trianglesArray[1+(i-1)*9+5]
|
||||||
|
p3x = trianglesArray[1+(i-1)*9+6]
|
||||||
|
p3y = trianglesArray[1+(i-1)*9+7]
|
||||||
|
p3z = trianglesArray[1+(i-1)*9+8]
|
||||||
|
|
||||||
|
minX = math.min(p1x, p2x, p3x)
|
||||||
|
minY = math.min(p1y, p2y, p3y)
|
||||||
|
minZ = math.min(p1z, p2z, p3z)
|
||||||
|
maxX = math.max(p1x, p2x, p3x)
|
||||||
|
maxY = math.max(p1y, p2y, p3y)
|
||||||
|
maxZ = math.max(p1z, p2z, p3z)
|
||||||
|
|
||||||
|
BVH.setBox(bboxArray, i, i, minX, minY, minZ, maxX, maxY, maxZ)
|
||||||
|
end
|
||||||
|
|
||||||
|
return bboxArray
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH:calcExtents(startIndex, endIndex, expandBy)
|
||||||
|
expandBy = expandBy or 0
|
||||||
|
|
||||||
|
if startIndex > endIndex then
|
||||||
|
return { vec3(), vec3() }
|
||||||
|
end
|
||||||
|
|
||||||
|
local minX = math.huge
|
||||||
|
local minY = math.huge
|
||||||
|
local minZ = math.huge
|
||||||
|
local maxX = -math.huge
|
||||||
|
local maxY = -math.huge
|
||||||
|
local maxZ = -math.huge
|
||||||
|
|
||||||
|
for i=startIndex, endIndex do
|
||||||
|
minX = math.min(self._bboxArray[1+(i-1)*7+1], minX)
|
||||||
|
minY = math.min(self._bboxArray[1+(i-1)*7+2], minY)
|
||||||
|
minZ = math.min(self._bboxArray[1+(i-1)*7+3], minZ)
|
||||||
|
maxX = math.max(self._bboxArray[1+(i-1)*7+4], maxX)
|
||||||
|
maxY = math.max(self._bboxArray[1+(i-1)*7+5], maxY)
|
||||||
|
maxZ = math.max(self._bboxArray[1+(i-1)*7+6], maxZ)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
vec3(minX - expandBy, minY - expandBy, minZ - expandBy),
|
||||||
|
vec3(maxX + expandBy, maxY + expandBy, maxZ + expandBy)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH:splitNode(node)
|
||||||
|
local num_elements = node:elementCount()
|
||||||
|
if (num_elements <= self._maxTrianglesPerNode) or (num_elements <= 0) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local startIndex = node._startIndex
|
||||||
|
local endIndex = node._endIndex
|
||||||
|
|
||||||
|
local leftNode = { {},{},{} }
|
||||||
|
local rightNode = { {},{},{} }
|
||||||
|
local extentCenters = { node:centerX(), node:centerY(), node:centerZ() }
|
||||||
|
|
||||||
|
local extentsLength = {
|
||||||
|
node._extentsMax.x - node._extentsMin.x,
|
||||||
|
node._extentsMax.y - node._extentsMin.y,
|
||||||
|
node._extentsMax.z - node._extentsMin.z
|
||||||
|
}
|
||||||
|
|
||||||
|
local objectCenter = {}
|
||||||
|
for i=startIndex, endIndex do
|
||||||
|
objectCenter[1] = (self._bboxArray[1+(i-1)*7+1] + self._bboxArray[1+(i-1)*7+4]) * 0.5 -- center = (min + max) / 2
|
||||||
|
objectCenter[2] = (self._bboxArray[1+(i-1)*7+2] + self._bboxArray[1+(i-1)*7+5]) * 0.5 -- center = (min + max) / 2
|
||||||
|
objectCenter[3] = (self._bboxArray[1+(i-1)*7+3] + self._bboxArray[1+(i-1)*7+6]) * 0.5 -- center = (min + max) / 2
|
||||||
|
|
||||||
|
for j=1, 3 do
|
||||||
|
if objectCenter[j] < extentCenters[j] then
|
||||||
|
table.insert(leftNode[j], i)
|
||||||
|
else
|
||||||
|
table.insert(rightNode[j], i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check if we couldn't split the node by any of the axes (x, y or z). halt
|
||||||
|
-- here, dont try to split any more (cause it will always fail, and we'll
|
||||||
|
-- enter an infinite loop
|
||||||
|
local splitFailed = {
|
||||||
|
#leftNode[1] == 0 or #rightNode[1] == 0,
|
||||||
|
#leftNode[2] == 0 or #rightNode[2] == 0,
|
||||||
|
#leftNode[3] == 0 or #rightNode[3] == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if splitFailed[1] and splitFailed[2] and splitFailed[3] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- choose the longest split axis. if we can't split by it, choose next best one.
|
||||||
|
local splitOrder = { 1, 2, 3 }
|
||||||
|
table.sort(splitOrder, function(a, b)
|
||||||
|
return extentsLength[a] > extentsLength[b]
|
||||||
|
end)
|
||||||
|
|
||||||
|
local leftElements
|
||||||
|
local rightElements
|
||||||
|
|
||||||
|
for i=1, 3 do
|
||||||
|
local candidateIndex = splitOrder[i]
|
||||||
|
if not splitFailed[candidateIndex] then
|
||||||
|
leftElements = leftNode[candidateIndex]
|
||||||
|
rightElements = rightNode[candidateIndex]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- sort the elements in range (startIndex, endIndex) according to which node they should be at
|
||||||
|
local node0Start = startIndex
|
||||||
|
local node1Start = node0Start + #leftElements
|
||||||
|
local node0End = node1Start - 1
|
||||||
|
local node1End = endIndex
|
||||||
|
local currElement
|
||||||
|
|
||||||
|
local helperPos = node._startIndex
|
||||||
|
local concatenatedElements = {}
|
||||||
|
|
||||||
|
for _, element in ipairs(leftElements) do
|
||||||
|
table.insert(concatenatedElements, element)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, element in ipairs(rightElements) do
|
||||||
|
table.insert(concatenatedElements, element)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- print(#leftElements, #rightElements, #concatenatedElements)
|
||||||
|
|
||||||
|
for i=1, #concatenatedElements do
|
||||||
|
currElement = concatenatedElements[i]
|
||||||
|
BVH.copyBox(self._bboxArray, currElement, self._bboxHelper, helperPos)
|
||||||
|
helperPos = helperPos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- copy results back to main array
|
||||||
|
for i=1+(node._startIndex-1)*7, node._endIndex*7 do
|
||||||
|
self._bboxArray[i] = self._bboxHelper[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create 2 new nodes for the node we just split, and add links to them from the parent node
|
||||||
|
local node0Extents = self:calcExtents(node0Start, node0End, EPSILON)
|
||||||
|
local node1Extents = self:calcExtents(node1Start, node1End, EPSILON)
|
||||||
|
|
||||||
|
local node0 = Node(node0Extents[1], node0Extents[2], node0Start, node0End, node._level + 1)
|
||||||
|
local node1 = Node(node1Extents[1], node1Extents[2], node1Start, node1End, node._level + 1)
|
||||||
|
|
||||||
|
node._node0 = node0
|
||||||
|
node._node1 = node1
|
||||||
|
node:clearShapes()
|
||||||
|
|
||||||
|
-- add new nodes to the split queue
|
||||||
|
table.insert(self._nodes_to_split, node0)
|
||||||
|
table.insert(self._nodes_to_split, node1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH._calcTValues(minVal, maxVal, rayOriginCoord, invdir)
|
||||||
|
local res = { min=0, max=0 }
|
||||||
|
|
||||||
|
if invdir >= 0 then
|
||||||
|
res.min = ( minVal - rayOriginCoord ) * invdir
|
||||||
|
res.max = ( maxVal - rayOriginCoord ) * invdir
|
||||||
|
else
|
||||||
|
res.min = ( maxVal - rayOriginCoord ) * invdir
|
||||||
|
res.max = ( minVal - rayOriginCoord ) * invdir
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH.intersectNodeBox(rayOrigin, invRayDirection, node)
|
||||||
|
local t = BVH._calcTValues(node._extentsMin.x, node._extentsMax.x, rayOrigin.x, invRayDirection.x)
|
||||||
|
local ty = BVH._calcTValues(node._extentsMin.y, node._extentsMax.y, rayOrigin.y, invRayDirection.y)
|
||||||
|
|
||||||
|
if t.min > ty.max or ty.min > t.max then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- These lines also handle the case where tmin or tmax is NaN
|
||||||
|
-- (result of 0 * Infinity). x !== x returns true if x is NaN
|
||||||
|
if ty.min > t.min or t.min ~= t.min then
|
||||||
|
t.min = ty.min
|
||||||
|
end
|
||||||
|
|
||||||
|
if ty.max < t.max or t.max ~= t.max then
|
||||||
|
t.max = ty.max
|
||||||
|
end
|
||||||
|
|
||||||
|
local tz = BVH._calcTValues(node._extentsMin.z, node._extentsMax.z, rayOrigin.z, invRayDirection.z)
|
||||||
|
|
||||||
|
if t.min > tz.max or tz.min > t.max then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if tz.min > t.min or t.min ~= t.min then
|
||||||
|
t.min = tz.min
|
||||||
|
end
|
||||||
|
|
||||||
|
if tz.max < t.max or t.max ~= t.max then
|
||||||
|
t.max = tz.max
|
||||||
|
end
|
||||||
|
|
||||||
|
--return point closest to the ray (positive side)
|
||||||
|
if t.max < 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH.setBox(bboxArray, pos, triangleId, minX, minY, minZ, maxX, maxY, maxZ)
|
||||||
|
bboxArray[1+(pos-1)*7] = triangleId
|
||||||
|
bboxArray[1+(pos-1)*7+1] = minX
|
||||||
|
bboxArray[1+(pos-1)*7+2] = minY
|
||||||
|
bboxArray[1+(pos-1)*7+3] = minZ
|
||||||
|
bboxArray[1+(pos-1)*7+4] = maxX
|
||||||
|
bboxArray[1+(pos-1)*7+5] = maxY
|
||||||
|
bboxArray[1+(pos-1)*7+6] = maxZ
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH.copyBox(sourceArray, sourcePos, destArray, destPos)
|
||||||
|
destArray[1+(destPos-1)*7] = sourceArray[1+(sourcePos-1)*7]
|
||||||
|
destArray[1+(destPos-1)*7+1] = sourceArray[1+(sourcePos-1)*7+1]
|
||||||
|
destArray[1+(destPos-1)*7+2] = sourceArray[1+(sourcePos-1)*7+2]
|
||||||
|
destArray[1+(destPos-1)*7+3] = sourceArray[1+(sourcePos-1)*7+3]
|
||||||
|
destArray[1+(destPos-1)*7+4] = sourceArray[1+(sourcePos-1)*7+4]
|
||||||
|
destArray[1+(destPos-1)*7+5] = sourceArray[1+(sourcePos-1)*7+5]
|
||||||
|
destArray[1+(destPos-1)*7+6] = sourceArray[1+(sourcePos-1)*7+6]
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVH.getBox(bboxArray, pos, outputBox)
|
||||||
|
outputBox.triangleId = bboxArray[1+(pos-1)*7]
|
||||||
|
outputBox.minX = bboxArray[1+(pos-1)*7+1]
|
||||||
|
outputBox.minY = bboxArray[1+(pos-1)*7+2]
|
||||||
|
outputBox.minZ = bboxArray[1+(pos-1)*7+3]
|
||||||
|
outputBox.maxX = bboxArray[1+(pos-1)*7+4]
|
||||||
|
outputBox.maxY = bboxArray[1+(pos-1)*7+5]
|
||||||
|
outputBox.maxZ = bboxArray[1+(pos-1)*7+6]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new_node(extentsMin, extentsMax, startIndex, endIndex, level)
|
||||||
|
return setmetatable({
|
||||||
|
_extentsMin = extentsMin,
|
||||||
|
_extentsMax = extentsMax,
|
||||||
|
_startIndex = startIndex,
|
||||||
|
_endIndex = endIndex,
|
||||||
|
_level = level
|
||||||
|
--_node0 = nil
|
||||||
|
--_node1 = nil
|
||||||
|
}, BVHNode)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVHNode:elementCount()
|
||||||
|
return (self._endIndex + 1) - self._startIndex
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVHNode:centerX()
|
||||||
|
return (self._extentsMin.x + self._extentsMax.x) * 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVHNode:centerY()
|
||||||
|
return (self._extentsMin.y + self._extentsMax.y) * 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVHNode:centerZ()
|
||||||
|
return (self._extentsMin.z + self._extentsMax.z) * 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVHNode:clearShapes()
|
||||||
|
self._startIndex = 0
|
||||||
|
self._endIndex = -1
|
||||||
|
end
|
||||||
|
|
||||||
|
function BVHNode.ngSphereRadius(extentsMin, extentsMax)
|
||||||
|
local centerX = (extentsMin.x + extentsMax.x) * 0.5
|
||||||
|
local centerY = (extentsMin.y + extentsMax.y) * 0.5
|
||||||
|
local centerZ = (extentsMin.z + extentsMax.z) * 0.5
|
||||||
|
|
||||||
|
local extentsMinDistSqr =
|
||||||
|
(centerX - extentsMin.x) * (centerX - extentsMin.x) +
|
||||||
|
(centerY - extentsMin.y) * (centerY - extentsMin.y) +
|
||||||
|
(centerZ - extentsMin.z) * (centerZ - extentsMin.z)
|
||||||
|
|
||||||
|
local extentsMaxDistSqr =
|
||||||
|
(centerX - extentsMax.x) * (centerX - extentsMax.x) +
|
||||||
|
(centerY - extentsMax.y) * (centerY - extentsMax.y) +
|
||||||
|
(centerZ - extentsMax.z) * (centerZ - extentsMax.z)
|
||||||
|
|
||||||
|
return math.sqrt(math.max(extentsMinDistSqr, extentsMaxDistSqr))
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
--- Draws node boundaries visually for debugging.
|
||||||
|
-- @param cube Cube model to draw
|
||||||
|
-- @param depth Used for recurcive calls to self method
|
||||||
|
function OctreeNode:draw_bounds(cube, depth)
|
||||||
|
depth = depth or 0
|
||||||
|
local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically
|
||||||
|
|
||||||
|
love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
|
||||||
|
local m = mat4()
|
||||||
|
:translate(self.center)
|
||||||
|
:scale(vec3(self.adjLength, self.adjLength, self.adjLength))
|
||||||
|
|
||||||
|
love.graphics.updateMatrix("transform", m)
|
||||||
|
love.graphics.setWireframe(true)
|
||||||
|
love.graphics.draw(cube)
|
||||||
|
love.graphics.setWireframe(false)
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
child:draw_bounds(cube, depth + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setColor(255, 255, 255)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Draws the bounds of all objects in the tree visually for debugging.
|
||||||
|
-- @param cube Cube model to draw
|
||||||
|
-- @param filter a function returning true or false to determine visibility.
|
||||||
|
function OctreeNode:draw_objects(cube, filter)
|
||||||
|
local tint = self.baseLength / 20
|
||||||
|
love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)
|
||||||
|
|
||||||
|
for _, object in ipairs(self.objects) do
|
||||||
|
if filter and filter(object.data) or not filter then
|
||||||
|
local m = mat4()
|
||||||
|
:translate(object.bounds.center)
|
||||||
|
:scale(object.bounds.size)
|
||||||
|
|
||||||
|
love.graphics.updateMatrix("transform", m)
|
||||||
|
love.graphics.draw(cube)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
child:draw_objects(cube, filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setColor(255, 255, 255)
|
||||||
|
end
|
||||||
|
|
||||||
|
--]]
|
||||||
|
|
||||||
|
Node = setmetatable({
|
||||||
|
new = new_node
|
||||||
|
}, {
|
||||||
|
__call = function(_, ...) return new_node(...) end
|
||||||
|
})
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
new = new
|
||||||
|
}, {
|
||||||
|
__call = function(_, ...) return new(...) end
|
||||||
|
})
|
||||||
400
libs/cpml/color.lua
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
--- Color utilities
|
||||||
|
-- @module color
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local constants = require(modules .. "constants")
|
||||||
|
local utils = require(modules .. "utils")
|
||||||
|
local precond = require(modules .. "_private_precond")
|
||||||
|
local color = {}
|
||||||
|
local color_mt = {}
|
||||||
|
|
||||||
|
local function new(r, g, b, a)
|
||||||
|
local c = { r, g, b, a }
|
||||||
|
c._c = c
|
||||||
|
return setmetatable(c, color_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)
|
||||||
|
-- hsv_to_color(hsv)
|
||||||
|
-- Converts a set of HSV values to a color. hsv is a table.
|
||||||
|
-- See also: hsv(h, s, v)
|
||||||
|
local function hsv_to_color(hsv)
|
||||||
|
local i
|
||||||
|
local f, q, p, t
|
||||||
|
local h, s, v
|
||||||
|
local a = hsv[4] or 1
|
||||||
|
s = hsv[2]
|
||||||
|
v = hsv[3]
|
||||||
|
|
||||||
|
if s == 0 then
|
||||||
|
return new(v, v, v, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
h = hsv[1] * 6 -- sector 0 to 5
|
||||||
|
|
||||||
|
i = math.floor(h)
|
||||||
|
f = h - i -- factorial part of h
|
||||||
|
p = v * (1-s)
|
||||||
|
q = v * (1-s*f)
|
||||||
|
t = v * (1-s*(1-f))
|
||||||
|
|
||||||
|
if i == 0 then return new(v, t, p, a)
|
||||||
|
elseif i == 1 then return new(q, v, p, a)
|
||||||
|
elseif i == 2 then return new(p, v, t, a)
|
||||||
|
elseif i == 3 then return new(p, q, v, a)
|
||||||
|
elseif i == 4 then return new(t, p, v, a)
|
||||||
|
else return new(v, p, q, a)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- color_to_hsv(c)
|
||||||
|
-- Takes in a normal color and returns a table with the HSV values.
|
||||||
|
local function color_to_hsv(c)
|
||||||
|
local r = c[1]
|
||||||
|
local g = c[2]
|
||||||
|
local b = c[3]
|
||||||
|
local a = c[4] or 1
|
||||||
|
local h, s, v
|
||||||
|
|
||||||
|
local min = math.min(r, g, b)
|
||||||
|
local max = math.max(r, g, b)
|
||||||
|
v = max
|
||||||
|
|
||||||
|
local delta = max - min
|
||||||
|
|
||||||
|
-- black, nothing else is really possible here.
|
||||||
|
if min == 0 and max == 0 then
|
||||||
|
return { 0, 0, 0, a }
|
||||||
|
end
|
||||||
|
|
||||||
|
if max ~= 0 then
|
||||||
|
s = delta / max
|
||||||
|
else
|
||||||
|
-- r = g = b = 0 s = 0, v is undefined
|
||||||
|
s = 0
|
||||||
|
h = -1
|
||||||
|
return { h, s, v, 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Prevent division by zero.
|
||||||
|
if delta == 0 then
|
||||||
|
delta = constants.DBL_EPSILON
|
||||||
|
end
|
||||||
|
|
||||||
|
if r == max then
|
||||||
|
h = ( g - b ) / delta -- yellow/magenta
|
||||||
|
elseif g == max then
|
||||||
|
h = 2 + ( b - r ) / delta -- cyan/yellow
|
||||||
|
else
|
||||||
|
h = 4 + ( r - g ) / delta -- magenta/cyan
|
||||||
|
end
|
||||||
|
|
||||||
|
h = h / 6 -- normalize from segment 0..5
|
||||||
|
|
||||||
|
if h < 0 then
|
||||||
|
h = h + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return { h, s, v, a }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param x Can be of three types: </br>
|
||||||
|
-- number red component 0-1
|
||||||
|
-- table {r, g, b, a}
|
||||||
|
-- nil for {0,0,0,0}
|
||||||
|
-- @tparam number g Green component 0-1
|
||||||
|
-- @tparam number b Blue component 0-1
|
||||||
|
-- @tparam number a Alpha component 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.new(r, g, b, a)
|
||||||
|
-- number, number, number, number
|
||||||
|
if r and g and b and a then
|
||||||
|
precond.typeof(r, "number", "new: Wrong argument type for r")
|
||||||
|
precond.typeof(g, "number", "new: Wrong argument type for g")
|
||||||
|
precond.typeof(b, "number", "new: Wrong argument type for b")
|
||||||
|
precond.typeof(a, "number", "new: Wrong argument type for a")
|
||||||
|
|
||||||
|
return new(r, g, b, a)
|
||||||
|
|
||||||
|
-- {r, g, b, a}
|
||||||
|
elseif type(r) == "table" then
|
||||||
|
local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
|
||||||
|
precond.typeof(rr, "number", "new: Wrong argument type for r")
|
||||||
|
precond.typeof(gg, "number", "new: Wrong argument type for g")
|
||||||
|
precond.typeof(bb, "number", "new: Wrong argument type for b")
|
||||||
|
precond.typeof(aa, "number", "new: Wrong argument type for a")
|
||||||
|
|
||||||
|
return new(rr, gg, bb, aa)
|
||||||
|
end
|
||||||
|
|
||||||
|
return new(0, 0, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert hue,saturation,value table to color object.
|
||||||
|
-- @tparam table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
|
||||||
|
-- @treturn color out
|
||||||
|
color.hsv_to_color_table = hsv_to_color
|
||||||
|
|
||||||
|
--- Convert color to hue,saturation,value table
|
||||||
|
-- @tparam color in
|
||||||
|
-- @treturn table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
|
||||||
|
color.color_to_hsv_table = color_to_hsv
|
||||||
|
|
||||||
|
--- Convert hue,saturation,value to color object.
|
||||||
|
-- @tparam number h hue 0-1
|
||||||
|
-- @tparam number s saturation 0-1
|
||||||
|
-- @tparam number v value 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.from_hsv(h, s, v)
|
||||||
|
return hsv_to_color { h, s, v }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert hue,saturation,value to color object.
|
||||||
|
-- @tparam number h hue 0-1
|
||||||
|
-- @tparam number s saturation 0-1
|
||||||
|
-- @tparam number v value 0-1
|
||||||
|
-- @tparam number a alpha 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.from_hsva(h, s, v, a)
|
||||||
|
return hsv_to_color { h, s, v, a }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Invert a color.
|
||||||
|
-- @tparam color to invert
|
||||||
|
-- @treturn color out
|
||||||
|
function color.invert(c)
|
||||||
|
return new(1 - c[1], 1 - c[2], 1 - c[3], c[4])
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Lighten a color by a component-wise fixed amount (alpha unchanged)
|
||||||
|
-- @tparam color to lighten
|
||||||
|
-- @tparam number amount to increase each component by, 0-1 scale
|
||||||
|
-- @treturn color out
|
||||||
|
function color.lighten(c, v)
|
||||||
|
return new(
|
||||||
|
utils.clamp(c[1] + v, 0, 1),
|
||||||
|
utils.clamp(c[2] + v, 0, 1),
|
||||||
|
utils.clamp(c[3] + v, 0, 1),
|
||||||
|
c[4]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Interpolate between two colors.
|
||||||
|
-- @tparam color at start
|
||||||
|
-- @tparam color at end
|
||||||
|
-- @tparam number s in 0-1 progress between the two colors
|
||||||
|
-- @treturn color out
|
||||||
|
function color.lerp(a, b, s)
|
||||||
|
return a + s * (b - a)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unpack a color into individual components in 0-1.
|
||||||
|
-- @tparam color to unpack
|
||||||
|
-- @treturn number r in 0-1
|
||||||
|
-- @treturn number g in 0-1
|
||||||
|
-- @treturn number b in 0-1
|
||||||
|
-- @treturn number a in 0-1
|
||||||
|
function color.unpack(c)
|
||||||
|
return c[1], c[2], c[3], c[4]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unpack a color into individual components in 0-255.
|
||||||
|
-- @tparam color to unpack
|
||||||
|
-- @treturn number r in 0-255
|
||||||
|
-- @treturn number g in 0-255
|
||||||
|
-- @treturn number b in 0-255
|
||||||
|
-- @treturn number a in 0-255
|
||||||
|
function color.as_255(c)
|
||||||
|
return c[1] * 255, c[2] * 255, c[3] * 255, c[4] * 255
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Darken a color by a component-wise fixed amount (alpha unchanged)
|
||||||
|
-- @tparam color to darken
|
||||||
|
-- @tparam number amount to decrease each component by, 0-1 scale
|
||||||
|
-- @treturn color out
|
||||||
|
function color.darken(c, v)
|
||||||
|
return new(
|
||||||
|
utils.clamp(c[1] - v, 0, 1),
|
||||||
|
utils.clamp(c[2] - v, 0, 1),
|
||||||
|
utils.clamp(c[3] - v, 0, 1),
|
||||||
|
c[4]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a color's components by a value (alpha unchanged)
|
||||||
|
-- @tparam color to multiply
|
||||||
|
-- @tparam number to multiply each component by
|
||||||
|
-- @treturn color out
|
||||||
|
function color.multiply(c, v)
|
||||||
|
local t = color.new()
|
||||||
|
for i = 1, 3 do
|
||||||
|
t[i] = c[i] * v
|
||||||
|
end
|
||||||
|
|
||||||
|
t[4] = c[4]
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
-- directly set alpha channel
|
||||||
|
-- @tparam color to alter
|
||||||
|
-- @tparam number new alpha 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.alpha(c, v)
|
||||||
|
local t = color.new()
|
||||||
|
for i = 1, 3 do
|
||||||
|
t[i] = c[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
t[4] = v
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a color's alpha by a value
|
||||||
|
-- @tparam color to multiply
|
||||||
|
-- @tparam number to multiply alpha by
|
||||||
|
-- @treturn color out
|
||||||
|
function color.opacity(c, v)
|
||||||
|
local t = color.new()
|
||||||
|
for i = 1, 3 do
|
||||||
|
t[i] = c[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
t[4] = c[4] * v
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set a color's hue (saturation, value, alpha unchanged)
|
||||||
|
-- @tparam color to alter
|
||||||
|
-- @tparam hue to set 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.hue(col, hue)
|
||||||
|
local c = color_to_hsv(col)
|
||||||
|
c[1] = (hue + 1) % 1
|
||||||
|
return hsv_to_color(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set a color's saturation (hue, value, alpha unchanged)
|
||||||
|
-- @tparam color to alter
|
||||||
|
-- @tparam saturation to set 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.saturation(col, percent)
|
||||||
|
local c = color_to_hsv(col)
|
||||||
|
c[2] = utils.clamp(percent, 0, 1)
|
||||||
|
return hsv_to_color(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set a color's value (saturation, hue, alpha unchanged)
|
||||||
|
-- @tparam color to alter
|
||||||
|
-- @tparam value to set 0-1
|
||||||
|
-- @treturn color out
|
||||||
|
function color.value(col, percent)
|
||||||
|
local c = color_to_hsv(col)
|
||||||
|
c[3] = utils.clamp(percent, 0, 1)
|
||||||
|
return hsv_to_color(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
|
||||||
|
function color.gamma_to_linear(r, g, b, a)
|
||||||
|
local function convert(c)
|
||||||
|
if c > 1.0 then
|
||||||
|
return 1.0
|
||||||
|
elseif c < 0.0 then
|
||||||
|
return 0.0
|
||||||
|
elseif c <= 0.04045 then
|
||||||
|
return c / 12.92
|
||||||
|
else
|
||||||
|
return math.pow((c + 0.055) / 1.055, 2.4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(r) == "table" then
|
||||||
|
local c = {}
|
||||||
|
for i = 1, 3 do
|
||||||
|
c[i] = convert(r[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
c[4] = r[4]
|
||||||
|
return c
|
||||||
|
else
|
||||||
|
return convert(r), convert(g), convert(b), a or 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB
|
||||||
|
function color.linear_to_gamma(r, g, b, a)
|
||||||
|
local function convert(c)
|
||||||
|
if c > 1.0 then
|
||||||
|
return 1.0
|
||||||
|
elseif c < 0.0 then
|
||||||
|
return 0.0
|
||||||
|
elseif c < 0.0031308 then
|
||||||
|
return c * 12.92
|
||||||
|
else
|
||||||
|
return 1.055 * math.pow(c, 0.41666) - 0.055
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(r) == "table" then
|
||||||
|
local c = {}
|
||||||
|
for i = 1, 3 do
|
||||||
|
c[i] = convert(r[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
c[4] = r[4]
|
||||||
|
return c
|
||||||
|
else
|
||||||
|
return convert(r), convert(g), convert(b), a or 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if color is valid
|
||||||
|
-- @tparam color to test
|
||||||
|
-- @treturn boolean is color
|
||||||
|
function color.is_color(a)
|
||||||
|
if type(a) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, 4 do
|
||||||
|
if type(a[i]) ~= "number" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam color a color to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function color.to_string(a)
|
||||||
|
return string.format("[ %3.0f, %3.0f, %3.0f, %3.0f ]", a[1], a[2], a[3], a[4])
|
||||||
|
end
|
||||||
|
|
||||||
|
color_mt.__index = color
|
||||||
|
color_mt.__tostring = color.to_string
|
||||||
|
|
||||||
|
function color_mt.__call(_, r, g, b, a)
|
||||||
|
return color.new(r, g, b, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
function color_mt.__add(a, b)
|
||||||
|
return new(a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4])
|
||||||
|
end
|
||||||
|
|
||||||
|
function color_mt.__sub(a, b)
|
||||||
|
return new(a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4])
|
||||||
|
end
|
||||||
|
|
||||||
|
function color_mt.__mul(a, b)
|
||||||
|
if type(a) == "number" then
|
||||||
|
return new(a * b[1], a * b[2], a * b[3], a * b[4])
|
||||||
|
elseif type(b) == "number" then
|
||||||
|
return new(b * a[1], b * a[2], b * a[3], b * a[4])
|
||||||
|
else
|
||||||
|
return new(a[1] * b[1], a[2] * b[2], a[3] * b[3], a[4] * b[4])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, color_mt)
|
||||||
20
libs/cpml/constants.lua
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
--- Various useful constants
|
||||||
|
-- @module constants
|
||||||
|
|
||||||
|
--- Constants
|
||||||
|
-- @table constants
|
||||||
|
-- @field FLT_EPSILON Floating point precision breaks down
|
||||||
|
-- @field DBL_EPSILON Double-precise floating point precision breaks down
|
||||||
|
-- @field DOT_THRESHOLD Close enough to 1 for interpolations to occur
|
||||||
|
local constants = {}
|
||||||
|
|
||||||
|
-- same as C's FLT_EPSILON
|
||||||
|
constants.FLT_EPSILON = 1.19209290e-07
|
||||||
|
|
||||||
|
-- same as C's DBL_EPSILON
|
||||||
|
constants.DBL_EPSILON = 2.2204460492503131e-16
|
||||||
|
|
||||||
|
-- used for quaternion.slerp
|
||||||
|
constants.DOT_THRESHOLD = 0.9995
|
||||||
|
|
||||||
|
return constants
|
||||||
709
libs/cpml/intersect.lua
Normal file
@@ -0,0 +1,709 @@
|
|||||||
|
--- Various geometric intersections
|
||||||
|
-- @module intersect
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local constants = require(modules .. "constants")
|
||||||
|
local mat4 = require(modules .. "mat4")
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local utils = require(modules .. "utils")
|
||||||
|
local DBL_EPSILON = constants.DBL_EPSILON
|
||||||
|
local sqrt = math.sqrt
|
||||||
|
local abs = math.abs
|
||||||
|
local min = math.min
|
||||||
|
local max = math.max
|
||||||
|
local intersect = {}
|
||||||
|
|
||||||
|
-- https://blogs.msdn.microsoft.com/rezanour/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests/
|
||||||
|
-- point is a vec3
|
||||||
|
-- triangle[1] is a vec3
|
||||||
|
-- triangle[2] is a vec3
|
||||||
|
-- triangle[3] is a vec3
|
||||||
|
function intersect.point_triangle(point, triangle)
|
||||||
|
local u = triangle[2] - triangle[1]
|
||||||
|
local v = triangle[3] - triangle[1]
|
||||||
|
local w = point - triangle[1]
|
||||||
|
|
||||||
|
local vw = v:cross(w)
|
||||||
|
local vu = v:cross(u)
|
||||||
|
|
||||||
|
if vw:dot(vu) < 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local uw = u:cross(w)
|
||||||
|
local uv = u:cross(v)
|
||||||
|
|
||||||
|
if uw:dot(uv) < 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local d = uv:len()
|
||||||
|
local r = vw:len() / d
|
||||||
|
local t = uw:len() / d
|
||||||
|
|
||||||
|
return r + t <= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- point is a vec3
|
||||||
|
-- aabb.min is a vec3
|
||||||
|
-- aabb.max is a vec3
|
||||||
|
function intersect.point_aabb(point, aabb)
|
||||||
|
return
|
||||||
|
aabb.min.x <= point.x and
|
||||||
|
aabb.max.x >= point.x and
|
||||||
|
aabb.min.y <= point.y and
|
||||||
|
aabb.max.y >= point.y and
|
||||||
|
aabb.min.z <= point.z and
|
||||||
|
aabb.max.z >= point.z
|
||||||
|
end
|
||||||
|
|
||||||
|
-- point is a vec3
|
||||||
|
-- frustum.left is a plane { a, b, c, d }
|
||||||
|
-- frustum.right is a plane { a, b, c, d }
|
||||||
|
-- frustum.bottom is a plane { a, b, c, d }
|
||||||
|
-- frustum.top is a plane { a, b, c, d }
|
||||||
|
-- frustum.near is a plane { a, b, c, d }
|
||||||
|
-- frustum.far is a plane { a, b, c, d }
|
||||||
|
function intersect.point_frustum(point, frustum)
|
||||||
|
local x, y, z = point:unpack()
|
||||||
|
local planes = {
|
||||||
|
frustum.left,
|
||||||
|
frustum.right,
|
||||||
|
frustum.bottom,
|
||||||
|
frustum.top,
|
||||||
|
frustum.near,
|
||||||
|
frustum.far or false
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Skip the last test for infinite projections, it'll never fail.
|
||||||
|
if not planes[6] then
|
||||||
|
table.remove(planes)
|
||||||
|
end
|
||||||
|
|
||||||
|
local dot
|
||||||
|
for i = 1, #planes do
|
||||||
|
dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
|
||||||
|
if dot <= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://www.lighthouse3d.com/tutorials/maths/ray-triangle-intersection/
|
||||||
|
-- ray.position is a vec3
|
||||||
|
-- ray.direction is a vec3
|
||||||
|
-- triangle[1] is a vec3
|
||||||
|
-- triangle[2] is a vec3
|
||||||
|
-- triangle[3] is a vec3
|
||||||
|
-- backface_cull is a boolean (optional)
|
||||||
|
function intersect.ray_triangle(ray, triangle, backface_cull)
|
||||||
|
local e1 = triangle[2] - triangle[1]
|
||||||
|
local e2 = triangle[3] - triangle[1]
|
||||||
|
local h = ray.direction:cross(e2)
|
||||||
|
local a = h:dot(e1)
|
||||||
|
|
||||||
|
-- if a is negative, ray hits the backface
|
||||||
|
if backface_cull and a < 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if a is too close to 0, ray does not intersect triangle
|
||||||
|
if abs(a) <= DBL_EPSILON then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local f = 1 / a
|
||||||
|
local s = ray.position - triangle[1]
|
||||||
|
local u = s:dot(h) * f
|
||||||
|
|
||||||
|
-- ray does not intersect triangle
|
||||||
|
if u < 0 or u > 1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local q = s:cross(e1)
|
||||||
|
local v = ray.direction:dot(q) * f
|
||||||
|
|
||||||
|
-- ray does not intersect triangle
|
||||||
|
if v < 0 or u + v > 1 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- at this stage we can compute t to find out where
|
||||||
|
-- the intersection point is on the line
|
||||||
|
local t = q:dot(e2) * f
|
||||||
|
|
||||||
|
-- return position of intersection and distance from ray origin
|
||||||
|
if t >= DBL_EPSILON then
|
||||||
|
return ray.position + ray.direction * t, t
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ray does not intersect triangle
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- https://gamedev.stackexchange.com/questions/96459/fast-ray-sphere-collision-code
|
||||||
|
-- ray.position is a vec3
|
||||||
|
-- ray.direction is a vec3
|
||||||
|
-- sphere.position is a vec3
|
||||||
|
-- sphere.radius is a number
|
||||||
|
function intersect.ray_sphere(ray, sphere)
|
||||||
|
local offset = ray.position - sphere.position
|
||||||
|
local b = offset:dot(ray.direction)
|
||||||
|
local c = offset:dot(offset) - sphere.radius * sphere.radius
|
||||||
|
|
||||||
|
-- ray's position outside sphere (c > 0)
|
||||||
|
-- ray's direction pointing away from sphere (b > 0)
|
||||||
|
if c > 0 and b > 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local discr = b * b - c
|
||||||
|
|
||||||
|
-- negative discriminant
|
||||||
|
if discr < 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clamp t to 0
|
||||||
|
local t = -b - sqrt(discr)
|
||||||
|
t = t < 0 and 0 or t
|
||||||
|
|
||||||
|
-- Return collision point and distance from ray origin
|
||||||
|
return ray.position + ray.direction * t, t
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://gamedev.stackexchange.com/a/18459
|
||||||
|
-- ray.position is a vec3
|
||||||
|
-- ray.direction is a vec3
|
||||||
|
-- aabb.min is a vec3
|
||||||
|
-- aabb.max is a vec3
|
||||||
|
function intersect.ray_aabb(ray, aabb)
|
||||||
|
local dir = ray.direction:normalize()
|
||||||
|
local dirfrac = vec3(
|
||||||
|
1 / dir.x,
|
||||||
|
1 / dir.y,
|
||||||
|
1 / dir.z
|
||||||
|
)
|
||||||
|
|
||||||
|
local t1 = (aabb.min.x - ray.position.x) * dirfrac.x
|
||||||
|
local t2 = (aabb.max.x - ray.position.x) * dirfrac.x
|
||||||
|
local t3 = (aabb.min.y - ray.position.y) * dirfrac.y
|
||||||
|
local t4 = (aabb.max.y - ray.position.y) * dirfrac.y
|
||||||
|
local t5 = (aabb.min.z - ray.position.z) * dirfrac.z
|
||||||
|
local t6 = (aabb.max.z - ray.position.z) * dirfrac.z
|
||||||
|
|
||||||
|
local tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))
|
||||||
|
local tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))
|
||||||
|
|
||||||
|
-- ray is intersecting AABB, but whole AABB is behind us
|
||||||
|
if tmax < 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ray does not intersect AABB
|
||||||
|
if tmin > tmax then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return collision point and distance from ray origin
|
||||||
|
return ray.position + ray.direction * tmin, tmin
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://stackoverflow.com/a/23976134/1190664
|
||||||
|
-- ray.position is a vec3
|
||||||
|
-- ray.direction is a vec3
|
||||||
|
-- plane.position is a vec3
|
||||||
|
-- plane.normal is a vec3
|
||||||
|
function intersect.ray_plane(ray, plane)
|
||||||
|
local denom = plane.normal:dot(ray.direction)
|
||||||
|
|
||||||
|
-- ray does not intersect plane
|
||||||
|
if abs(denom) < DBL_EPSILON then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- distance of direction
|
||||||
|
local d = plane.position - ray.position
|
||||||
|
local t = d:dot(plane.normal) / denom
|
||||||
|
|
||||||
|
if t < DBL_EPSILON then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return collision point and distance from ray origin
|
||||||
|
return ray.position + ray.direction * t, t
|
||||||
|
end
|
||||||
|
|
||||||
|
function intersect.ray_capsule(ray, capsule)
|
||||||
|
local dist2, p1, p2 = intersect.closest_point_segment_segment(
|
||||||
|
ray.position,
|
||||||
|
ray.position + ray.direction * 1e10,
|
||||||
|
capsule.a,
|
||||||
|
capsule.b
|
||||||
|
)
|
||||||
|
if dist2 <= capsule.radius^2 then
|
||||||
|
return p1
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- https://web.archive.org/web/20120414063459/http://local.wasp.uwa.edu.au/~pbourke//geometry/lineline3d/
|
||||||
|
-- a[1] is a vec3
|
||||||
|
-- a[2] is a vec3
|
||||||
|
-- b[1] is a vec3
|
||||||
|
-- b[2] is a vec3
|
||||||
|
-- e is a number
|
||||||
|
function intersect.line_line(a, b, e)
|
||||||
|
-- new points
|
||||||
|
local p13 = a[1] - b[1]
|
||||||
|
local p43 = b[2] - b[1]
|
||||||
|
local p21 = a[2] - a[1]
|
||||||
|
|
||||||
|
-- if lengths are negative or too close to 0, lines do not intersect
|
||||||
|
if p43:len2() < DBL_EPSILON or p21:len2() < DBL_EPSILON then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dot products
|
||||||
|
local d1343 = p13:dot(p43)
|
||||||
|
local d4321 = p43:dot(p21)
|
||||||
|
local d1321 = p13:dot(p21)
|
||||||
|
local d4343 = p43:dot(p43)
|
||||||
|
local d2121 = p21:dot(p21)
|
||||||
|
local denom = d2121 * d4343 - d4321 * d4321
|
||||||
|
|
||||||
|
-- if denom is too close to 0, lines do not intersect
|
||||||
|
if abs(denom) < DBL_EPSILON then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local numer = d1343 * d4321 - d1321 * d4343
|
||||||
|
local mua = numer / denom
|
||||||
|
local mub = (d1343 + d4321 * mua) / d4343
|
||||||
|
|
||||||
|
-- return positions of intersection on each line
|
||||||
|
local out1 = a[1] + p21 * mua
|
||||||
|
local out2 = b[1] + p43 * mub
|
||||||
|
local dist = out1:dist(out2)
|
||||||
|
|
||||||
|
-- if distance of the shortest segment between lines is less than threshold
|
||||||
|
if e and dist > e then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return { out1, out2 }, dist
|
||||||
|
end
|
||||||
|
|
||||||
|
-- a[1] is a vec3
|
||||||
|
-- a[2] is a vec3
|
||||||
|
-- b[1] is a vec3
|
||||||
|
-- b[2] is a vec3
|
||||||
|
-- e is a number
|
||||||
|
function intersect.segment_segment(a, b, e)
|
||||||
|
local c, d = intersect.line_line(a, b, e)
|
||||||
|
|
||||||
|
if c and ((
|
||||||
|
a[1].x <= c[1].x and
|
||||||
|
a[1].y <= c[1].y and
|
||||||
|
a[1].z <= c[1].z and
|
||||||
|
c[1].x <= a[2].x and
|
||||||
|
c[1].y <= a[2].y and
|
||||||
|
c[1].z <= a[2].z
|
||||||
|
) or (
|
||||||
|
a[1].x >= c[1].x and
|
||||||
|
a[1].y >= c[1].y and
|
||||||
|
a[1].z >= c[1].z and
|
||||||
|
c[1].x >= a[2].x and
|
||||||
|
c[1].y >= a[2].y and
|
||||||
|
c[1].z >= a[2].z
|
||||||
|
)) and ((
|
||||||
|
b[1].x <= c[2].x and
|
||||||
|
b[1].y <= c[2].y and
|
||||||
|
b[1].z <= c[2].z and
|
||||||
|
c[2].x <= b[2].x and
|
||||||
|
c[2].y <= b[2].y and
|
||||||
|
c[2].z <= b[2].z
|
||||||
|
) or (
|
||||||
|
b[1].x >= c[2].x and
|
||||||
|
b[1].y >= c[2].y and
|
||||||
|
b[1].z >= c[2].z and
|
||||||
|
c[2].x >= b[2].x and
|
||||||
|
c[2].y >= b[2].y and
|
||||||
|
c[2].z >= b[2].z
|
||||||
|
)) then
|
||||||
|
return c, d
|
||||||
|
end
|
||||||
|
|
||||||
|
-- segments do not intersect
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- a.min is a vec3
|
||||||
|
-- a.max is a vec3
|
||||||
|
-- b.min is a vec3
|
||||||
|
-- b.max is a vec3
|
||||||
|
function intersect.aabb_aabb(a, b)
|
||||||
|
return
|
||||||
|
a.min.x <= b.max.x and
|
||||||
|
a.max.x >= b.min.x and
|
||||||
|
a.min.y <= b.max.y and
|
||||||
|
a.max.y >= b.min.y and
|
||||||
|
a.min.z <= b.max.z and
|
||||||
|
a.max.z >= b.min.z
|
||||||
|
end
|
||||||
|
|
||||||
|
-- aabb.position is a vec3
|
||||||
|
-- aabb.extent is a vec3 (half-size)
|
||||||
|
-- obb.position is a vec3
|
||||||
|
-- obb.extent is a vec3 (half-size)
|
||||||
|
-- obb.rotation is a mat4
|
||||||
|
function intersect.aabb_obb(aabb, obb)
|
||||||
|
local a = aabb.extent
|
||||||
|
local b = obb.extent
|
||||||
|
local T = obb.position - aabb.position
|
||||||
|
local rot = mat4():transpose(obb.rotation)
|
||||||
|
local B = {}
|
||||||
|
local t
|
||||||
|
|
||||||
|
for i = 1, 3 do
|
||||||
|
B[i] = {}
|
||||||
|
for j = 1, 3 do
|
||||||
|
assert((i - 1) * 4 + j < 16 and (i - 1) * 4 + j > 0)
|
||||||
|
B[i][j] = abs(rot[(i - 1) * 4 + j]) + 1e-6
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
t = abs(T.x)
|
||||||
|
if not (t <= (b.x + a.x * B[1][1] + b.y * B[1][2] + b.z * B[1][3])) then return false end
|
||||||
|
t = abs(T.x * B[1][1] + T.y * B[2][1] + T.z * B[3][1])
|
||||||
|
if not (t <= (b.x + a.x * B[1][1] + a.y * B[2][1] + a.z * B[3][1])) then return false end
|
||||||
|
t = abs(T.y)
|
||||||
|
if not (t <= (a.y + b.x * B[2][1] + b.y * B[2][2] + b.z * B[2][3])) then return false end
|
||||||
|
t = abs(T.z)
|
||||||
|
if not (t <= (a.z + b.x * B[3][1] + b.y * B[3][2] + b.z * B[3][3])) then return false end
|
||||||
|
t = abs(T.x * B[1][2] + T.y * B[2][2] + T.z * B[3][2])
|
||||||
|
if not (t <= (b.y + a.x * B[1][2] + a.y * B[2][2] + a.z * B[3][2])) then return false end
|
||||||
|
t = abs(T.x * B[1][3] + T.y * B[2][3] + T.z * B[3][3])
|
||||||
|
if not (t <= (b.z + a.x * B[1][3] + a.y * B[2][3] + a.z * B[3][3])) then return false end
|
||||||
|
t = abs(T.z * B[2][1] - T.y * B[3][1])
|
||||||
|
if not (t <= (a.y * B[3][1] + a.z * B[2][1] + b.y * B[1][3] + b.z * B[1][2])) then return false end
|
||||||
|
t = abs(T.z * B[2][2] - T.y * B[3][2])
|
||||||
|
if not (t <= (a.y * B[3][2] + a.z * B[2][2] + b.x * B[1][3] + b.z * B[1][1])) then return false end
|
||||||
|
t = abs(T.z * B[2][3] - T.y * B[3][3])
|
||||||
|
if not (t <= (a.y * B[3][3] + a.z * B[2][3] + b.x * B[1][2] + b.y * B[1][1])) then return false end
|
||||||
|
t = abs(T.x * B[3][1] - T.z * B[1][1])
|
||||||
|
if not (t <= (a.x * B[3][1] + a.z * B[1][1] + b.y * B[2][3] + b.z * B[2][2])) then return false end
|
||||||
|
t = abs(T.x * B[3][2] - T.z * B[1][2])
|
||||||
|
if not (t <= (a.x * B[3][2] + a.z * B[1][2] + b.x * B[2][3] + b.z * B[2][1])) then return false end
|
||||||
|
t = abs(T.x * B[3][3] - T.z * B[1][3])
|
||||||
|
if not (t <= (a.x * B[3][3] + a.z * B[1][3] + b.x * B[2][2] + b.y * B[2][1])) then return false end
|
||||||
|
t = abs(T.y * B[1][1] - T.x * B[2][1])
|
||||||
|
if not (t <= (a.x * B[2][1] + a.y * B[1][1] + b.y * B[3][3] + b.z * B[3][2])) then return false end
|
||||||
|
t = abs(T.y * B[1][2] - T.x * B[2][2])
|
||||||
|
if not (t <= (a.x * B[2][2] + a.y * B[1][2] + b.x * B[3][3] + b.z * B[3][1])) then return false end
|
||||||
|
t = abs(T.y * B[1][3] - T.x * B[2][3])
|
||||||
|
if not (t <= (a.x * B[2][3] + a.y * B[1][3] + b.x * B[3][2] + b.y * B[3][1])) then return false end
|
||||||
|
|
||||||
|
-- https://gamedev.stackexchange.com/questions/24078/which-side-was-hit
|
||||||
|
-- Minkowski Sum
|
||||||
|
local wy = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.y - obb.position.y)
|
||||||
|
local hx = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.x - obb.position.x)
|
||||||
|
|
||||||
|
if wy.x > hx.x and wy.y > hx.y and wy.z > hx.z then
|
||||||
|
if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
|
||||||
|
return vec3(obb.rotation * { 0, -1, 0, 1 })
|
||||||
|
else
|
||||||
|
return vec3(obb.rotation * { -1, 0, 0, 1 })
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
|
||||||
|
return vec3(obb.rotation * { 1, 0, 0, 1 })
|
||||||
|
else
|
||||||
|
return vec3(obb.rotation * { 0, 1, 0, 1 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://stackoverflow.com/a/4579069/1190664
|
||||||
|
-- aabb.min is a vec3
|
||||||
|
-- aabb.max is a vec3
|
||||||
|
-- sphere.position is a vec3
|
||||||
|
-- sphere.radius is a number
|
||||||
|
local axes = { "x", "y", "z" }
|
||||||
|
function intersect.aabb_sphere(aabb, sphere)
|
||||||
|
local dist2 = sphere.radius ^ 2
|
||||||
|
|
||||||
|
for _, axis in ipairs(axes) do
|
||||||
|
local pos = sphere.position[axis]
|
||||||
|
local amin = aabb.min[axis]
|
||||||
|
local amax = aabb.max[axis]
|
||||||
|
|
||||||
|
if pos < amin then
|
||||||
|
dist2 = dist2 - (pos - amin) ^ 2
|
||||||
|
elseif pos > amax then
|
||||||
|
dist2 = dist2 - (pos - amax) ^ 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return dist2 > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- aabb.min is a vec3
|
||||||
|
-- aabb.max is a vec3
|
||||||
|
-- frustum.left is a plane { a, b, c, d }
|
||||||
|
-- frustum.right is a plane { a, b, c, d }
|
||||||
|
-- frustum.bottom is a plane { a, b, c, d }
|
||||||
|
-- frustum.top is a plane { a, b, c, d }
|
||||||
|
-- frustum.near is a plane { a, b, c, d }
|
||||||
|
-- frustum.far is a plane { a, b, c, d }
|
||||||
|
function intersect.aabb_frustum(aabb, frustum)
|
||||||
|
-- Indexed for the 'index trick' later
|
||||||
|
local box = {
|
||||||
|
aabb.min,
|
||||||
|
aabb.max
|
||||||
|
}
|
||||||
|
|
||||||
|
-- We have 6 planes defining the frustum, 5 if infinite.
|
||||||
|
local planes = {
|
||||||
|
frustum.left,
|
||||||
|
frustum.right,
|
||||||
|
frustum.bottom,
|
||||||
|
frustum.top,
|
||||||
|
frustum.near,
|
||||||
|
frustum.far or false
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Skip the last test for infinite projections, it'll never fail.
|
||||||
|
if not planes[6] then
|
||||||
|
table.remove(planes)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #planes do
|
||||||
|
-- This is the current plane
|
||||||
|
local p = planes[i]
|
||||||
|
|
||||||
|
-- p-vertex selection (with the index trick)
|
||||||
|
-- According to the plane normal we can know the
|
||||||
|
-- indices of the positive vertex
|
||||||
|
local px = p.a > 0.0 and 2 or 1
|
||||||
|
local py = p.b > 0.0 and 2 or 1
|
||||||
|
local pz = p.c > 0.0 and 2 or 1
|
||||||
|
|
||||||
|
-- project p-vertex on plane normal
|
||||||
|
-- (How far is p-vertex from the origin)
|
||||||
|
local dot = (p.a * box[px].x) + (p.b * box[py].y) + (p.c * box[pz].z)
|
||||||
|
|
||||||
|
-- Doesn't intersect if it is behind the plane
|
||||||
|
if dot < -p.d then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- outer.min is a vec3
|
||||||
|
-- outer.max is a vec3
|
||||||
|
-- inner.min is a vec3
|
||||||
|
-- inner.max is a vec3
|
||||||
|
function intersect.encapsulate_aabb(outer, inner)
|
||||||
|
return
|
||||||
|
outer.min.x <= inner.min.x and
|
||||||
|
outer.max.x >= inner.max.x and
|
||||||
|
outer.min.y <= inner.min.y and
|
||||||
|
outer.max.y >= inner.max.y and
|
||||||
|
outer.min.z <= inner.min.z and
|
||||||
|
outer.max.z >= inner.max.z
|
||||||
|
end
|
||||||
|
|
||||||
|
-- a.position is a vec3
|
||||||
|
-- a.radius is a number
|
||||||
|
-- b.position is a vec3
|
||||||
|
-- b.radius is a number
|
||||||
|
function intersect.circle_circle(a, b)
|
||||||
|
return a.position:dist(b.position) <= a.radius + b.radius
|
||||||
|
end
|
||||||
|
|
||||||
|
-- a.position is a vec3
|
||||||
|
-- a.radius is a number
|
||||||
|
-- b.position is a vec3
|
||||||
|
-- b.radius is a number
|
||||||
|
function intersect.sphere_sphere(a, b)
|
||||||
|
return intersect.circle_circle(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://realtimecollisiondetection.net/blog/?p=103
|
||||||
|
-- sphere.position is a vec3
|
||||||
|
-- sphere.radius is a number
|
||||||
|
-- triangle[1] is a vec3
|
||||||
|
-- triangle[2] is a vec3
|
||||||
|
-- triangle[3] is a vec3
|
||||||
|
function intersect.sphere_triangle(sphere, triangle)
|
||||||
|
-- Sphere is centered at origin
|
||||||
|
local A = triangle[1] - sphere.position
|
||||||
|
local B = triangle[2] - sphere.position
|
||||||
|
local C = triangle[3] - sphere.position
|
||||||
|
|
||||||
|
-- Compute normal of triangle plane
|
||||||
|
local V = (B - A):cross(C - A)
|
||||||
|
|
||||||
|
-- Test if sphere lies outside triangle plane
|
||||||
|
local rr = sphere.radius * sphere.radius
|
||||||
|
local d = A:dot(V)
|
||||||
|
local e = V:dot(V)
|
||||||
|
local s1 = d * d > rr * e
|
||||||
|
|
||||||
|
-- Test if sphere lies outside triangle vertices
|
||||||
|
local aa = A:dot(A)
|
||||||
|
local ab = A:dot(B)
|
||||||
|
local ac = A:dot(C)
|
||||||
|
local bb = B:dot(B)
|
||||||
|
local bc = B:dot(C)
|
||||||
|
local cc = C:dot(C)
|
||||||
|
|
||||||
|
local s2 = (aa > rr) and (ab > aa) and (ac > aa)
|
||||||
|
local s3 = (bb > rr) and (ab > bb) and (bc > bb)
|
||||||
|
local s4 = (cc > rr) and (ac > cc) and (bc > cc)
|
||||||
|
|
||||||
|
-- Test is sphere lies outside triangle edges
|
||||||
|
local AB = B - A
|
||||||
|
local BC = C - B
|
||||||
|
local CA = A - C
|
||||||
|
|
||||||
|
local d1 = ab - aa
|
||||||
|
local d2 = bc - bb
|
||||||
|
local d3 = ac - cc
|
||||||
|
|
||||||
|
local e1 = AB:dot(AB)
|
||||||
|
local e2 = BC:dot(BC)
|
||||||
|
local e3 = CA:dot(CA)
|
||||||
|
|
||||||
|
local Q1 = A * e1 - AB * d1
|
||||||
|
local Q2 = B * e2 - BC * d2
|
||||||
|
local Q3 = C * e3 - CA * d3
|
||||||
|
|
||||||
|
local QC = C * e1 - Q1
|
||||||
|
local QA = A * e2 - Q2
|
||||||
|
local QB = B * e3 - Q3
|
||||||
|
|
||||||
|
local s5 = (Q1:dot(Q1) > rr * e1 * e1) and (Q1:dot(QC) > 0)
|
||||||
|
local s6 = (Q2:dot(Q2) > rr * e2 * e2) and (Q2:dot(QA) > 0)
|
||||||
|
local s7 = (Q3:dot(Q3) > rr * e3 * e3) and (Q3:dot(QB) > 0)
|
||||||
|
|
||||||
|
-- Return whether or not any of the tests passed
|
||||||
|
return s1 or s2 or s3 or s4 or s5 or s6 or s7
|
||||||
|
end
|
||||||
|
|
||||||
|
-- sphere.position is a vec3
|
||||||
|
-- sphere.radius is a number
|
||||||
|
-- frustum.left is a plane { a, b, c, d }
|
||||||
|
-- frustum.right is a plane { a, b, c, d }
|
||||||
|
-- frustum.bottom is a plane { a, b, c, d }
|
||||||
|
-- frustum.top is a plane { a, b, c, d }
|
||||||
|
-- frustum.near is a plane { a, b, c, d }
|
||||||
|
-- frustum.far is a plane { a, b, c, d }
|
||||||
|
function intersect.sphere_frustum(sphere, frustum)
|
||||||
|
local x, y, z = sphere.position:unpack()
|
||||||
|
local planes = {
|
||||||
|
frustum.left,
|
||||||
|
frustum.right,
|
||||||
|
frustum.bottom,
|
||||||
|
frustum.top,
|
||||||
|
frustum.near
|
||||||
|
}
|
||||||
|
|
||||||
|
if frustum.far then
|
||||||
|
table.insert(planes, frustum.far, 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
local dot
|
||||||
|
for i = 1, #planes do
|
||||||
|
dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
|
||||||
|
|
||||||
|
if dot <= -sphere.radius then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- dot + radius is the distance of the object from the near plane.
|
||||||
|
-- make sure that the near plane is the last test!
|
||||||
|
return dot + sphere.radius
|
||||||
|
end
|
||||||
|
|
||||||
|
function intersect.capsule_capsule(c1, c2)
|
||||||
|
local dist2, p1, p2 = intersect.closest_point_segment_segment(c1.a, c1.b, c2.a, c2.b)
|
||||||
|
local radius = c1.radius + c2.radius
|
||||||
|
|
||||||
|
if dist2 <= radius * radius then
|
||||||
|
return p1, p2
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function intersect.closest_point_segment_segment(p1, p2, p3, p4)
|
||||||
|
local s -- Distance of intersection along segment 1
|
||||||
|
local t -- Distance of intersection along segment 2
|
||||||
|
local c1 -- Collision point on segment 1
|
||||||
|
local c2 -- Collision point on segment 2
|
||||||
|
|
||||||
|
local d1 = p2 - p1 -- Direction of segment 1
|
||||||
|
local d2 = p4 - p3 -- Direction of segment 2
|
||||||
|
local r = p1 - p3
|
||||||
|
local a = d1:dot(d1)
|
||||||
|
local e = d2:dot(d2)
|
||||||
|
local f = d2:dot(r)
|
||||||
|
|
||||||
|
-- Check if both segments degenerate into points
|
||||||
|
if a <= DBL_EPSILON and e <= DBL_EPSILON then
|
||||||
|
s = 0
|
||||||
|
t = 0
|
||||||
|
c1 = p1
|
||||||
|
c2 = p3
|
||||||
|
return (c1 - c2):dot(c1 - c2), s, t, c1, c2
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if segment 1 degenerates into a point
|
||||||
|
if a <= DBL_EPSILON then
|
||||||
|
s = 0
|
||||||
|
t = utils.clamp(f / e, 0, 1)
|
||||||
|
else
|
||||||
|
local c = d1:dot(r)
|
||||||
|
|
||||||
|
-- Check is segment 2 degenerates into a point
|
||||||
|
if e <= DBL_EPSILON then
|
||||||
|
t = 0
|
||||||
|
s = utils.clamp(-c / a, 0, 1)
|
||||||
|
else
|
||||||
|
local b = d1:dot(d2)
|
||||||
|
local denom = a * e - b * b
|
||||||
|
|
||||||
|
if abs(denom) > 0 then
|
||||||
|
s = utils.clamp((b * f - c * e) / denom, 0, 1)
|
||||||
|
else
|
||||||
|
s = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
t = (b * s + f) / e
|
||||||
|
|
||||||
|
if t < 0 then
|
||||||
|
t = 0
|
||||||
|
s = utils.clamp(-c / a, 0, 1)
|
||||||
|
elseif t > 1 then
|
||||||
|
t = 1
|
||||||
|
s = utils.clamp((b - c) / a, 0, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
c1 = p1 + d1 * s
|
||||||
|
c2 = p3 + d2 * t
|
||||||
|
|
||||||
|
return (c1 - c2):dot(c1 - c2), c1, c2, s, t
|
||||||
|
end
|
||||||
|
|
||||||
|
return intersect
|
||||||
943
libs/cpml/mat4.lua
Normal file
@@ -0,0 +1,943 @@
|
|||||||
|
--- double 4x4, 1-based, column major matrices
|
||||||
|
-- @module mat4
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local constants = require(modules .. "constants")
|
||||||
|
local vec2 = require(modules .. "vec2")
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local quat = require(modules .. "quat")
|
||||||
|
local utils = require(modules .. "utils")
|
||||||
|
local precond = require(modules .. "_private_precond")
|
||||||
|
local private = require(modules .. "_private_utils")
|
||||||
|
local sqrt = math.sqrt
|
||||||
|
local cos = math.cos
|
||||||
|
local sin = math.sin
|
||||||
|
local tan = math.tan
|
||||||
|
local rad = math.rad
|
||||||
|
local mat4 = {}
|
||||||
|
local mat4_mt = {}
|
||||||
|
|
||||||
|
-- Private constructor.
|
||||||
|
local function new(m)
|
||||||
|
m = m or {
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0
|
||||||
|
}
|
||||||
|
m._m = m
|
||||||
|
return setmetatable(m, mat4_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert matrix into identity
|
||||||
|
local function identity(m)
|
||||||
|
m[1], m[2], m[3], m[4] = 1, 0, 0, 0
|
||||||
|
m[5], m[6], m[7], m[8] = 0, 1, 0, 0
|
||||||
|
m[9], m[10], m[11], m[12] = 0, 0, 1, 0
|
||||||
|
m[13], m[14], m[15], m[16] = 0, 0, 0, 1
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||||
|
local status, ffi
|
||||||
|
if type(jit) == "table" and jit.status() then
|
||||||
|
-- status, ffi = pcall(require, "ffi")
|
||||||
|
if status then
|
||||||
|
ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;"
|
||||||
|
new = ffi.typeof("cpml_mat4")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Statically allocate a temporary variable used in some of our functions.
|
||||||
|
local tmp = new()
|
||||||
|
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||||
|
local tv4 = { 0, 0, 0, 0 }
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param a Can be of four types: </br>
|
||||||
|
-- table Length 16 (4x4 matrix)
|
||||||
|
-- table Length 9 (3x3 matrix)
|
||||||
|
-- table Length 4 (4 vec4s)
|
||||||
|
-- nil
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.new(a)
|
||||||
|
local out = new()
|
||||||
|
|
||||||
|
-- 4x4 matrix
|
||||||
|
if type(a) == "table" and #a == 16 then
|
||||||
|
for i = 1, 16 do
|
||||||
|
out[i] = tonumber(a[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 3x3 matrix
|
||||||
|
elseif type(a) == "table" and #a == 9 then
|
||||||
|
out[1], out[2], out[3] = a[1], a[2], a[3]
|
||||||
|
out[5], out[6], out[7] = a[4], a[5], a[6]
|
||||||
|
out[9], out[10], out[11] = a[7], a[8], a[9]
|
||||||
|
out[16] = 1
|
||||||
|
|
||||||
|
-- 4 vec4s
|
||||||
|
elseif type(a) == "table" and type(a[1]) == "table" then
|
||||||
|
local idx = 1
|
||||||
|
for i = 1, 4 do
|
||||||
|
for j = 1, 4 do
|
||||||
|
out[idx] = a[i][j]
|
||||||
|
idx = idx + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- nil
|
||||||
|
else
|
||||||
|
out[1] = 1
|
||||||
|
out[6] = 1
|
||||||
|
out[11] = 1
|
||||||
|
out[16] = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create an identity matrix.
|
||||||
|
-- @tparam mat4 a Matrix to overwrite
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.identity(a)
|
||||||
|
return identity(a or new())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a matrix from an angle/axis pair.
|
||||||
|
-- @tparam number angle Angle of rotation
|
||||||
|
-- @tparam vec3 axis Axis of rotation
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_angle_axis(angle, axis)
|
||||||
|
local l = axis:len()
|
||||||
|
if l == 0 then
|
||||||
|
return new()
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, y, z = axis.x / l, axis.y / l, axis.z / l
|
||||||
|
local c = cos(angle)
|
||||||
|
local s = sin(angle)
|
||||||
|
|
||||||
|
return new {
|
||||||
|
x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
|
||||||
|
x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0,
|
||||||
|
x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a matrix from a quaternion.
|
||||||
|
-- @tparam quat q Rotation quaternion
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_quaternion(q)
|
||||||
|
return mat4.from_angle_axis(q:to_angle_axis())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a matrix from a direction/up pair.
|
||||||
|
-- @tparam vec3 direction Vector direction
|
||||||
|
-- @tparam vec3 up Up direction
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_direction(direction, up)
|
||||||
|
local forward = vec3.normalize(direction)
|
||||||
|
local side = vec3.cross(forward, up):normalize()
|
||||||
|
local new_up = vec3.cross(side, forward):normalize()
|
||||||
|
|
||||||
|
local out = new()
|
||||||
|
out[1] = side.x
|
||||||
|
out[5] = side.y
|
||||||
|
out[9] = side.z
|
||||||
|
out[2] = new_up.x
|
||||||
|
out[6] = new_up.y
|
||||||
|
out[10] = new_up.z
|
||||||
|
out[3] = forward.x
|
||||||
|
out[7] = forward.y
|
||||||
|
out[11] = forward.z
|
||||||
|
out[16] = 1
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a matrix from a transform.
|
||||||
|
-- @tparam vec3 trans Translation vector
|
||||||
|
-- @tparam quat rot Rotation quaternion
|
||||||
|
-- @tparam vec3 scale Scale vector
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_transform(trans, rot, scale)
|
||||||
|
local rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
|
||||||
|
|
||||||
|
local sm = new {
|
||||||
|
scale.x, 0, 0, 0,
|
||||||
|
0, scale.y, 0, 0,
|
||||||
|
0, 0, scale.z, 0,
|
||||||
|
0, 0, 0, 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
local rm = new {
|
||||||
|
1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0,
|
||||||
|
2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0,
|
||||||
|
2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
local rsm = rm * sm
|
||||||
|
|
||||||
|
rsm[13] = trans.x
|
||||||
|
rsm[14] = trans.y
|
||||||
|
rsm[15] = trans.z
|
||||||
|
|
||||||
|
return rsm
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create matrix from orthogonal.
|
||||||
|
-- @tparam number left
|
||||||
|
-- @tparam number right
|
||||||
|
-- @tparam number top
|
||||||
|
-- @tparam number bottom
|
||||||
|
-- @tparam number near
|
||||||
|
-- @tparam number far
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_ortho(left, right, top, bottom, near, far)
|
||||||
|
local out = new()
|
||||||
|
out[1] = 2 / (right - left)
|
||||||
|
out[6] = 2 / (top - bottom)
|
||||||
|
out[11] = -2 / (far - near)
|
||||||
|
out[13] = -((right + left) / (right - left))
|
||||||
|
out[14] = -((top + bottom) / (top - bottom))
|
||||||
|
out[15] = -((far + near) / (far - near))
|
||||||
|
out[16] = 1
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create matrix from perspective.
|
||||||
|
-- @tparam number fovy Field of view
|
||||||
|
-- @tparam number aspect Aspect ratio
|
||||||
|
-- @tparam number near Near plane
|
||||||
|
-- @tparam number far Far plane
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_perspective(fovy, aspect, near, far)
|
||||||
|
assert(aspect ~= 0)
|
||||||
|
assert(near ~= far)
|
||||||
|
|
||||||
|
local t = tan(rad(fovy) / 2)
|
||||||
|
local out = new()
|
||||||
|
out[1] = 1 / (t * aspect)
|
||||||
|
out[6] = 1 / t
|
||||||
|
out[11] = -(far + near) / (far - near)
|
||||||
|
out[12] = -1
|
||||||
|
out[15] = -(2 * far * near) / (far - near)
|
||||||
|
out[16] = 0
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Adapted from the Oculus SDK.
|
||||||
|
--- Create matrix from HMD perspective.
|
||||||
|
-- @tparam number tanHalfFov Tangent of half of the field of view
|
||||||
|
-- @tparam number zNear Near plane
|
||||||
|
-- @tparam number zFar Far plane
|
||||||
|
-- @tparam boolean flipZ Z axis is flipped or not
|
||||||
|
-- @tparam boolean farAtInfinity Far plane is infinite or not
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
|
||||||
|
-- CPML is right-handed and intended for GL, so these don't need to be arguments.
|
||||||
|
local rightHanded = true
|
||||||
|
local isOpenGL = true
|
||||||
|
|
||||||
|
local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
|
||||||
|
local x_scale = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
|
||||||
|
local x_offset = (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
|
||||||
|
local y_scale = 2 / (tanHalfFov.UpTan + tanHalfFov.DownTan )
|
||||||
|
local y_offset = (tanHalfFov.UpTan - tanHalfFov.DownTan ) * y_scale * 0.5
|
||||||
|
|
||||||
|
local result = {
|
||||||
|
Scale = vec2(x_scale, y_scale),
|
||||||
|
Offset = vec2(x_offset, y_offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Hey - why is that Y.Offset negated?
|
||||||
|
-- It's because a projection matrix transforms from world coords with Y=up,
|
||||||
|
-- whereas this is from NDC which is Y=down.
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
if not flipZ and farAtInfinity then
|
||||||
|
print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
|
||||||
|
farAtInfinity = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A projection matrix is very like a scaling from NDC, so we can start with that.
|
||||||
|
local scaleAndOffset = CreateNDCScaleAndOffsetFromFov(tanHalfFov)
|
||||||
|
local handednessScale = rightHanded and -1.0 or 1.0
|
||||||
|
local projection = new()
|
||||||
|
|
||||||
|
-- Produces X result, mapping clip edges to [-w,+w]
|
||||||
|
projection[1] = scaleAndOffset.Scale.x
|
||||||
|
projection[2] = 0
|
||||||
|
projection[3] = handednessScale * scaleAndOffset.Offset.x
|
||||||
|
projection[4] = 0
|
||||||
|
|
||||||
|
-- Produces Y result, mapping clip edges to [-w,+w]
|
||||||
|
-- Hey - why is that YOffset negated?
|
||||||
|
-- It's because a projection matrix transforms from world coords with Y=up,
|
||||||
|
-- whereas this is derived from an NDC scaling, which is Y=down.
|
||||||
|
projection[5] = 0
|
||||||
|
projection[6] = scaleAndOffset.Scale.y
|
||||||
|
projection[7] = handednessScale * -scaleAndOffset.Offset.y
|
||||||
|
projection[8] = 0
|
||||||
|
|
||||||
|
-- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
|
||||||
|
-- We'll just use some defaults for now.
|
||||||
|
projection[9] = 0
|
||||||
|
projection[10] = 0
|
||||||
|
|
||||||
|
if farAtInfinity then
|
||||||
|
if isOpenGL then
|
||||||
|
-- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
|
||||||
|
projection[11] = -handednessScale
|
||||||
|
projection[12] = 2.0 * zNear
|
||||||
|
else
|
||||||
|
projection[11] = 0
|
||||||
|
projection[12] = zNear
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if isOpenGL then
|
||||||
|
-- Clip range is [-w,+w], so 0 is at the middle of the range.
|
||||||
|
projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
|
||||||
|
projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
|
||||||
|
else
|
||||||
|
-- Clip range is [0,+w], so 0 is at the start of the range.
|
||||||
|
projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
|
||||||
|
projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Produces W result (= Z in)
|
||||||
|
projection[13] = 0
|
||||||
|
projection[14] = 0
|
||||||
|
projection[15] = handednessScale
|
||||||
|
projection[16] = 0
|
||||||
|
|
||||||
|
return projection:transpose(projection)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone a matrix.
|
||||||
|
-- @tparam mat4 a Matrix to clone
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.clone(a)
|
||||||
|
return new(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mul_internal(out, a, b)
|
||||||
|
tm4[1] = b[1] * a[1] + b[2] * a[5] + b[3] * a[9] + b[4] * a[13]
|
||||||
|
tm4[2] = b[1] * a[2] + b[2] * a[6] + b[3] * a[10] + b[4] * a[14]
|
||||||
|
tm4[3] = b[1] * a[3] + b[2] * a[7] + b[3] * a[11] + b[4] * a[15]
|
||||||
|
tm4[4] = b[1] * a[4] + b[2] * a[8] + b[3] * a[12] + b[4] * a[16]
|
||||||
|
tm4[5] = b[5] * a[1] + b[6] * a[5] + b[7] * a[9] + b[8] * a[13]
|
||||||
|
tm4[6] = b[5] * a[2] + b[6] * a[6] + b[7] * a[10] + b[8] * a[14]
|
||||||
|
tm4[7] = b[5] * a[3] + b[6] * a[7] + b[7] * a[11] + b[8] * a[15]
|
||||||
|
tm4[8] = b[5] * a[4] + b[6] * a[8] + b[7] * a[12] + b[8] * a[16]
|
||||||
|
tm4[9] = b[9] * a[1] + b[10] * a[5] + b[11] * a[9] + b[12] * a[13]
|
||||||
|
tm4[10] = b[9] * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
|
||||||
|
tm4[11] = b[9] * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
|
||||||
|
tm4[12] = b[9] * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
|
||||||
|
tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9] + b[16] * a[13]
|
||||||
|
tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
|
||||||
|
tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
|
||||||
|
tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
out[i] = tm4[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply N matrices.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 or {mat4, ...} left hand operand(s)
|
||||||
|
-- @tparam mat4 right hand operand if a is not table
|
||||||
|
-- @treturn mat4 out multiplied matrix result
|
||||||
|
function mat4.mul(out, a, b)
|
||||||
|
if mat4.is_mat4(a) then
|
||||||
|
mul_internal(out, a, b)
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
if #a == 0 then
|
||||||
|
identity(out)
|
||||||
|
elseif #a == 1 then
|
||||||
|
-- only one matrix, just copy
|
||||||
|
for i = 1, 16 do
|
||||||
|
out[i] = a[1][i]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local ma = a[1]
|
||||||
|
local mb = a[2]
|
||||||
|
for i = 2, #a do
|
||||||
|
mul_internal(out, ma, mb)
|
||||||
|
ma = out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a matrix and a vec3, with perspective division.
|
||||||
|
-- This function uses an implicit 1 for the fourth component.
|
||||||
|
-- @tparam vec3 out vec3 to store the result
|
||||||
|
-- @tparam mat4 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function mat4.mul_vec3_perspective(out, a, b)
|
||||||
|
local v4x = b.x * a[1] + b.y * a[5] + b.z * a[9] + a[13]
|
||||||
|
local v4y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14]
|
||||||
|
local v4z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15]
|
||||||
|
local v4w = b.x * a[4] + b.y * a[8] + b.z * a[12] + a[16]
|
||||||
|
local inv_w = 0
|
||||||
|
if v4w ~= 0 then
|
||||||
|
inv_w = utils.sign(v4w) / v4w
|
||||||
|
end
|
||||||
|
out.x = v4x * inv_w
|
||||||
|
out.y = v4y * inv_w
|
||||||
|
out.z = v4z * inv_w
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a matrix and a vec4.
|
||||||
|
-- @tparam table out table to store the result
|
||||||
|
-- @tparam mat4 a Left hand operand
|
||||||
|
-- @tparam table b Right hand operand
|
||||||
|
-- @treturn vec4 out
|
||||||
|
function mat4.mul_vec4(out, a, b)
|
||||||
|
tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9] + b[4] * a[13]
|
||||||
|
tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
|
||||||
|
tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
|
||||||
|
tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
|
||||||
|
|
||||||
|
for i = 1, 4 do
|
||||||
|
out[i] = tv4[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Invert a matrix.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 a Matrix to invert
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.invert(out, a)
|
||||||
|
tm4[1] = a[6] * a[11] * a[16] - a[6] * a[12] * a[15] - a[10] * a[7] * a[16] + a[10] * a[8] * a[15] + a[14] * a[7] * a[12] - a[14] * a[8] * a[11]
|
||||||
|
tm4[2] = -a[2] * a[11] * a[16] + a[2] * a[12] * a[15] + a[10] * a[3] * a[16] - a[10] * a[4] * a[15] - a[14] * a[3] * a[12] + a[14] * a[4] * a[11]
|
||||||
|
tm4[3] = a[2] * a[7] * a[16] - a[2] * a[8] * a[15] - a[6] * a[3] * a[16] + a[6] * a[4] * a[15] + a[14] * a[3] * a[8] - a[14] * a[4] * a[7]
|
||||||
|
tm4[4] = -a[2] * a[7] * a[12] + a[2] * a[8] * a[11] + a[6] * a[3] * a[12] - a[6] * a[4] * a[11] - a[10] * a[3] * a[8] + a[10] * a[4] * a[7]
|
||||||
|
tm4[5] = -a[5] * a[11] * a[16] + a[5] * a[12] * a[15] + a[9] * a[7] * a[16] - a[9] * a[8] * a[15] - a[13] * a[7] * a[12] + a[13] * a[8] * a[11]
|
||||||
|
tm4[6] = a[1] * a[11] * a[16] - a[1] * a[12] * a[15] - a[9] * a[3] * a[16] + a[9] * a[4] * a[15] + a[13] * a[3] * a[12] - a[13] * a[4] * a[11]
|
||||||
|
tm4[7] = -a[1] * a[7] * a[16] + a[1] * a[8] * a[15] + a[5] * a[3] * a[16] - a[5] * a[4] * a[15] - a[13] * a[3] * a[8] + a[13] * a[4] * a[7]
|
||||||
|
tm4[8] = a[1] * a[7] * a[12] - a[1] * a[8] * a[11] - a[5] * a[3] * a[12] + a[5] * a[4] * a[11] + a[9] * a[3] * a[8] - a[9] * a[4] * a[7]
|
||||||
|
tm4[9] = a[5] * a[10] * a[16] - a[5] * a[12] * a[14] - a[9] * a[6] * a[16] + a[9] * a[8] * a[14] + a[13] * a[6] * a[12] - a[13] * a[8] * a[10]
|
||||||
|
tm4[10] = -a[1] * a[10] * a[16] + a[1] * a[12] * a[14] + a[9] * a[2] * a[16] - a[9] * a[4] * a[14] - a[13] * a[2] * a[12] + a[13] * a[4] * a[10]
|
||||||
|
tm4[11] = a[1] * a[6] * a[16] - a[1] * a[8] * a[14] - a[5] * a[2] * a[16] + a[5] * a[4] * a[14] + a[13] * a[2] * a[8] - a[13] * a[4] * a[6]
|
||||||
|
tm4[12] = -a[1] * a[6] * a[12] + a[1] * a[8] * a[10] + a[5] * a[2] * a[12] - a[5] * a[4] * a[10] - a[9] * a[2] * a[8] + a[9] * a[4] * a[6]
|
||||||
|
tm4[13] = -a[5] * a[10] * a[15] + a[5] * a[11] * a[14] + a[9] * a[6] * a[15] - a[9] * a[7] * a[14] - a[13] * a[6] * a[11] + a[13] * a[7] * a[10]
|
||||||
|
tm4[14] = a[1] * a[10] * a[15] - a[1] * a[11] * a[14] - a[9] * a[2] * a[15] + a[9] * a[3] * a[14] + a[13] * a[2] * a[11] - a[13] * a[3] * a[10]
|
||||||
|
tm4[15] = -a[1] * a[6] * a[15] + a[1] * a[7] * a[14] + a[5] * a[2] * a[15] - a[5] * a[3] * a[14] - a[13] * a[2] * a[7] + a[13] * a[3] * a[6]
|
||||||
|
tm4[16] = a[1] * a[6] * a[11] - a[1] * a[7] * a[10] - a[5] * a[2] * a[11] + a[5] * a[3] * a[10] + a[9] * a[2] * a[7] - a[9] * a[3] * a[6]
|
||||||
|
|
||||||
|
local det = a[1] * tm4[1] + a[2] * tm4[5] + a[3] * tm4[9] + a[4] * tm4[13]
|
||||||
|
|
||||||
|
if det == 0 then return a end
|
||||||
|
|
||||||
|
det = 1 / det
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
out[i] = tm4[i] * det
|
||||||
|
end
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scale a matrix.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 a Matrix to scale
|
||||||
|
-- @tparam vec3 s Scalar
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.scale(out, a, s)
|
||||||
|
identity(tmp)
|
||||||
|
tmp[1] = s.x
|
||||||
|
tmp[6] = s.y
|
||||||
|
tmp[11] = s.z
|
||||||
|
|
||||||
|
return out:mul(tmp, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rotate a matrix.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 a Matrix to rotate
|
||||||
|
-- @tparam number angle Angle to rotate by (in radians)
|
||||||
|
-- @tparam vec3 axis Axis to rotate on
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.rotate(out, a, angle, axis)
|
||||||
|
if type(angle) == "table" or type(angle) == "cdata" then
|
||||||
|
angle, axis = angle:to_angle_axis()
|
||||||
|
end
|
||||||
|
|
||||||
|
local l = axis:len()
|
||||||
|
|
||||||
|
if l == 0 then
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, y, z = axis.x / l, axis.y / l, axis.z / l
|
||||||
|
local c = cos(angle)
|
||||||
|
local s = sin(angle)
|
||||||
|
|
||||||
|
identity(tmp)
|
||||||
|
tmp[1] = x * x * (1 - c) + c
|
||||||
|
tmp[2] = y * x * (1 - c) + z * s
|
||||||
|
tmp[3] = x * z * (1 - c) - y * s
|
||||||
|
tmp[5] = x * y * (1 - c) - z * s
|
||||||
|
tmp[6] = y * y * (1 - c) + c
|
||||||
|
tmp[7] = y * z * (1 - c) + x * s
|
||||||
|
tmp[9] = x * z * (1 - c) + y * s
|
||||||
|
tmp[10] = y * z * (1 - c) - x * s
|
||||||
|
tmp[11] = z * z * (1 - c) + c
|
||||||
|
|
||||||
|
return out:mul(tmp, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Translate a matrix.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 a Matrix to translate
|
||||||
|
-- @tparam vec3 t Translation vector
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.translate(out, a, t)
|
||||||
|
identity(tmp)
|
||||||
|
tmp[13] = t.x
|
||||||
|
tmp[14] = t.y
|
||||||
|
tmp[15] = t.z
|
||||||
|
|
||||||
|
return out:mul(tmp, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Shear a matrix.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 a Matrix to translate
|
||||||
|
-- @tparam number yx
|
||||||
|
-- @tparam number zx
|
||||||
|
-- @tparam number xy
|
||||||
|
-- @tparam number zy
|
||||||
|
-- @tparam number xz
|
||||||
|
-- @tparam number yz
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
|
||||||
|
identity(tmp)
|
||||||
|
tmp[2] = yx or 0
|
||||||
|
tmp[3] = zx or 0
|
||||||
|
tmp[5] = xy or 0
|
||||||
|
tmp[7] = zy or 0
|
||||||
|
tmp[9] = xz or 0
|
||||||
|
tmp[10] = yz or 0
|
||||||
|
|
||||||
|
return out:mul(tmp, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Reflect a matrix across a plane.
|
||||||
|
-- @tparam mat4 Matrix to store the result
|
||||||
|
-- @tparam a Matrix to reflect
|
||||||
|
-- @tparam vec3 position A point on the plane
|
||||||
|
-- @tparam vec3 normal The (normalized!) normal vector of the plane
|
||||||
|
function mat4.reflect(out, a, position, normal)
|
||||||
|
local nx, ny, nz = normal:unpack()
|
||||||
|
local d = -position:dot(normal)
|
||||||
|
tmp[1] = 1 - 2 * nx ^ 2
|
||||||
|
tmp[2] = 2 * nx * ny
|
||||||
|
tmp[3] = -2 * nx * nz
|
||||||
|
tmp[4] = 0
|
||||||
|
tmp[5] = -2 * nx * ny
|
||||||
|
tmp[6] = 1 - 2 * ny ^ 2
|
||||||
|
tmp[7] = -2 * ny * nz
|
||||||
|
tmp[8] = 0
|
||||||
|
tmp[9] = -2 * nx * nz
|
||||||
|
tmp[10] = -2 * ny * nz
|
||||||
|
tmp[11] = 1 - 2 * nz ^ 2
|
||||||
|
tmp[12] = 0
|
||||||
|
tmp[13] = -2 * nx * d
|
||||||
|
tmp[14] = -2 * ny * d
|
||||||
|
tmp[15] = -2 * nz * d
|
||||||
|
tmp[16] = 1
|
||||||
|
|
||||||
|
return out:mul(tmp, a)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Transform matrix to look at a point.
|
||||||
|
-- @tparam mat4 out Matrix to store result
|
||||||
|
-- @tparam vec3 eye Location of viewer's view plane
|
||||||
|
-- @tparam vec3 center Location of object to view
|
||||||
|
-- @tparam vec3 up Up direction
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.look_at(out, eye, look_at, up)
|
||||||
|
local z_axis = (eye - look_at):normalize()
|
||||||
|
local x_axis = up:cross(z_axis):normalize()
|
||||||
|
local y_axis = z_axis:cross(x_axis)
|
||||||
|
out[1] = x_axis.x
|
||||||
|
out[2] = y_axis.x
|
||||||
|
out[3] = z_axis.x
|
||||||
|
out[4] = 0
|
||||||
|
out[5] = x_axis.y
|
||||||
|
out[6] = y_axis.y
|
||||||
|
out[7] = z_axis.y
|
||||||
|
out[8] = 0
|
||||||
|
out[9] = x_axis.z
|
||||||
|
out[10] = y_axis.z
|
||||||
|
out[11] = z_axis.z
|
||||||
|
out[12] = 0
|
||||||
|
out[13] = -out[ 1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
|
||||||
|
out[14] = -out[ 2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
|
||||||
|
out[15] = -out[ 3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
|
||||||
|
out[16] = -out[ 4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Transform matrix to target a point.
|
||||||
|
-- @tparam mat4 out Matrix to store result
|
||||||
|
-- @tparam vec3 eye Location of viewer's view plane
|
||||||
|
-- @tparam vec3 center Location of object to view
|
||||||
|
-- @tparam vec3 up Up direction
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.target(out, from, to, up)
|
||||||
|
local z_axis = (from - to):normalize()
|
||||||
|
local x_axis = up:cross(z_axis):normalize()
|
||||||
|
local y_axis = z_axis:cross(x_axis)
|
||||||
|
out[1] = x_axis.x
|
||||||
|
out[2] = x_axis.y
|
||||||
|
out[3] = x_axis.z
|
||||||
|
out[4] = 0
|
||||||
|
out[5] = y_axis.x
|
||||||
|
out[6] = y_axis.y
|
||||||
|
out[7] = y_axis.z
|
||||||
|
out[8] = 0
|
||||||
|
out[9] = z_axis.x
|
||||||
|
out[10] = z_axis.y
|
||||||
|
out[11] = z_axis.z
|
||||||
|
out[12] = 0
|
||||||
|
out[13] = from.x
|
||||||
|
out[14] = from.y
|
||||||
|
out[15] = from.z
|
||||||
|
out[16] = 1
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Transpose a matrix.
|
||||||
|
-- @tparam mat4 out Matrix to store the result
|
||||||
|
-- @tparam mat4 a Matrix to transpose
|
||||||
|
-- @treturn mat4 out
|
||||||
|
function mat4.transpose(out, a)
|
||||||
|
tm4[1] = a[1]
|
||||||
|
tm4[2] = a[5]
|
||||||
|
tm4[3] = a[9]
|
||||||
|
tm4[4] = a[13]
|
||||||
|
tm4[5] = a[2]
|
||||||
|
tm4[6] = a[6]
|
||||||
|
tm4[7] = a[10]
|
||||||
|
tm4[8] = a[14]
|
||||||
|
tm4[9] = a[3]
|
||||||
|
tm4[10] = a[7]
|
||||||
|
tm4[11] = a[11]
|
||||||
|
tm4[12] = a[15]
|
||||||
|
tm4[13] = a[4]
|
||||||
|
tm4[14] = a[8]
|
||||||
|
tm4[15] = a[12]
|
||||||
|
tm4[16] = a[16]
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
out[i] = tm4[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Project a point into screen space
|
||||||
|
-- @tparam vec3 obj Object position in world space
|
||||||
|
-- @tparam mat4 mvp Projection matrix
|
||||||
|
-- @tparam table viewport XYWH of viewport
|
||||||
|
-- @treturn vec3 win
|
||||||
|
function mat4.project(obj, mvp, viewport)
|
||||||
|
local point = mat4.mul_vec3_perspective(vec3(), mvp, obj)
|
||||||
|
point.x = point.x * 0.5 + 0.5
|
||||||
|
point.y = point.y * 0.5 + 0.5
|
||||||
|
point.z = point.z * 0.5 + 0.5
|
||||||
|
point.x = point.x * viewport[3] + viewport[1]
|
||||||
|
point.y = point.y * viewport[4] + viewport[2]
|
||||||
|
return point
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unproject a point from screen space to world space.
|
||||||
|
-- @tparam vec3 win Object position in screen space
|
||||||
|
-- @tparam mat4 mvp Projection matrix
|
||||||
|
-- @tparam table viewport XYWH of viewport
|
||||||
|
-- @treturn vec3 obj
|
||||||
|
function mat4.unproject(win, mvp, viewport)
|
||||||
|
local point = vec3.clone(win)
|
||||||
|
|
||||||
|
-- 0..n -> 0..1
|
||||||
|
point.x = (point.x - viewport[1]) / viewport[3]
|
||||||
|
point.y = (point.y - viewport[2]) / viewport[4]
|
||||||
|
|
||||||
|
-- 0..1 -> -1..1
|
||||||
|
point.x = point.x * 2 - 1
|
||||||
|
point.y = point.y * 2 - 1
|
||||||
|
point.z = point.z * 2 - 1
|
||||||
|
|
||||||
|
return mat4.mul_vec3_perspective(point, tmp:invert(mvp), point)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a mat4.
|
||||||
|
-- @tparam mat4 a Matrix to be tested
|
||||||
|
-- @treturn boolean is_mat4
|
||||||
|
function mat4.is_mat4(a)
|
||||||
|
if type(a) == "cdata" then
|
||||||
|
return ffi.istype("cpml_mat4", a)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(a) ~= "table" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
if type(a[i]) ~= "number" then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return whether any component is NaN
|
||||||
|
-- @tparam mat4 a Matrix to be tested
|
||||||
|
-- @treturn boolean if any component is NaN
|
||||||
|
function vec2.has_nan(a)
|
||||||
|
for i = 1, 16 do
|
||||||
|
if private.is_nan(a[i]) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam mat4 a Matrix to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function mat4.to_string(a)
|
||||||
|
local str = "[ "
|
||||||
|
for i = 1, 16 do
|
||||||
|
str = str .. string.format("%+0.3f", a[i])
|
||||||
|
if i < 16 then
|
||||||
|
str = str .. ", "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
str = str .. " ]"
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert a matrix to row vec4s.
|
||||||
|
-- @tparam mat4 a Matrix to be converted
|
||||||
|
-- @treturn table vec4s
|
||||||
|
function mat4.to_vec4s(a)
|
||||||
|
return {
|
||||||
|
{ a[1], a[2], a[3], a[4] },
|
||||||
|
{ a[5], a[6], a[7], a[8] },
|
||||||
|
{ a[9], a[10], a[11], a[12] },
|
||||||
|
{ a[13], a[14], a[15], a[16] }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert a matrix to col vec4s.
|
||||||
|
-- @tparam mat4 a Matrix to be converted
|
||||||
|
-- @treturn table vec4s
|
||||||
|
function mat4.to_vec4s_cols(a)
|
||||||
|
return {
|
||||||
|
{ a[1], a[5], a[9], a[13] },
|
||||||
|
{ a[2], a[6], a[10], a[14] },
|
||||||
|
{ a[3], a[7], a[11], a[15] },
|
||||||
|
{ a[4], a[8], a[12], a[16] }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
|
||||||
|
--- Convert a matrix to a quaternion.
|
||||||
|
-- @tparam mat4 a Matrix to be converted
|
||||||
|
-- @treturn quat out
|
||||||
|
function mat4.to_quat(a)
|
||||||
|
identity(tmp):transpose(a)
|
||||||
|
|
||||||
|
local w = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
|
||||||
|
local scale = w * 4
|
||||||
|
local q = quat.new(
|
||||||
|
tmp[10] - tmp[7] / scale,
|
||||||
|
tmp[3] - tmp[9] / scale,
|
||||||
|
tmp[5] - tmp[2] / scale,
|
||||||
|
w
|
||||||
|
)
|
||||||
|
|
||||||
|
return q:normalize(q)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
|
||||||
|
--- Convert a matrix to a frustum.
|
||||||
|
-- @tparam mat4 a Matrix to be converted (projection * view)
|
||||||
|
-- @tparam boolean infinite Infinite removes the far plane
|
||||||
|
-- @treturn frustum out
|
||||||
|
function mat4.to_frustum(a, infinite)
|
||||||
|
local t
|
||||||
|
local frustum = {}
|
||||||
|
|
||||||
|
-- Extract the LEFT plane
|
||||||
|
frustum.left = {}
|
||||||
|
frustum.left.a = a[4] + a[1]
|
||||||
|
frustum.left.b = a[8] + a[5]
|
||||||
|
frustum.left.c = a[12] + a[9]
|
||||||
|
frustum.left.d = a[16] + a[13]
|
||||||
|
|
||||||
|
-- Normalize the result
|
||||||
|
t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
|
||||||
|
frustum.left.a = frustum.left.a / t
|
||||||
|
frustum.left.b = frustum.left.b / t
|
||||||
|
frustum.left.c = frustum.left.c / t
|
||||||
|
frustum.left.d = frustum.left.d / t
|
||||||
|
|
||||||
|
-- Extract the RIGHT plane
|
||||||
|
frustum.right = {}
|
||||||
|
frustum.right.a = a[4] - a[1]
|
||||||
|
frustum.right.b = a[8] - a[5]
|
||||||
|
frustum.right.c = a[12] - a[9]
|
||||||
|
frustum.right.d = a[16] - a[13]
|
||||||
|
|
||||||
|
-- Normalize the result
|
||||||
|
t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
|
||||||
|
frustum.right.a = frustum.right.a / t
|
||||||
|
frustum.right.b = frustum.right.b / t
|
||||||
|
frustum.right.c = frustum.right.c / t
|
||||||
|
frustum.right.d = frustum.right.d / t
|
||||||
|
|
||||||
|
-- Extract the BOTTOM plane
|
||||||
|
frustum.bottom = {}
|
||||||
|
frustum.bottom.a = a[4] + a[2]
|
||||||
|
frustum.bottom.b = a[8] + a[6]
|
||||||
|
frustum.bottom.c = a[12] + a[10]
|
||||||
|
frustum.bottom.d = a[16] + a[14]
|
||||||
|
|
||||||
|
-- Normalize the result
|
||||||
|
t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
|
||||||
|
frustum.bottom.a = frustum.bottom.a / t
|
||||||
|
frustum.bottom.b = frustum.bottom.b / t
|
||||||
|
frustum.bottom.c = frustum.bottom.c / t
|
||||||
|
frustum.bottom.d = frustum.bottom.d / t
|
||||||
|
|
||||||
|
-- Extract the TOP plane
|
||||||
|
frustum.top = {}
|
||||||
|
frustum.top.a = a[4] - a[2]
|
||||||
|
frustum.top.b = a[8] - a[6]
|
||||||
|
frustum.top.c = a[12] - a[10]
|
||||||
|
frustum.top.d = a[16] - a[14]
|
||||||
|
|
||||||
|
-- Normalize the result
|
||||||
|
t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
|
||||||
|
frustum.top.a = frustum.top.a / t
|
||||||
|
frustum.top.b = frustum.top.b / t
|
||||||
|
frustum.top.c = frustum.top.c / t
|
||||||
|
frustum.top.d = frustum.top.d / t
|
||||||
|
|
||||||
|
-- Extract the NEAR plane
|
||||||
|
frustum.near = {}
|
||||||
|
frustum.near.a = a[4] + a[3]
|
||||||
|
frustum.near.b = a[8] + a[7]
|
||||||
|
frustum.near.c = a[12] + a[11]
|
||||||
|
frustum.near.d = a[16] + a[15]
|
||||||
|
|
||||||
|
-- Normalize the result
|
||||||
|
t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
|
||||||
|
frustum.near.a = frustum.near.a / t
|
||||||
|
frustum.near.b = frustum.near.b / t
|
||||||
|
frustum.near.c = frustum.near.c / t
|
||||||
|
frustum.near.d = frustum.near.d / t
|
||||||
|
|
||||||
|
if not infinite then
|
||||||
|
-- Extract the FAR plane
|
||||||
|
frustum.far = {}
|
||||||
|
frustum.far.a = a[4] - a[3]
|
||||||
|
frustum.far.b = a[8] - a[7]
|
||||||
|
frustum.far.c = a[12] - a[11]
|
||||||
|
frustum.far.d = a[16] - a[15]
|
||||||
|
|
||||||
|
-- Normalize the result
|
||||||
|
t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
|
||||||
|
frustum.far.a = frustum.far.a / t
|
||||||
|
frustum.far.b = frustum.far.b / t
|
||||||
|
frustum.far.c = frustum.far.c / t
|
||||||
|
frustum.far.d = frustum.far.d / t
|
||||||
|
end
|
||||||
|
|
||||||
|
return frustum
|
||||||
|
end
|
||||||
|
|
||||||
|
function mat4_mt.__index(t, k)
|
||||||
|
if type(t) == "cdata" then
|
||||||
|
if type(k) == "number" then
|
||||||
|
return t._m[k-1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return rawget(mat4, k)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mat4_mt.__newindex(t, k, v)
|
||||||
|
if type(t) == "cdata" then
|
||||||
|
if type(k) == "number" then
|
||||||
|
t._m[k-1] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mat4_mt.__tostring = mat4.to_string
|
||||||
|
|
||||||
|
function mat4_mt.__call(_, a)
|
||||||
|
return mat4.new(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mat4_mt.__unm(a)
|
||||||
|
return new():invert(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
function mat4_mt.__eq(a, b)
|
||||||
|
if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, 16 do
|
||||||
|
if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function mat4_mt.__mul(a, b)
|
||||||
|
precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))
|
||||||
|
|
||||||
|
if vec3.is_vec3(b) then
|
||||||
|
return mat4.mul_vec3_perspective(vec3(), a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
|
||||||
|
|
||||||
|
if mat4.is_mat4(b) then
|
||||||
|
return new():mul(a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return mat4.mul_vec4({}, a, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||||
|
ffi.metatype(new, mat4_mt)
|
||||||
|
end, function() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, mat4_mt)
|
||||||
51
libs/cpml/mesh.lua
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
--- Mesh utilities
|
||||||
|
-- @module mesh
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local mesh = {}
|
||||||
|
|
||||||
|
-- vertices is an arbitrary list of vec3s
|
||||||
|
function mesh.average(vertices)
|
||||||
|
local out = vec3()
|
||||||
|
for _, v in ipairs(vertices) do
|
||||||
|
out = out + v
|
||||||
|
end
|
||||||
|
return out / #vertices
|
||||||
|
end
|
||||||
|
|
||||||
|
-- triangle[1] is a vec3
|
||||||
|
-- triangle[2] is a vec3
|
||||||
|
-- triangle[3] is a vec3
|
||||||
|
function mesh.normal(triangle)
|
||||||
|
local ba = triangle[2] - triangle[1]
|
||||||
|
local ca = triangle[3] - triangle[1]
|
||||||
|
return ba:cross(ca):normalize()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- triangle[1] is a vec3
|
||||||
|
-- triangle[2] is a vec3
|
||||||
|
-- triangle[3] is a vec3
|
||||||
|
function mesh.plane_from_triangle(triangle)
|
||||||
|
return {
|
||||||
|
origin = triangle[1],
|
||||||
|
normal = mesh.normal(triangle)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- plane.origin is a vec3
|
||||||
|
-- plane.normal is a vec3
|
||||||
|
-- direction is a vec3
|
||||||
|
function mesh.is_front_facing(plane, direction)
|
||||||
|
return plane.normal:dot(direction) >= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- point is a vec3
|
||||||
|
-- plane.origin is a vec3
|
||||||
|
-- plane.normal is a vec3
|
||||||
|
-- plane.dot is a number
|
||||||
|
function mesh.signed_distance(point, plane)
|
||||||
|
return point:dot(plane.normal) - plane.normal:dot(plane.origin)
|
||||||
|
end
|
||||||
|
|
||||||
|
return mesh
|
||||||
634
libs/cpml/octree.lua
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
-- https://github.com/Nition/UnityOctree
|
||||||
|
-- https://github.com/Nition/UnityOctree/blob/master/LICENCE
|
||||||
|
-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctree.cs
|
||||||
|
-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctreeNode.cs
|
||||||
|
|
||||||
|
--- Octree
|
||||||
|
-- @module octree
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local intersect = require(modules .. "intersect")
|
||||||
|
local mat4 = require(modules .. "mat4")
|
||||||
|
local utils = require(modules .. "utils")
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local Octree = {}
|
||||||
|
local OctreeNode = {}
|
||||||
|
local Node
|
||||||
|
|
||||||
|
Octree.__index = Octree
|
||||||
|
OctreeNode.__index = OctreeNode
|
||||||
|
|
||||||
|
--== Octree ==--
|
||||||
|
|
||||||
|
--- Constructor for the bounds octree.
|
||||||
|
-- @param initialWorldSize Size of the sides of the initial node, in metres. The octree will never shrink smaller than this
|
||||||
|
-- @param initialWorldPos Position of the centre of the initial node
|
||||||
|
-- @param minNodeSize Nodes will stop splitting if the new nodes would be smaller than this (metres)
|
||||||
|
-- @param looseness Clamped between 1 and 2. Values > 1 let nodes overlap
|
||||||
|
local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
|
||||||
|
local tree = setmetatable({}, Octree)
|
||||||
|
|
||||||
|
if minNodeSize > initialWorldSize then
|
||||||
|
print("Minimum node size must be at least as big as the initial world size. Was: " .. minNodeSize .. " Adjusted to: " .. initialWorldSize)
|
||||||
|
minNodeSize = initialWorldSize
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The total amount of objects currently in the tree
|
||||||
|
tree.count = 0
|
||||||
|
|
||||||
|
-- Size that the octree was on creation
|
||||||
|
tree.initialSize = initialWorldSize
|
||||||
|
|
||||||
|
-- Minimum side length that a node can be - essentially an alternative to having a max depth
|
||||||
|
tree.minSize = minNodeSize
|
||||||
|
|
||||||
|
-- Should be a value between 1 and 2. A multiplier for the base size of a node.
|
||||||
|
-- 1.0 is a "normal" octree, while values > 1 have overlap
|
||||||
|
tree.looseness = utils.clamp(looseness, 1, 2)
|
||||||
|
|
||||||
|
-- Root node of the octree
|
||||||
|
tree.rootNode = Node(tree.initialSize, tree.minSize, tree.looseness, initialWorldPos)
|
||||||
|
|
||||||
|
return tree
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
|
||||||
|
-- @param xDir X direction of growth. 1 or -1
|
||||||
|
-- @param yDir Y direction of growth. 1 or -1
|
||||||
|
-- @param zDir Z direction of growth. 1 or -1
|
||||||
|
-- @return Octant where the root node should be
|
||||||
|
local function get_root_pos_index(xDir, yDir, zDir)
|
||||||
|
local result = xDir > 0 and 1 or 0
|
||||||
|
if yDir < 0 then return result + 4 end
|
||||||
|
if zDir > 0 then return result + 2 end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add an object.
|
||||||
|
-- @param obj Object to add
|
||||||
|
-- @param objBounds 3D bounding box around the object
|
||||||
|
function Octree:add(obj, objBounds)
|
||||||
|
-- Add object or expand the octree until it can be added
|
||||||
|
local count = 0 -- Safety check against infinite/excessive growth
|
||||||
|
|
||||||
|
while not self.rootNode:add(obj, objBounds) do
|
||||||
|
count = count + 1
|
||||||
|
self:grow(objBounds.center - self.rootNode.center)
|
||||||
|
|
||||||
|
if count > 20 then
|
||||||
|
print("Aborted Add operation as it seemed to be going on forever (" .. count - 1 .. ") attempts at growing the octree.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.count = self.count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Remove an object. Makes the assumption that the object only exists once in the tree.
|
||||||
|
-- @param obj Object to remove
|
||||||
|
-- @return bool True if the object was removed successfully
|
||||||
|
function Octree:remove(obj)
|
||||||
|
local removed = self.rootNode:remove(obj)
|
||||||
|
|
||||||
|
-- See if we can shrink the octree down now that we've removed the item
|
||||||
|
if removed then
|
||||||
|
self.count = self.count - 1
|
||||||
|
self:shrink()
|
||||||
|
end
|
||||||
|
|
||||||
|
return removed
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
|
||||||
|
-- @param checkBounds bounds to check
|
||||||
|
-- @return bool True if there was a collision
|
||||||
|
function Octree:is_colliding(checkBounds)
|
||||||
|
return self.rootNode:is_colliding(checkBounds)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
|
||||||
|
-- @param checkBounds bounds to check
|
||||||
|
-- @return table Objects that intersect with the specified bounds
|
||||||
|
function Octree:get_colliding(checkBounds)
|
||||||
|
return self.rootNode:get_colliding(checkBounds)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Cast a ray through the node and its children
|
||||||
|
-- @param ray Ray with a position and a direction
|
||||||
|
-- @param func Function to execute on any objects within child nodes
|
||||||
|
-- @param out Table to store results of func in
|
||||||
|
-- @return boolean True if an intersect detected
|
||||||
|
function Octree:cast_ray(ray, func, out)
|
||||||
|
assert(func)
|
||||||
|
return self.rootNode:cast_ray(ray, func, out)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Draws node boundaries visually for debugging.
|
||||||
|
function Octree:draw_bounds(cube)
|
||||||
|
self.rootNode:draw_bounds(cube)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Draws the bounds of all objects in the tree visually for debugging.
|
||||||
|
function Octree:draw_objects(cube, filter)
|
||||||
|
self.rootNode:draw_objects(cube, filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Grow the octree to fit in all objects.
|
||||||
|
-- @param direction Direction to grow
|
||||||
|
function Octree:grow(direction)
|
||||||
|
local xDirection = direction.x >= 0 and 1 or -1
|
||||||
|
local yDirection = direction.y >= 0 and 1 or -1
|
||||||
|
local zDirection = direction.z >= 0 and 1 or -1
|
||||||
|
|
||||||
|
local oldRoot = self.rootNode
|
||||||
|
local half = self.rootNode.baseLength / 2
|
||||||
|
local newLength = self.rootNode.baseLength * 2
|
||||||
|
local newCenter = self.rootNode.center + vec3(xDirection * half, yDirection * half, zDirection * half)
|
||||||
|
|
||||||
|
-- Create a new, bigger octree root node
|
||||||
|
self.rootNode = Node(newLength, self.minSize, self.looseness, newCenter)
|
||||||
|
|
||||||
|
-- Create 7 new octree children to go with the old root as children of the new root
|
||||||
|
local rootPos = get_root_pos_index(xDirection, yDirection, zDirection)
|
||||||
|
local children = {}
|
||||||
|
|
||||||
|
for i = 0, 7 do
|
||||||
|
if i == rootPos then
|
||||||
|
children[i+1] = oldRoot
|
||||||
|
else
|
||||||
|
xDirection = i % 2 == 0 and -1 or 1
|
||||||
|
yDirection = i > 3 and -1 or 1
|
||||||
|
zDirection = (i < 2 or (i > 3 and i < 6)) and -1 or 1
|
||||||
|
children[i+1] = Node(self.rootNode.baseLength, self.minSize, self.looseness, newCenter + vec3(xDirection * half, yDirection * half, zDirection * half))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Attach the new children to the new root node
|
||||||
|
self.rootNode:set_children(children)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Shrink the octree if possible, else leave it the same.
|
||||||
|
function Octree:shrink()
|
||||||
|
self.rootNode = self.rootNode:shrink_if_possible(self.initialSize)
|
||||||
|
end
|
||||||
|
|
||||||
|
--== Octree Node ==--
|
||||||
|
|
||||||
|
--- Constructor.
|
||||||
|
-- @param baseLength Length of this node, not taking looseness into account
|
||||||
|
-- @param minSize Minimum size of nodes in this octree
|
||||||
|
-- @param looseness Multiplier for baseLengthVal to get the actual size
|
||||||
|
-- @param center Centre position of this node
|
||||||
|
local function new_node(baseLength, minSize, looseness, center)
|
||||||
|
local node = setmetatable({}, OctreeNode)
|
||||||
|
|
||||||
|
-- Objects in this node
|
||||||
|
node.objects = {}
|
||||||
|
|
||||||
|
-- Child nodes
|
||||||
|
node.children = {}
|
||||||
|
|
||||||
|
-- If there are already numObjectsAllowed in a node, we split it into children
|
||||||
|
-- A generally good number seems to be something around 8-15
|
||||||
|
node.numObjectsAllowed = 8
|
||||||
|
|
||||||
|
node:set_values(baseLength, minSize, looseness, center)
|
||||||
|
|
||||||
|
return node
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new_bound(center, size)
|
||||||
|
return {
|
||||||
|
center = center,
|
||||||
|
size = size,
|
||||||
|
min = center - (size / 2),
|
||||||
|
max = center + (size / 2)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add an object.
|
||||||
|
-- @param obj Object to add
|
||||||
|
-- @param objBounds 3D bounding box around the object
|
||||||
|
-- @return boolean True if the object fits entirely within this node
|
||||||
|
function OctreeNode:add(obj, objBounds)
|
||||||
|
if not intersect.encapsulate_aabb(self.bounds, objBounds) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We know it fits at this level if we've got this far
|
||||||
|
-- Just add if few objects are here, or children would be below min size
|
||||||
|
if #self.objects < self.numObjectsAllowed
|
||||||
|
or self.baseLength / 2 < self.minSize then
|
||||||
|
table.insert(self.objects, {
|
||||||
|
data = obj,
|
||||||
|
bounds = objBounds
|
||||||
|
})
|
||||||
|
else
|
||||||
|
-- Fits at this level, but we can go deeper. Would it fit there?
|
||||||
|
|
||||||
|
local best_fit_child
|
||||||
|
|
||||||
|
-- Create the 8 children
|
||||||
|
if #self.children == 0 then
|
||||||
|
self:split()
|
||||||
|
|
||||||
|
if #self.children == 0 then
|
||||||
|
print("Child creation failed for an unknown reason. Early exit.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Now that we have the new children, see if this node's existing objects would fit there
|
||||||
|
for i = #self.objects, 1, -1 do
|
||||||
|
local object = self.objects[i]
|
||||||
|
-- Find which child the object is closest to based on where the
|
||||||
|
-- object's center is located in relation to the octree's center.
|
||||||
|
best_fit_child = self:best_fit_child(object.bounds)
|
||||||
|
|
||||||
|
-- Does it fit?
|
||||||
|
if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, object.bounds) then
|
||||||
|
self.children[best_fit_child]:add(object.data, object.bounds) -- Go a level deeper
|
||||||
|
table.remove(self.objects, i) -- Remove from here
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Now handle the new object we're adding now
|
||||||
|
best_fit_child = self:best_fit_child(objBounds)
|
||||||
|
|
||||||
|
if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, objBounds) then
|
||||||
|
self.children[best_fit_child]:add(obj, objBounds)
|
||||||
|
else
|
||||||
|
table.insert(self.objects, {
|
||||||
|
data = obj,
|
||||||
|
bounds = objBounds
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Remove an object. Makes the assumption that the object only exists once in the tree.
|
||||||
|
-- @param obj Object to remove
|
||||||
|
-- @return boolean True if the object was removed successfully
|
||||||
|
function OctreeNode:remove(obj)
|
||||||
|
local removed = false
|
||||||
|
|
||||||
|
for i, object in ipairs(self.objects) do
|
||||||
|
if object == obj then
|
||||||
|
removed = table.remove(self.objects, i) and true or false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not removed then
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
removed = child:remove(obj)
|
||||||
|
if removed then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if removed then
|
||||||
|
-- Check if we should merge nodes now that we've removed an item
|
||||||
|
if self:should_merge() then
|
||||||
|
self:merge()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return removed
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
|
||||||
|
-- @param checkBounds Bounds to check
|
||||||
|
-- @return boolean True if there was a collision
|
||||||
|
function OctreeNode:is_colliding(checkBounds)
|
||||||
|
-- Are the input bounds at least partially in this node?
|
||||||
|
if not intersect.aabb_aabb(self.bounds, checkBounds) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check against any objects in this node
|
||||||
|
for _, object in ipairs(self.objects) do
|
||||||
|
if intersect.aabb_aabb(object.bounds, checkBounds) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check children
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
if child:is_colliding(checkBounds) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
|
||||||
|
-- @param checkBounds Bounds to check. Passing by ref as it improve performance with structs
|
||||||
|
-- @param results List results
|
||||||
|
-- @return table Objects that intersect with the specified bounds
|
||||||
|
function OctreeNode:get_colliding(checkBounds, results)
|
||||||
|
results = results or {}
|
||||||
|
|
||||||
|
-- Are the input bounds at least partially in this node?
|
||||||
|
if not intersect.aabb_aabb(self.bounds, checkBounds) then
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check against any objects in this node
|
||||||
|
for _, object in ipairs(self.objects) do
|
||||||
|
if intersect.aabb_aabb(object.bounds, checkBounds) then
|
||||||
|
table.insert(results, object.data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check children
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
results = child:get_colliding(checkBounds, results)
|
||||||
|
end
|
||||||
|
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Cast a ray through the node and its children
|
||||||
|
-- @param ray Ray with a position and a direction
|
||||||
|
-- @param func Function to execute on any objects within child nodes
|
||||||
|
-- @param out Table to store results of func in
|
||||||
|
-- @param depth (used internally)
|
||||||
|
-- @return boolean True if an intersect is detected
|
||||||
|
function OctreeNode:cast_ray(ray, func, out, depth)
|
||||||
|
depth = depth or 1
|
||||||
|
|
||||||
|
if intersect.ray_aabb(ray, self.bounds) then
|
||||||
|
if #self.objects > 0 then
|
||||||
|
local hit = func(ray, self.objects, out)
|
||||||
|
|
||||||
|
if hit then
|
||||||
|
return hit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
local hit = child:cast_ray(ray, func, out, depth + 1)
|
||||||
|
|
||||||
|
if hit then
|
||||||
|
return hit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set the 8 children of this octree.
|
||||||
|
-- @param childOctrees The 8 new child nodes
|
||||||
|
function OctreeNode:set_children(childOctrees)
|
||||||
|
if #childOctrees ~= 8 then
|
||||||
|
print("Child octree array must be length 8. Was length: " .. #childOctrees)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self.children = childOctrees
|
||||||
|
end
|
||||||
|
|
||||||
|
--- We can shrink the octree if:
|
||||||
|
--- - This node is >= double minLength in length
|
||||||
|
--- - All objects in the root node are within one octant
|
||||||
|
--- - This node doesn't have children, or does but 7/8 children are empty
|
||||||
|
--- We can also shrink it if there are no objects left at all!
|
||||||
|
-- @param minLength Minimum dimensions of a node in this octree
|
||||||
|
-- @return table The new root, or the existing one if we didn't shrink
|
||||||
|
function OctreeNode:shrink_if_possible(minLength)
|
||||||
|
if self.baseLength < 2 * minLength then
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
if #self.objects == 0 and #self.children == 0 then
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check objects in root
|
||||||
|
local bestFit = 0
|
||||||
|
|
||||||
|
for i, object in ipairs(self.objects) do
|
||||||
|
local newBestFit = self:best_fit_child(object.bounds)
|
||||||
|
|
||||||
|
if i == 1 or newBestFit == bestFit then
|
||||||
|
-- In same octant as the other(s). Does it fit completely inside that octant?
|
||||||
|
if intersect.encapsulate_aabb(self.childBounds[newBestFit], object.bounds) then
|
||||||
|
if bestFit < 1 then
|
||||||
|
bestFit = newBestFit
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Nope, so we can't reduce. Otherwise we continue
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return self -- Can't reduce - objects fit in different octants
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check objects in children if there are any
|
||||||
|
if #self.children > 0 then
|
||||||
|
local childHadContent = false
|
||||||
|
|
||||||
|
for i, child in ipairs(self.children) do
|
||||||
|
if child:has_any_objects() then
|
||||||
|
if childHadContent then
|
||||||
|
return self -- Can't shrink - another child had content already
|
||||||
|
end
|
||||||
|
|
||||||
|
if bestFit > 0 and bestFit ~= i then
|
||||||
|
return self -- Can't reduce - objects in root are in a different octant to objects in child
|
||||||
|
end
|
||||||
|
|
||||||
|
childHadContent = true
|
||||||
|
bestFit = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Can reduce
|
||||||
|
if #self.children == 0 then
|
||||||
|
-- We don't have any children, so just shrink this node to the new size
|
||||||
|
-- We already know that everything will still fit in it
|
||||||
|
self:set_values(self.baseLength / 2, self.minSize, self.looseness, self.childBounds[bestFit].center)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We have children. Use the appropriate child as the new root node
|
||||||
|
return self.children[bestFit]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Set values for this node.
|
||||||
|
-- @param baseLength Length of this node, not taking looseness into account
|
||||||
|
-- @param minSize Minimum size of nodes in this octree
|
||||||
|
-- @param looseness Multiplier for baseLengthVal to get the actual size
|
||||||
|
-- @param center Centre position of this node
|
||||||
|
function OctreeNode:set_values(baseLength, minSize, looseness, center)
|
||||||
|
-- Length of this node if it has a looseness of 1.0
|
||||||
|
self.baseLength = baseLength
|
||||||
|
|
||||||
|
-- Minimum size for a node in this octree
|
||||||
|
self.minSize = minSize
|
||||||
|
|
||||||
|
-- Looseness value for this node
|
||||||
|
self.looseness = looseness
|
||||||
|
|
||||||
|
-- Centre of this node
|
||||||
|
self.center = center
|
||||||
|
|
||||||
|
-- Actual length of sides, taking the looseness value into account
|
||||||
|
self.adjLength = self.looseness * self.baseLength
|
||||||
|
|
||||||
|
-- Create the bounding box.
|
||||||
|
self.size = vec3(self.adjLength, self.adjLength, self.adjLength)
|
||||||
|
|
||||||
|
-- Bounding box that represents this node
|
||||||
|
self.bounds = new_bound(self.center, self.size)
|
||||||
|
|
||||||
|
self.quarter = self.baseLength / 4
|
||||||
|
self.childActualLength = (self.baseLength / 2) * self.looseness
|
||||||
|
self.childActualSize = vec3(self.childActualLength, self.childActualLength, self.childActualLength)
|
||||||
|
|
||||||
|
-- Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
|
||||||
|
self.childBounds = {
|
||||||
|
new_bound(self.center + vec3(-self.quarter, self.quarter, -self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3( self.quarter, self.quarter, -self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3(-self.quarter, self.quarter, self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3( self.quarter, self.quarter, self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3(-self.quarter, -self.quarter, -self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3( self.quarter, -self.quarter, -self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3(-self.quarter, -self.quarter, self.quarter), self.childActualSize),
|
||||||
|
new_bound(self.center + vec3( self.quarter, -self.quarter, self.quarter), self.childActualSize)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Splits the octree into eight children.
|
||||||
|
function OctreeNode:split()
|
||||||
|
if #self.children > 0 then return end
|
||||||
|
|
||||||
|
local quarter = self.baseLength / 4
|
||||||
|
local newLength = self.baseLength / 2
|
||||||
|
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, quarter, -quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, quarter, -quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, quarter, quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, quarter, quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, -quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, -quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, quarter)))
|
||||||
|
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, quarter)))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Merge all children into this node - the opposite of Split.
|
||||||
|
--- Note: We only have to check one level down since a merge will never happen if the children already have children,
|
||||||
|
--- since THAT won't happen unless there are already too many objects to merge.
|
||||||
|
function OctreeNode:merge()
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
for _, object in ipairs(child.objects) do
|
||||||
|
table.insert(self.objects, object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove the child nodes (and the objects in them - they've been added elsewhere now)
|
||||||
|
self.children = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Find which child node this object would be most likely to fit in.
|
||||||
|
-- @param objBounds The object's bounds
|
||||||
|
-- @return number One of the eight child octants
|
||||||
|
function OctreeNode:best_fit_child(objBounds)
|
||||||
|
return (objBounds.center.x <= self.center.x and 0 or 1) + (objBounds.center.y >= self.center.y and 0 or 4) + (objBounds.center.z <= self.center.z and 0 or 2) + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks if there are few enough objects in this node and its children that the children should all be merged into this.
|
||||||
|
-- @return boolean True there are less or the same abount of objects in this and its children than numObjectsAllowed
|
||||||
|
function OctreeNode:should_merge()
|
||||||
|
local totalObjects = #self.objects
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
if #child.children > 0 then
|
||||||
|
-- If any of the *children* have children, there are definitely too many to merge,
|
||||||
|
-- or the child would have been merged already
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
totalObjects = totalObjects + #child.objects
|
||||||
|
end
|
||||||
|
|
||||||
|
return totalObjects <= self.numObjectsAllowed
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks if this node or anything below it has something in it.
|
||||||
|
-- @return boolean True if this node or any of its children, grandchildren etc have something in the
|
||||||
|
function OctreeNode:has_any_objects()
|
||||||
|
if #self.objects > 0 then return true end
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
if child:has_any_objects() then return true end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Draws node boundaries visually for debugging.
|
||||||
|
-- @param cube Cube model to draw
|
||||||
|
-- @param depth Used for recurcive calls to this method
|
||||||
|
function OctreeNode:draw_bounds(cube, depth)
|
||||||
|
depth = depth or 0
|
||||||
|
local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically
|
||||||
|
|
||||||
|
love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
|
||||||
|
local m = mat4()
|
||||||
|
:translate(self.center)
|
||||||
|
:scale(vec3(self.adjLength, self.adjLength, self.adjLength))
|
||||||
|
|
||||||
|
love.graphics.updateMatrix("transform", m)
|
||||||
|
love.graphics.setWireframe(true)
|
||||||
|
love.graphics.draw(cube)
|
||||||
|
love.graphics.setWireframe(false)
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
child:draw_bounds(cube, depth + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setColor(255, 255, 255)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Draws the bounds of all objects in the tree visually for debugging.
|
||||||
|
-- @param cube Cube model to draw
|
||||||
|
-- @param filter a function returning true or false to determine visibility.
|
||||||
|
function OctreeNode:draw_objects(cube, filter)
|
||||||
|
local tint = self.baseLength / 20
|
||||||
|
love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)
|
||||||
|
|
||||||
|
for _, object in ipairs(self.objects) do
|
||||||
|
if filter and filter(object.data) or not filter then
|
||||||
|
local m = mat4()
|
||||||
|
:translate(object.bounds.center)
|
||||||
|
:scale(object.bounds.size)
|
||||||
|
|
||||||
|
love.graphics.updateMatrix("transform", m)
|
||||||
|
love.graphics.draw(cube)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, child in ipairs(self.children) do
|
||||||
|
child:draw_objects(cube, filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setColor(255, 255, 255)
|
||||||
|
end
|
||||||
|
|
||||||
|
Node = setmetatable({
|
||||||
|
new = new_node
|
||||||
|
}, {
|
||||||
|
__call = function(_, ...) return new_node(...) end
|
||||||
|
})
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
new = new
|
||||||
|
}, {
|
||||||
|
__call = function(_, ...) return new(...) end
|
||||||
|
})
|
||||||
498
libs/cpml/quat.lua
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
--- A quaternion and associated utilities.
|
||||||
|
-- @module quat
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local constants = require(modules .. "constants")
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local precond = require(modules .. "_private_precond")
|
||||||
|
local private = require(modules .. "_private_utils")
|
||||||
|
local DOT_THRESHOLD = constants.DOT_THRESHOLD
|
||||||
|
local DBL_EPSILON = constants.DBL_EPSILON
|
||||||
|
local acos = math.acos
|
||||||
|
local cos = math.cos
|
||||||
|
local sin = math.sin
|
||||||
|
local min = math.min
|
||||||
|
local max = math.max
|
||||||
|
local sqrt = math.sqrt
|
||||||
|
local quat = {}
|
||||||
|
local quat_mt = {}
|
||||||
|
|
||||||
|
-- Private constructor.
|
||||||
|
local function new(x, y, z, w)
|
||||||
|
return setmetatable({
|
||||||
|
x = x or 0,
|
||||||
|
y = y or 0,
|
||||||
|
z = z or 0,
|
||||||
|
w = w or 1
|
||||||
|
}, quat_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||||
|
local status, ffi
|
||||||
|
if type(jit) == "table" and jit.status() then
|
||||||
|
status, ffi = pcall(require, "ffi")
|
||||||
|
if status then
|
||||||
|
ffi.cdef "typedef struct { double x, y, z, w;} cpml_quat;"
|
||||||
|
new = ffi.typeof("cpml_quat")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Statically allocate a temporary variable used in some of our functions.
|
||||||
|
local tmp = new()
|
||||||
|
local qv, uv, uuv = vec3(), vec3(), vec3()
|
||||||
|
|
||||||
|
--- Constants
|
||||||
|
-- @table quat
|
||||||
|
-- @field unit Unit quaternion
|
||||||
|
-- @field zero Empty quaternion
|
||||||
|
quat.unit = new(0, 0, 0, 1)
|
||||||
|
quat.zero = new(0, 0, 0, 0)
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param x Can be of two types: </br>
|
||||||
|
-- number x X component
|
||||||
|
-- table {x, y, z, w} or {x=x, y=y, z=z, w=w}
|
||||||
|
-- @tparam number y Y component
|
||||||
|
-- @tparam number z Z component
|
||||||
|
-- @tparam number w W component
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.new(x, y, z, w)
|
||||||
|
-- number, number, number, number
|
||||||
|
if x and y and z and w then
|
||||||
|
precond.typeof(x, "number", "new: Wrong argument type for x")
|
||||||
|
precond.typeof(y, "number", "new: Wrong argument type for y")
|
||||||
|
precond.typeof(z, "number", "new: Wrong argument type for z")
|
||||||
|
precond.typeof(w, "number", "new: Wrong argument type for w")
|
||||||
|
|
||||||
|
return new(x, y, z, w)
|
||||||
|
|
||||||
|
-- {x, y, z, w} or {x=x, y=y, z=z, w=w}
|
||||||
|
elseif type(x) == "table" then
|
||||||
|
local xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
|
||||||
|
precond.typeof(xx, "number", "new: Wrong argument type for x")
|
||||||
|
precond.typeof(yy, "number", "new: Wrong argument type for y")
|
||||||
|
precond.typeof(zz, "number", "new: Wrong argument type for z")
|
||||||
|
precond.typeof(ww, "number", "new: Wrong argument type for w")
|
||||||
|
|
||||||
|
return new(xx, yy, zz, ww)
|
||||||
|
end
|
||||||
|
|
||||||
|
return new(0, 0, 0, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a quaternion from an angle/axis pair.
|
||||||
|
-- @tparam number angle Angle (in radians)
|
||||||
|
-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis
|
||||||
|
-- @param y axis -- y component of axis (optional, only if x component param used)
|
||||||
|
-- @param z axis -- z component of axis (optional, only if x component param used)
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.from_angle_axis(angle, axis, a3, a4)
|
||||||
|
if axis and a3 and a4 then
|
||||||
|
local x, y, z = axis, a3, a4
|
||||||
|
local s = sin(angle * 0.5)
|
||||||
|
local c = cos(angle * 0.5)
|
||||||
|
return new(x * s, y * s, z * s, c)
|
||||||
|
else
|
||||||
|
return quat.from_angle_axis(angle, axis.x, axis.y, axis.z)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a quaternion from a normal/up vector pair.
|
||||||
|
-- @tparam vec3 normal
|
||||||
|
-- @tparam vec3 up (optional)
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.from_direction(normal, up)
|
||||||
|
local u = up or vec3.unit_z
|
||||||
|
local n = normal:normalize()
|
||||||
|
local a = u:cross(n)
|
||||||
|
local d = u:dot(n)
|
||||||
|
return new(a.x, a.y, a.z, d + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to clone
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.clone(a)
|
||||||
|
return new(a.x, a.y, a.z, a.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add two quaternions.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam quat b Right hand operand
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.add(a, b)
|
||||||
|
return new(
|
||||||
|
a.x + b.x,
|
||||||
|
a.y + b.y,
|
||||||
|
a.z + b.z,
|
||||||
|
a.w + b.w
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Subtract a quaternion from another.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam quat b Right hand operand
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.sub(a, b)
|
||||||
|
return new(
|
||||||
|
a.x - b.x,
|
||||||
|
a.y - b.y,
|
||||||
|
a.z - b.z,
|
||||||
|
a.w - b.w
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply two quaternions.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam quat b Right hand operand
|
||||||
|
-- @treturn quat quaternion equivalent to "apply b, then a"
|
||||||
|
function quat.mul(a, b)
|
||||||
|
return new(
|
||||||
|
a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,
|
||||||
|
a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,
|
||||||
|
a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,
|
||||||
|
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a quaternion and a vec3.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function quat.mul_vec3(a, b)
|
||||||
|
qv.x = a.x
|
||||||
|
qv.y = a.y
|
||||||
|
qv.z = a.z
|
||||||
|
uv = qv:cross(b)
|
||||||
|
uuv = qv:cross(uv)
|
||||||
|
return b + ((uv * a.w) + uuv) * 2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Raise a normalized quaternion to a scalar power.
|
||||||
|
-- @tparam quat a Left hand operand (should be a unit quaternion)
|
||||||
|
-- @tparam number s Right hand operand
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.pow(a, s)
|
||||||
|
-- Do it as a slerp between identity and a (code borrowed from slerp)
|
||||||
|
if a.w < 0 then
|
||||||
|
a = -a
|
||||||
|
end
|
||||||
|
local dot = a.w
|
||||||
|
|
||||||
|
dot = min(max(dot, -1), 1)
|
||||||
|
|
||||||
|
local theta = acos(dot) * s
|
||||||
|
local c = new(a.x, a.y, a.z, 0):normalize() * sin(theta)
|
||||||
|
c.w = cos(theta)
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Normalize a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to normalize
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.normalize(a)
|
||||||
|
if a:is_zero() then
|
||||||
|
return new(0, 0, 0, 0)
|
||||||
|
end
|
||||||
|
return a:scale(1 / a:len())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the dot product of two quaternions.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam quat b Right hand operand
|
||||||
|
-- @treturn number dot
|
||||||
|
function quat.dot(a, b)
|
||||||
|
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the length of a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to get length of
|
||||||
|
-- @treturn number len
|
||||||
|
function quat.len(a)
|
||||||
|
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the squared length of a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to get length of
|
||||||
|
-- @treturn number len
|
||||||
|
function quat.len2(a)
|
||||||
|
return a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a quaternion by a scalar.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam number s Right hand operand
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.scale(a, s)
|
||||||
|
return new(
|
||||||
|
a.x * s,
|
||||||
|
a.y * s,
|
||||||
|
a.z * s,
|
||||||
|
a.w * s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Alias of from_angle_axis.
|
||||||
|
-- @tparam number angle Angle (in radians)
|
||||||
|
-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis
|
||||||
|
-- @param y axis -- y component of axis (optional, only if x component param used)
|
||||||
|
-- @param z axis -- z component of axis (optional, only if x component param used)
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.rotate(angle, axis, a3, a4)
|
||||||
|
return quat.from_angle_axis(angle, axis, a3, a4)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the conjugate of a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to conjugate
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.conjugate(a)
|
||||||
|
return new(-a.x, -a.y, -a.z, a.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the inverse of a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to invert
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.inverse(a)
|
||||||
|
tmp.x = -a.x
|
||||||
|
tmp.y = -a.y
|
||||||
|
tmp.z = -a.z
|
||||||
|
tmp.w = a.w
|
||||||
|
return tmp:normalize()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the reciprocal of a quaternion.
|
||||||
|
-- @tparam quat a Quaternion to reciprocate
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.reciprocal(a)
|
||||||
|
if a:is_zero() then
|
||||||
|
error("Cannot reciprocate a zero quaternion")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp.x = -a.x
|
||||||
|
tmp.y = -a.y
|
||||||
|
tmp.z = -a.z
|
||||||
|
tmp.w = a.w
|
||||||
|
|
||||||
|
return tmp:scale(1 / a:len2())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Lerp between two quaternions.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam quat b Right hand operand
|
||||||
|
-- @tparam number s Step value
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.lerp(a, b, s)
|
||||||
|
return (a + (b - a) * s):normalize()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Slerp between two quaternions.
|
||||||
|
-- @tparam quat a Left hand operand
|
||||||
|
-- @tparam quat b Right hand operand
|
||||||
|
-- @tparam number s Step value
|
||||||
|
-- @treturn quat out
|
||||||
|
function quat.slerp(a, b, s)
|
||||||
|
local dot = a:dot(b)
|
||||||
|
|
||||||
|
if dot < 0 then
|
||||||
|
a = -a
|
||||||
|
dot = -dot
|
||||||
|
end
|
||||||
|
|
||||||
|
if dot > DOT_THRESHOLD then
|
||||||
|
return a:lerp(b, s)
|
||||||
|
end
|
||||||
|
|
||||||
|
dot = min(max(dot, -1), 1)
|
||||||
|
|
||||||
|
local theta = acos(dot) * s
|
||||||
|
local c = (b - a * dot):normalize()
|
||||||
|
return a * cos(theta) + c * sin(theta)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unpack a quaternion into individual components.
|
||||||
|
-- @tparam quat a Quaternion to unpack
|
||||||
|
-- @treturn number x
|
||||||
|
-- @treturn number y
|
||||||
|
-- @treturn number z
|
||||||
|
-- @treturn number w
|
||||||
|
function quat.unpack(a)
|
||||||
|
return a.x, a.y, a.z, a.w
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a quat.
|
||||||
|
-- @tparam quat a Quaternion to be tested
|
||||||
|
-- @treturn boolean is_quat
|
||||||
|
function quat.is_quat(a)
|
||||||
|
if type(a) == "cdata" then
|
||||||
|
return ffi.istype("cpml_quat", a)
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
type(a) == "table" and
|
||||||
|
type(a.x) == "number" and
|
||||||
|
type(a.y) == "number" and
|
||||||
|
type(a.z) == "number" and
|
||||||
|
type(a.w) == "number"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a zero quat.
|
||||||
|
-- @tparam quat a Quaternion to be tested
|
||||||
|
-- @treturn boolean is_zero
|
||||||
|
function quat.is_zero(a)
|
||||||
|
return
|
||||||
|
a.x == 0 and
|
||||||
|
a.y == 0 and
|
||||||
|
a.z == 0 and
|
||||||
|
a.w == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a real quat.
|
||||||
|
-- @tparam quat a Quaternion to be tested
|
||||||
|
-- @treturn boolean is_real
|
||||||
|
function quat.is_real(a)
|
||||||
|
return
|
||||||
|
a.x == 0 and
|
||||||
|
a.y == 0 and
|
||||||
|
a.z == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not an imaginary quat.
|
||||||
|
-- @tparam quat a Quaternion to be tested
|
||||||
|
-- @treturn boolean is_imaginary
|
||||||
|
function quat.is_imaginary(a)
|
||||||
|
return a.w == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return whether any component is NaN
|
||||||
|
-- @tparam quat a Quaternion to be tested
|
||||||
|
-- @treturn boolean if x,y,z, or w is NaN
|
||||||
|
function quat.has_nan(a)
|
||||||
|
return private.is_nan(a.x) or
|
||||||
|
private.is_nan(a.y) or
|
||||||
|
private.is_nan(a.z) or
|
||||||
|
private.is_nan(a.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert a quaternion into an angle plus axis components.
|
||||||
|
-- @tparam quat a Quaternion to convert
|
||||||
|
-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,0,0,1)
|
||||||
|
-- @treturn number angle
|
||||||
|
-- @treturn x axis-x
|
||||||
|
-- @treturn y axis-y
|
||||||
|
-- @treturn z axis-z
|
||||||
|
function quat.to_angle_axis_unpack(a, identityAxis)
|
||||||
|
if a.w > 1 or a.w < -1 then
|
||||||
|
a = a:normalize()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If length of xyz components is less than DBL_EPSILON, this is zero or close enough (an identity quaternion)
|
||||||
|
-- Normally an identity quat would return a nonsense answer, so we return an arbitrary zero rotation early.
|
||||||
|
-- FIXME: Is it safe to assume there are *no* valid quaternions with nonzero degenerate lengths?
|
||||||
|
if a.x*a.x + a.y*a.y + a.z*a.z < constants.DBL_EPSILON*constants.DBL_EPSILON then
|
||||||
|
if identityAxis then
|
||||||
|
return 0,identityAxis:unpack()
|
||||||
|
else
|
||||||
|
return 0,0,0,1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local x, y, z
|
||||||
|
local angle = 2 * acos(a.w)
|
||||||
|
local s = sqrt(1 - a.w * a.w)
|
||||||
|
|
||||||
|
if s < DBL_EPSILON then
|
||||||
|
x = a.x
|
||||||
|
y = a.y
|
||||||
|
z = a.z
|
||||||
|
else
|
||||||
|
x = a.x / s
|
||||||
|
y = a.y / s
|
||||||
|
z = a.z / s
|
||||||
|
end
|
||||||
|
|
||||||
|
return angle, x, y, z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert a quaternion into an angle/axis pair.
|
||||||
|
-- @tparam quat a Quaternion to convert
|
||||||
|
-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,vec3(0,0,1))
|
||||||
|
-- @treturn number angle
|
||||||
|
-- @treturn vec3 axis
|
||||||
|
function quat.to_angle_axis(a, identityAxis)
|
||||||
|
local angle, x, y, z = a:to_angle_axis_unpack(identityAxis)
|
||||||
|
return angle, vec3(x, y, z)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert a quaternion into a vec3.
|
||||||
|
-- @tparam quat a Quaternion to convert
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function quat.to_vec3(a)
|
||||||
|
return vec3(a.x, a.y, a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam quat a Quaternion to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function quat.to_string(a)
|
||||||
|
return string.format("(%+0.3f,%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z, a.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
quat_mt.__index = quat
|
||||||
|
quat_mt.__tostring = quat.to_string
|
||||||
|
|
||||||
|
function quat_mt.__call(_, x, y, z, w)
|
||||||
|
return quat.new(x, y, z, w)
|
||||||
|
end
|
||||||
|
|
||||||
|
function quat_mt.__unm(a)
|
||||||
|
return a:scale(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function quat_mt.__eq(a,b)
|
||||||
|
if not quat.is_quat(a) or not quat.is_quat(b) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w
|
||||||
|
end
|
||||||
|
|
||||||
|
function quat_mt.__add(a, b)
|
||||||
|
precond.assert(quat.is_quat(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||||
|
precond.assert(quat.is_quat(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
|
||||||
|
return a:add(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function quat_mt.__sub(a, b)
|
||||||
|
precond.assert(quat.is_quat(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||||
|
precond.assert(quat.is_quat(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
|
||||||
|
return a:sub(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function quat_mt.__mul(a, b)
|
||||||
|
precond.assert(quat.is_quat(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||||
|
assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.quat> or <cpml.vec3> or <number> expected)")
|
||||||
|
|
||||||
|
if quat.is_quat(b) then
|
||||||
|
return a:mul(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(b) == "number" then
|
||||||
|
return a:scale(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a:mul_vec3(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function quat_mt.__pow(a, n)
|
||||||
|
precond.assert(quat.is_quat(a), "__pow: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||||
|
precond.typeof(n, "number", "__pow: Wrong argument type for right hand operand.")
|
||||||
|
return a:pow(n)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||||
|
ffi.metatype(new, quat_mt)
|
||||||
|
end, function() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, quat_mt)
|
||||||
349
libs/cpml/simplex.lua
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
--- Simplex Noise
|
||||||
|
-- @module simplex
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Based on code in "Simplex noise demystified", by Stefan Gustavson
|
||||||
|
-- www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
|
||||||
|
--
|
||||||
|
-- Thanks to Mike Pall for some cleanup and improvements (and for LuaJIT!)
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
-- a copy of this software and associated documentation files (the
|
||||||
|
-- "Software"), to deal in the Software without restriction, including
|
||||||
|
-- without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
-- distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
-- permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
-- the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be
|
||||||
|
-- included in all copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
--
|
||||||
|
-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||||
|
--
|
||||||
|
|
||||||
|
if _G.love and _G.love.math then
|
||||||
|
return love.math.noise
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Bail out with dummy module if FFI is missing.
|
||||||
|
local has_ffi, ffi = pcall(require, "ffi")
|
||||||
|
if not has_ffi then
|
||||||
|
return function()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Modules --
|
||||||
|
local bit = require("bit")
|
||||||
|
|
||||||
|
-- Imports --
|
||||||
|
local band = bit.band
|
||||||
|
local bor = bit.bor
|
||||||
|
local floor = math.floor
|
||||||
|
local lshift = bit.lshift
|
||||||
|
local max = math.max
|
||||||
|
local rshift = bit.rshift
|
||||||
|
|
||||||
|
-- Permutation of 0-255, replicated to allow easy indexing with sums of two bytes --
|
||||||
|
local Perms = ffi.new("uint8_t[512]", {
|
||||||
|
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
|
||||||
|
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
|
||||||
|
247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
|
||||||
|
57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
|
||||||
|
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
|
||||||
|
60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
|
||||||
|
65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
|
||||||
|
200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,
|
||||||
|
52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,
|
||||||
|
207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
|
||||||
|
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
||||||
|
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
|
||||||
|
218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
|
||||||
|
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
|
||||||
|
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
|
||||||
|
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
|
||||||
|
})
|
||||||
|
|
||||||
|
-- The above, mod 12 for each element --
|
||||||
|
local Perms12 = ffi.new("uint8_t[512]")
|
||||||
|
|
||||||
|
for i = 0, 255 do
|
||||||
|
local x = Perms[i] % 12
|
||||||
|
|
||||||
|
Perms[i + 256], Perms12[i], Perms12[i + 256] = Perms[i], x, x
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Gradients for 2D, 3D case --
|
||||||
|
local Grads3 = ffi.new("const double[12][3]",
|
||||||
|
{ 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 },
|
||||||
|
{ 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 },
|
||||||
|
{ 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }
|
||||||
|
)
|
||||||
|
|
||||||
|
-- 2D weight contribution
|
||||||
|
local function GetN2(bx, by, x, y)
|
||||||
|
local t = .5 - x * x - y * y
|
||||||
|
local index = Perms12[bx + Perms[by]]
|
||||||
|
|
||||||
|
return max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function simplex_2d(x, y)
|
||||||
|
--[[
|
||||||
|
2D skew factors:
|
||||||
|
F = (math.sqrt(3) - 1) / 2
|
||||||
|
G = (3 - math.sqrt(3)) / 6
|
||||||
|
G2 = 2 * G - 1
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Skew the input space to determine which simplex cell we are in.
|
||||||
|
local s = (x + y) * 0.366025403 -- F
|
||||||
|
local ix, iy = floor(x + s), floor(y + s)
|
||||||
|
|
||||||
|
-- Unskew the cell origin back to (x, y) space.
|
||||||
|
local t = (ix + iy) * 0.211324865 -- G
|
||||||
|
local x0 = x + t - ix
|
||||||
|
local y0 = y + t - iy
|
||||||
|
|
||||||
|
-- Calculate the contribution from the two fixed corners.
|
||||||
|
-- A step of (1,0) in (i,j) means a step of (1-G,-G) in (x,y), and
|
||||||
|
-- A step of (0,1) in (i,j) means a step of (-G,1-G) in (x,y).
|
||||||
|
ix, iy = band(ix, 255), band(iy, 255)
|
||||||
|
|
||||||
|
local n0 = GetN2(ix, iy, x0, y0)
|
||||||
|
local n2 = GetN2(ix + 1, iy + 1, x0 - 0.577350270, y0 - 0.577350270) -- G2
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Determine other corner based on simplex (equilateral triangle) we are in:
|
||||||
|
if x0 > y0 then
|
||||||
|
ix, x1 = ix + 1, x1 - 1
|
||||||
|
else
|
||||||
|
iy, y1 = iy + 1, y1 - 1
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
local xi = rshift(floor(y0 - x0), 31) -- y0 < x0
|
||||||
|
local n1 = GetN2(ix + xi, iy + (1 - xi), x0 + 0.211324865 - xi, y0 - 0.788675135 + xi) -- x0 + G - xi, y0 + G - (1 - xi)
|
||||||
|
|
||||||
|
-- Add contributions from each corner to get the final noise value.
|
||||||
|
-- The result is scaled to return values in the interval [-1,1].
|
||||||
|
return 70.1480580019 * (n0 + n1 + n2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 3D weight contribution
|
||||||
|
local function GetN3(ix, iy, iz, x, y, z)
|
||||||
|
local t = .6 - x * x - y * y - z * z
|
||||||
|
local index = Perms12[ix + Perms[iy + Perms[iz]]]
|
||||||
|
|
||||||
|
return max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y + Grads3[index][2] * z)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function simplex_3d(x, y, z)
|
||||||
|
--[[
|
||||||
|
3D skew factors:
|
||||||
|
F = 1 / 3
|
||||||
|
G = 1 / 6
|
||||||
|
G2 = 2 * G
|
||||||
|
G3 = 3 * G - 1
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Skew the input space to determine which simplex cell we are in.
|
||||||
|
local s = (x + y + z) * 0.333333333 -- F
|
||||||
|
local ix, iy, iz = floor(x + s), floor(y + s), floor(z + s)
|
||||||
|
|
||||||
|
-- Unskew the cell origin back to (x, y, z) space.
|
||||||
|
local t = (ix + iy + iz) * 0.166666667 -- G
|
||||||
|
local x0 = x + t - ix
|
||||||
|
local y0 = y + t - iy
|
||||||
|
local z0 = z + t - iz
|
||||||
|
|
||||||
|
-- Calculate the contribution from the two fixed corners.
|
||||||
|
-- A step of (1,0,0) in (i,j,k) means a step of (1-G,-G,-G) in (x,y,z);
|
||||||
|
-- a step of (0,1,0) in (i,j,k) means a step of (-G,1-G,-G) in (x,y,z);
|
||||||
|
-- a step of (0,0,1) in (i,j,k) means a step of (-G,-G,1-G) in (x,y,z).
|
||||||
|
ix, iy, iz = band(ix, 255), band(iy, 255), band(iz, 255)
|
||||||
|
|
||||||
|
local n0 = GetN3(ix, iy, iz, x0, y0, z0)
|
||||||
|
local n3 = GetN3(ix + 1, iy + 1, iz + 1, x0 - 0.5, y0 - 0.5, z0 - 0.5) -- G3
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Determine other corners based on simplex (skewed tetrahedron) we are in:
|
||||||
|
|
||||||
|
if x0 >= y0 then -- ~A
|
||||||
|
if y0 >= z0 then -- ~A and ~B
|
||||||
|
i1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 1, 0
|
||||||
|
elseif x0 >= z0 then -- ~A and B and ~C
|
||||||
|
i1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 0, 1
|
||||||
|
else -- ~A and B and C
|
||||||
|
i1, j1, k1, i2, j2, k2 = 0, 0, 1, 1, 0, 1
|
||||||
|
end
|
||||||
|
else -- A
|
||||||
|
if y0 < z0 then -- A and B
|
||||||
|
i1, j1, k1, i2, j2, k2 = 0, 0, 1, 0, 1, 1
|
||||||
|
elseif x0 < z0 then -- A and ~B and C
|
||||||
|
i1, j1, k1, i2, j2, k2 = 0, 1, 0, 0, 1, 1
|
||||||
|
else -- A and ~B and ~C
|
||||||
|
i1, j1, k1, i2, j2, k2 = 0, 1, 0, 1, 1, 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
local xLy = rshift(floor(x0 - y0), 31) -- x0 < y0
|
||||||
|
local yLz = rshift(floor(y0 - z0), 31) -- y0 < z0
|
||||||
|
local xLz = rshift(floor(x0 - z0), 31) -- x0 < z0
|
||||||
|
|
||||||
|
local i1 = band(1 - xLy, bor(1 - yLz, 1 - xLz)) -- x0 >= y0 and (y0 >= z0 or x0 >= z0)
|
||||||
|
local j1 = band(xLy, 1 - yLz) -- x0 < y0 and y0 >= z0
|
||||||
|
local k1 = band(yLz, bor(xLy, xLz)) -- y0 < z0 and (x0 < y0 or x0 < z0)
|
||||||
|
|
||||||
|
local i2 = bor(1 - xLy, band(1 - yLz, 1 - xLz)) -- x0 >= y0 or (y0 >= z0 and x0 >= z0)
|
||||||
|
local j2 = bor(xLy, 1 - yLz) -- x0 < y0 or y0 >= z0
|
||||||
|
local k2 = bor(band(1 - xLy, yLz), band(xLy, bor(yLz, xLz))) -- (x0 >= y0 and y0 < z0) or (x0 < y0 and (y0 < z0 or x0 < z0))
|
||||||
|
|
||||||
|
local n1 = GetN3(ix + i1, iy + j1, iz + k1, x0 + 0.166666667 - i1, y0 + 0.166666667 - j1, z0 + 0.166666667 - k1) -- G
|
||||||
|
local n2 = GetN3(ix + i2, iy + j2, iz + k2, x0 + 0.333333333 - i2, y0 + 0.333333333 - j2, z0 + 0.333333333 - k2) -- G2
|
||||||
|
|
||||||
|
-- Add contributions from each corner to get the final noise value.
|
||||||
|
-- The result is scaled to stay just inside [-1,1]
|
||||||
|
return 28.452842 * (n0 + n1 + n2 + n3)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Gradients for 4D case --
|
||||||
|
local Grads4 = ffi.new("const double[32][4]",
|
||||||
|
{ 0, 1, 1, 1 }, { 0, 1, 1, -1 }, { 0, 1, -1, 1 }, { 0, 1, -1, -1 },
|
||||||
|
{ 0, -1, 1, 1 }, { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 },
|
||||||
|
{ 1, 0, 1, 1 }, { 1, 0, 1, -1 }, { 1, 0, -1, 1 }, { 1, 0, -1, -1 },
|
||||||
|
{ -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, { -1, 0, -1, -1 },
|
||||||
|
{ 1, 1, 0, 1 }, { 1, 1, 0, -1 }, { 1, -1, 0, 1 }, { 1, -1, 0, -1 },
|
||||||
|
{ -1, 1, 0, 1 }, { -1, 1, 0, -1 }, { -1, -1, 0, 1 }, { -1, -1, 0, -1 },
|
||||||
|
{ 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 },
|
||||||
|
{ -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 }
|
||||||
|
)
|
||||||
|
|
||||||
|
-- 4D weight contribution
|
||||||
|
local function GetN4(ix, iy, iz, iw, x, y, z, w)
|
||||||
|
local t = .6 - x * x - y * y - z * z - w * w
|
||||||
|
local index = band(Perms[ix + Perms[iy + Perms[iz + Perms[iw]]]], 0x1F)
|
||||||
|
|
||||||
|
return max(0, (t * t) * (t * t)) * (Grads4[index][0] * x + Grads4[index][1] * y + Grads4[index][2] * z + Grads4[index][3] * w)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A lookup table to traverse the simplex around a given point in 4D.
|
||||||
|
-- Details can be found where this table is used, in the 4D noise method.
|
||||||
|
local Simplex = ffi.new("uint8_t[64][4]",
|
||||||
|
{ 0, 1, 2, 3 }, { 0, 1, 3, 2 }, {}, { 0, 2, 3, 1 }, {}, {}, {}, { 1, 2, 3 },
|
||||||
|
{ 0, 2, 1, 3 }, {}, { 0, 3, 1, 2 }, { 0, 3, 2, 1 }, {}, {}, {}, { 1, 3, 2 },
|
||||||
|
{}, {}, {}, {}, {}, {}, {}, {},
|
||||||
|
{ 1, 2, 0, 3 }, {}, { 1, 3, 0, 2 }, {}, {}, {}, { 2, 3, 0, 1 }, { 2, 3, 1 },
|
||||||
|
{ 1, 0, 2, 3 }, { 1, 0, 3, 2 }, {}, {}, {}, { 2, 0, 3, 1 }, {}, { 2, 1, 3 },
|
||||||
|
{}, {}, {}, {}, {}, {}, {}, {},
|
||||||
|
{ 2, 0, 1, 3 }, {}, {}, {}, { 3, 0, 1, 2 }, { 3, 0, 2, 1 }, {}, { 3, 1, 2 },
|
||||||
|
{ 2, 1, 0, 3 }, {}, {}, {}, { 3, 1, 0, 2 }, {}, { 3, 2, 0, 1 }, { 3, 2, 1 }
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Convert the above indices to masks that can be shifted / anded into offsets --
|
||||||
|
for i = 0, 63 do
|
||||||
|
Simplex[i][0] = lshift(1, Simplex[i][0]) - 1
|
||||||
|
Simplex[i][1] = lshift(1, Simplex[i][1]) - 1
|
||||||
|
Simplex[i][2] = lshift(1, Simplex[i][2]) - 1
|
||||||
|
Simplex[i][3] = lshift(1, Simplex[i][3]) - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function simplex_4d(x, y, z, w)
|
||||||
|
--[[
|
||||||
|
4D skew factors:
|
||||||
|
F = (math.sqrt(5) - 1) / 4
|
||||||
|
G = (5 - math.sqrt(5)) / 20
|
||||||
|
G2 = 2 * G
|
||||||
|
G3 = 3 * G
|
||||||
|
G4 = 4 * G - 1
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Skew the input space to determine which simplex cell we are in.
|
||||||
|
local s = (x + y + z + w) * 0.309016994 -- F
|
||||||
|
local ix, iy, iz, iw = floor(x + s), floor(y + s), floor(z + s), floor(w + s)
|
||||||
|
|
||||||
|
-- Unskew the cell origin back to (x, y, z) space.
|
||||||
|
local t = (ix + iy + iz + iw) * 0.138196601 -- G
|
||||||
|
local x0 = x + t - ix
|
||||||
|
local y0 = y + t - iy
|
||||||
|
local z0 = z + t - iz
|
||||||
|
local w0 = w + t - iw
|
||||||
|
|
||||||
|
-- For the 4D case, the simplex is a 4D shape I won't even try to describe.
|
||||||
|
-- To find out which of the 24 possible simplices we're in, we need to
|
||||||
|
-- determine the magnitude ordering of x0, y0, z0 and w0.
|
||||||
|
-- The method below is a good way of finding the ordering of x,y,z,w and
|
||||||
|
-- then find the correct traversal order for the simplex we<77>re in.
|
||||||
|
-- First, six pair-wise comparisons are performed between each possible pair
|
||||||
|
-- of the four coordinates, and the results are used to add up binary bits
|
||||||
|
-- for an integer index.
|
||||||
|
local c1 = band(rshift(floor(y0 - x0), 26), 32)
|
||||||
|
local c2 = band(rshift(floor(z0 - x0), 27), 16)
|
||||||
|
local c3 = band(rshift(floor(z0 - y0), 28), 8)
|
||||||
|
local c4 = band(rshift(floor(w0 - x0), 29), 4)
|
||||||
|
local c5 = band(rshift(floor(w0 - y0), 30), 2)
|
||||||
|
local c6 = rshift(floor(w0 - z0), 31)
|
||||||
|
|
||||||
|
-- Simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
|
||||||
|
-- Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
|
||||||
|
-- impossible. Only the 24 indices which have non-zero entries make any sense.
|
||||||
|
-- We use a thresholding to set the coordinates in turn from the largest magnitude.
|
||||||
|
local c = c1 + c2 + c3 + c4 + c5 + c6
|
||||||
|
|
||||||
|
-- The number 3 (i.e. bit 2) in the "simplex" array is at the position of the largest coordinate.
|
||||||
|
local i1 = rshift(Simplex[c][0], 2)
|
||||||
|
local j1 = rshift(Simplex[c][1], 2)
|
||||||
|
local k1 = rshift(Simplex[c][2], 2)
|
||||||
|
local l1 = rshift(Simplex[c][3], 2)
|
||||||
|
|
||||||
|
-- The number 2 (i.e. bit 1) in the "simplex" array is at the second largest coordinate.
|
||||||
|
local i2 = band(rshift(Simplex[c][0], 1), 1)
|
||||||
|
local j2 = band(rshift(Simplex[c][1], 1), 1)
|
||||||
|
local k2 = band(rshift(Simplex[c][2], 1), 1)
|
||||||
|
local l2 = band(rshift(Simplex[c][3], 1), 1)
|
||||||
|
|
||||||
|
-- The number 1 (i.e. bit 0) in the "simplex" array is at the second smallest coordinate.
|
||||||
|
local i3 = band(Simplex[c][0], 1)
|
||||||
|
local j3 = band(Simplex[c][1], 1)
|
||||||
|
local k3 = band(Simplex[c][2], 1)
|
||||||
|
local l3 = band(Simplex[c][3], 1)
|
||||||
|
|
||||||
|
-- Work out the hashed gradient indices of the five simplex corners
|
||||||
|
-- Sum up and scale the result to cover the range [-1,1]
|
||||||
|
ix, iy, iz, iw = band(ix, 255), band(iy, 255), band(iz, 255), band(iw, 255)
|
||||||
|
|
||||||
|
local n0 = GetN4(ix, iy, iz, iw, x0, y0, z0, w0)
|
||||||
|
local n1 = GetN4(ix + i1, iy + j1, iz + k1, iw + l1, x0 + 0.138196601 - i1, y0 + 0.138196601 - j1, z0 + 0.138196601 - k1, w0 + 0.138196601 - l1) -- G
|
||||||
|
local n2 = GetN4(ix + i2, iy + j2, iz + k2, iw + l2, x0 + 0.276393202 - i2, y0 + 0.276393202 - j2, z0 + 0.276393202 - k2, w0 + 0.276393202 - l2) -- G2
|
||||||
|
local n3 = GetN4(ix + i3, iy + j3, iz + k3, iw + l3, x0 + 0.414589803 - i3, y0 + 0.414589803 - j3, z0 + 0.414589803 - k3, w0 + 0.414589803 - l3) -- G3
|
||||||
|
local n4 = GetN4(ix + 1, iy + 1, iz + 1, iw + 1, x0 - 0.447213595, y0 - 0.447213595, z0 - 0.447213595, w0 - 0.447213595) -- G4
|
||||||
|
|
||||||
|
return 2.210600293 * (n0 + n1 + n2 + n3 + n4)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Simplex Noise
|
||||||
|
-- @param x
|
||||||
|
-- @param y
|
||||||
|
-- @param z optional
|
||||||
|
-- @param w optional
|
||||||
|
-- @return Noise value in the range [-1, +1]
|
||||||
|
return function(x, y, z, w)
|
||||||
|
if w then
|
||||||
|
return simplex_4d(x, y, z, w)
|
||||||
|
end
|
||||||
|
if z then
|
||||||
|
return simplex_3d(x, y, z)
|
||||||
|
end
|
||||||
|
if y then
|
||||||
|
return simplex_2d(x, y)
|
||||||
|
end
|
||||||
|
error "Simplex requires at least two arguments"
|
||||||
|
end
|
||||||
228
libs/cpml/utils.lua
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
--- Various utility functions
|
||||||
|
-- @module utils
|
||||||
|
|
||||||
|
local modules = (...): gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local vec2 = require(modules .. "vec2")
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local private = require(modules .. "_private_utils")
|
||||||
|
local abs = math.abs
|
||||||
|
local ceil = math.ceil
|
||||||
|
local floor = math.floor
|
||||||
|
local log = math.log
|
||||||
|
local utils = {}
|
||||||
|
|
||||||
|
-- reimplementation of math.frexp, due to its removal from Lua 5.3 :(
|
||||||
|
-- courtesy of airstruck
|
||||||
|
local log2 = log(2)
|
||||||
|
|
||||||
|
local frexp = math.frexp or function(x)
|
||||||
|
if x == 0 then return 0, 0 end
|
||||||
|
local e = floor(log(abs(x)) / log2 + 1)
|
||||||
|
return x / 2 ^ e, e
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clamps a value within the specified range.
|
||||||
|
-- @param value Input value
|
||||||
|
-- @param min Minimum output value
|
||||||
|
-- @param max Maximum output value
|
||||||
|
-- @return number
|
||||||
|
function utils.clamp(value, min, max)
|
||||||
|
return math.max(math.min(value, max), min)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns `value` if it is equal or greater than |`size`|, or 0.
|
||||||
|
-- @param value
|
||||||
|
-- @param size
|
||||||
|
-- @return number
|
||||||
|
function utils.deadzone(value, size)
|
||||||
|
return abs(value) >= size and value or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if value is equal or greater than threshold.
|
||||||
|
-- @param value
|
||||||
|
-- @param threshold
|
||||||
|
-- @return boolean
|
||||||
|
function utils.threshold(value, threshold)
|
||||||
|
-- I know, it barely saves any typing at all.
|
||||||
|
return abs(value) >= threshold
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if value is equal or less than threshold.
|
||||||
|
-- @param value
|
||||||
|
-- @param threshold
|
||||||
|
-- @return boolean
|
||||||
|
function utils.tolerance(value, threshold)
|
||||||
|
-- I know, it barely saves any typing at all.
|
||||||
|
return abs(value) <= threshold
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scales a value from one range to another.
|
||||||
|
-- @param value Input value
|
||||||
|
-- @param min_in Minimum input value
|
||||||
|
-- @param max_in Maximum input value
|
||||||
|
-- @param min_out Minimum output value
|
||||||
|
-- @param max_out Maximum output value
|
||||||
|
-- @return number
|
||||||
|
function utils.map(value, min_in, max_in, min_out, max_out)
|
||||||
|
return ((value) - (min_in)) * ((max_out) - (min_out)) / ((max_in) - (min_in)) + (min_out)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Linear interpolation.
|
||||||
|
-- Performs linear interpolation between 0 and 1 when `low` < `progress` < `high`.
|
||||||
|
-- @param low value to return when `progress` is 0
|
||||||
|
-- @param high value to return when `progress` is 1
|
||||||
|
-- @param progress (0-1)
|
||||||
|
-- @return number
|
||||||
|
function utils.lerp(low, high, progress)
|
||||||
|
return low * (1 - progress) + high * progress
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Exponential decay
|
||||||
|
-- @param low initial value
|
||||||
|
-- @param high target value
|
||||||
|
-- @param rate portion of the original value remaining per second
|
||||||
|
-- @param dt time delta
|
||||||
|
-- @return number
|
||||||
|
function utils.decay(low, high, rate, dt)
|
||||||
|
return utils.lerp(low, high, 1.0 - math.exp(-rate * dt))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Hermite interpolation.
|
||||||
|
-- Performs smooth Hermite interpolation between 0 and 1 when `low` < `progress` < `high`.
|
||||||
|
-- @param progress (0-1)
|
||||||
|
-- @param low value to return when `progress` is 0
|
||||||
|
-- @param high value to return when `progress` is 1
|
||||||
|
-- @return number
|
||||||
|
function utils.smoothstep(progress, low, high)
|
||||||
|
local t = utils.clamp((progress - low) / (high - low), 0.0, 1.0)
|
||||||
|
return t * t * (3.0 - 2.0 * t)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Round number at a given precision.
|
||||||
|
-- Truncates `value` at `precision` points after the decimal (whole number if
|
||||||
|
-- left unspecified).
|
||||||
|
-- @param value
|
||||||
|
-- @param precision
|
||||||
|
-- @return number
|
||||||
|
utils.round = private.round
|
||||||
|
|
||||||
|
--- Wrap `value` around if it exceeds `limit`.
|
||||||
|
-- @param value
|
||||||
|
-- @param limit
|
||||||
|
-- @return number
|
||||||
|
function utils.wrap(value, limit)
|
||||||
|
if value < 0 then
|
||||||
|
value = value + utils.round(((-value/limit)+1))*limit
|
||||||
|
end
|
||||||
|
return value % limit
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if a value is a power-of-two.
|
||||||
|
-- Returns true if a number is a valid power-of-two, otherwise false.
|
||||||
|
-- @author undef
|
||||||
|
-- @param value
|
||||||
|
-- @return boolean
|
||||||
|
function utils.is_pot(value)
|
||||||
|
-- found here: https://love2d.org/forums/viewtopic.php?p=182219#p182219
|
||||||
|
-- check if a number is a power-of-two
|
||||||
|
return (frexp(value)) == 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if a value is NaN
|
||||||
|
-- Returns true if a number is not a valid number
|
||||||
|
-- @param value
|
||||||
|
-- @return boolean
|
||||||
|
utils.is_nan = private.is_nan
|
||||||
|
|
||||||
|
-- Originally from vec3
|
||||||
|
function utils.project_on(a, b)
|
||||||
|
local s =
|
||||||
|
(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /
|
||||||
|
(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0)
|
||||||
|
|
||||||
|
if a.z and b.z then
|
||||||
|
return vec3(
|
||||||
|
b.x * s,
|
||||||
|
b.y * s,
|
||||||
|
b.z * s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return vec2(
|
||||||
|
b.x * s,
|
||||||
|
b.y * s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Originally from vec3
|
||||||
|
function utils.project_from(a, b)
|
||||||
|
local s =
|
||||||
|
(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) /
|
||||||
|
(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0)
|
||||||
|
|
||||||
|
if a.z and b.z then
|
||||||
|
return vec3(
|
||||||
|
b.x * s,
|
||||||
|
b.y * s,
|
||||||
|
b.z * s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return vec2(
|
||||||
|
b.x * s,
|
||||||
|
b.y * s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Originally from vec3
|
||||||
|
function utils.mirror_on(a, b)
|
||||||
|
local s =
|
||||||
|
(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /
|
||||||
|
(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) * 2
|
||||||
|
|
||||||
|
if a.z and b.z then
|
||||||
|
return vec3(
|
||||||
|
b.x * s - a.x,
|
||||||
|
b.y * s - a.y,
|
||||||
|
b.z * s - a.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return vec2(
|
||||||
|
b.x * s - a.x,
|
||||||
|
b.y * s - a.y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Originally from vec3
|
||||||
|
function utils.reflect(i, n)
|
||||||
|
return i - (n * (2 * n:dot(i)))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Originally from vec3
|
||||||
|
function utils.refract(i, n, ior)
|
||||||
|
local d = n:dot(i)
|
||||||
|
local k = 1 - ior * ior * (1 - d * d)
|
||||||
|
|
||||||
|
if k >= 0 then
|
||||||
|
return (i * ior) - (n * (ior * d + k ^ 0.5))
|
||||||
|
end
|
||||||
|
|
||||||
|
return vec3()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the sign of a number
|
||||||
|
-- returns 1 for positive values, -1 for negative and 0 for zero.
|
||||||
|
-- @param value
|
||||||
|
-- @return number
|
||||||
|
function utils.sign(n)
|
||||||
|
if n > 0 then
|
||||||
|
return 1
|
||||||
|
elseif n < 0 then
|
||||||
|
return -1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return utils
|
||||||
444
libs/cpml/vec2.lua
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
--- A 2 component vector.
|
||||||
|
-- @module vec2
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local vec3 = require(modules .. "vec3")
|
||||||
|
local precond = require(modules .. "_private_precond")
|
||||||
|
local private = require(modules .. "_private_utils")
|
||||||
|
local acos = math.acos
|
||||||
|
local atan2 = math.atan2 or math.atan
|
||||||
|
local sqrt = math.sqrt
|
||||||
|
local cos = math.cos
|
||||||
|
local sin = math.sin
|
||||||
|
local vec2 = {}
|
||||||
|
local vec2_mt = {}
|
||||||
|
|
||||||
|
-- Private constructor.
|
||||||
|
local function new(x, y)
|
||||||
|
return setmetatable({
|
||||||
|
x = x or 0,
|
||||||
|
y = y or 0
|
||||||
|
}, vec2_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||||
|
local status, ffi
|
||||||
|
if type(jit) == "table" and jit.status() then
|
||||||
|
status, ffi = pcall(require, "ffi")
|
||||||
|
if status then
|
||||||
|
ffi.cdef "typedef struct { double x, y;} cpml_vec2;"
|
||||||
|
new = ffi.typeof("cpml_vec2")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Constants
|
||||||
|
-- @table vec2
|
||||||
|
-- @field unit_x X axis of rotation
|
||||||
|
-- @field unit_y Y axis of rotation
|
||||||
|
-- @field zero Empty vector
|
||||||
|
vec2.unit_x = new(1, 0)
|
||||||
|
vec2.unit_y = new(0, 1)
|
||||||
|
vec2.zero = new(0, 0)
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param x Can be of three types: </br>
|
||||||
|
-- number X component
|
||||||
|
-- table {x, y} or {x = x, y = y}
|
||||||
|
-- scalar to fill the vector eg. {x, x}
|
||||||
|
-- @tparam number y Y component
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.new(x, y)
|
||||||
|
-- number, number
|
||||||
|
if x and y then
|
||||||
|
precond.typeof(x, "number", "new: Wrong argument type for x")
|
||||||
|
precond.typeof(y, "number", "new: Wrong argument type for y")
|
||||||
|
|
||||||
|
return new(x, y)
|
||||||
|
|
||||||
|
-- {x, y} or {x=x, y=y}
|
||||||
|
elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
|
||||||
|
local xx, yy = x.x or x[1], x.y or x[2]
|
||||||
|
precond.typeof(xx, "number", "new: Wrong argument type for x")
|
||||||
|
precond.typeof(yy, "number", "new: Wrong argument type for y")
|
||||||
|
|
||||||
|
return new(xx, yy)
|
||||||
|
|
||||||
|
-- number
|
||||||
|
elseif type(x) == "number" then
|
||||||
|
return new(x, x)
|
||||||
|
else
|
||||||
|
return new()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert point from polar to cartesian.
|
||||||
|
-- @tparam number radius Radius of the point
|
||||||
|
-- @tparam number theta Angle of the point (in radians)
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.from_cartesian(radius, theta)
|
||||||
|
return new(radius * cos(theta), radius * sin(theta))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone a vector.
|
||||||
|
-- @tparam vec2 a Vector to be cloned
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.clone(a)
|
||||||
|
return new(a.x, a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.add(a, b)
|
||||||
|
return new(
|
||||||
|
a.x + b.x,
|
||||||
|
a.y + b.y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Subtract one vector from another.
|
||||||
|
-- Order: If a and b are positions, computes the direction and distance from b
|
||||||
|
-- to a.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.sub(a, b)
|
||||||
|
return new(
|
||||||
|
a.x - b.x,
|
||||||
|
a.y - b.y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a vector by another vector.
|
||||||
|
-- Component-size multiplication not matrix multiplication.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.mul(a, b)
|
||||||
|
return new(
|
||||||
|
a.x * b.x,
|
||||||
|
a.y * b.y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Divide a vector by another vector.
|
||||||
|
-- Component-size inv multiplication. Like a non-uniform scale().
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.div(a, b)
|
||||||
|
return new(
|
||||||
|
a.x / b.x,
|
||||||
|
a.y / b.y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the normal of a vector.
|
||||||
|
-- @tparam vec2 a Vector to normalize
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.normalize(a)
|
||||||
|
if a:is_zero() then
|
||||||
|
return new()
|
||||||
|
end
|
||||||
|
return a:scale(1 / a:len())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Trim a vector to a given length.
|
||||||
|
-- @tparam vec2 a Vector to be trimmed
|
||||||
|
-- @tparam number len Length to trim the vector to
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.trim(a, len)
|
||||||
|
return a:normalize():scale(math.min(a:len(), len))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the cross product of two vectors.
|
||||||
|
-- Order: Positive if a is clockwise from b. Magnitude is the area spanned by
|
||||||
|
-- the parallelograms that a and b span.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn number magnitude
|
||||||
|
function vec2.cross(a, b)
|
||||||
|
return a.x * b.y - a.y * b.x
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the dot product of two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn number dot
|
||||||
|
function vec2.dot(a, b)
|
||||||
|
return a.x * b.x + a.y * b.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the length of a vector.
|
||||||
|
-- @tparam vec2 a Vector to get the length of
|
||||||
|
-- @treturn number len
|
||||||
|
function vec2.len(a)
|
||||||
|
return sqrt(a.x * a.x + a.y * a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the squared length of a vector.
|
||||||
|
-- @tparam vec2 a Vector to get the squared length of
|
||||||
|
-- @treturn number len
|
||||||
|
function vec2.len2(a)
|
||||||
|
return a.x * a.x + a.y * a.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the distance between two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn number dist
|
||||||
|
function vec2.dist(a, b)
|
||||||
|
local dx = a.x - b.x
|
||||||
|
local dy = a.y - b.y
|
||||||
|
return sqrt(dx * dx + dy * dy)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the squared distance between two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn number dist
|
||||||
|
function vec2.dist2(a, b)
|
||||||
|
local dx = a.x - b.x
|
||||||
|
local dy = a.y - b.y
|
||||||
|
return dx * dx + dy * dy
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scale a vector by a scalar.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam number b Right hand operand
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.scale(a, b)
|
||||||
|
return new(
|
||||||
|
a.x * b,
|
||||||
|
a.y * b
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rotate a vector.
|
||||||
|
-- @tparam vec2 a Vector to rotate
|
||||||
|
-- @tparam number phi Angle to rotate vector by (in radians)
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.rotate(a, phi)
|
||||||
|
local c = cos(phi)
|
||||||
|
local s = sin(phi)
|
||||||
|
return new(
|
||||||
|
c * a.x - s * a.y,
|
||||||
|
s * a.x + c * a.y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the perpendicular vector of a vector.
|
||||||
|
-- @tparam vec2 a Vector to get perpendicular axes from
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.perpendicular(a)
|
||||||
|
return new(-a.y, a.x)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Signed angle from one vector to another.
|
||||||
|
-- Rotations from +x to +y are positive.
|
||||||
|
-- @tparam vec2 a Vector
|
||||||
|
-- @tparam vec2 b Vector
|
||||||
|
-- @treturn number angle in (-pi, pi]
|
||||||
|
function vec2.angle_to(a, b)
|
||||||
|
if b then
|
||||||
|
local angle = atan2(b.y, b.x) - atan2(a.y, a.x)
|
||||||
|
-- convert to (-pi, pi]
|
||||||
|
if angle > math.pi then
|
||||||
|
angle = angle - 2 * math.pi
|
||||||
|
elseif angle <= -math.pi then
|
||||||
|
angle = angle + 2 * math.pi
|
||||||
|
end
|
||||||
|
return angle
|
||||||
|
end
|
||||||
|
|
||||||
|
return atan2(a.y, a.x)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unsigned angle between two vectors.
|
||||||
|
-- Directionless and thus commutative.
|
||||||
|
-- @tparam vec2 a Vector
|
||||||
|
-- @tparam vec2 b Vector
|
||||||
|
-- @treturn number angle in [0, pi]
|
||||||
|
function vec2.angle_between(a, b)
|
||||||
|
if b then
|
||||||
|
if vec2.is_vec2(a) then
|
||||||
|
return acos(a:dot(b) / (a:len() * b:len()))
|
||||||
|
end
|
||||||
|
|
||||||
|
return acos(vec3.dot(a, b) / (vec3.len(a) * vec3.len(b)))
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Lerp between two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @tparam number s Step value
|
||||||
|
-- @treturn vec2 out
|
||||||
|
function vec2.lerp(a, b, s)
|
||||||
|
return a + (b - a) * s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unpack a vector into individual components.
|
||||||
|
-- @tparam vec2 a Vector to unpack
|
||||||
|
-- @treturn number x
|
||||||
|
-- @treturn number y
|
||||||
|
function vec2.unpack(a)
|
||||||
|
return a.x, a.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the component-wise minimum of two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.
|
||||||
|
function vec2.component_min(a, b)
|
||||||
|
return new(math.min(a.x, b.x), math.min(a.y, b.y))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the component-wise maximum of two vectors.
|
||||||
|
-- @tparam vec2 a Left hand operand
|
||||||
|
-- @tparam vec2 b Right hand operand
|
||||||
|
-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.
|
||||||
|
function vec2.component_max(a, b)
|
||||||
|
return new(math.max(a.x, b.x), math.max(a.y, b.y))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a vec2.
|
||||||
|
-- @tparam vec2 a Vector to be tested
|
||||||
|
-- @treturn boolean is_vec2
|
||||||
|
function vec2.is_vec2(a)
|
||||||
|
if type(a) == "cdata" then
|
||||||
|
return ffi.istype("cpml_vec2", a)
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
type(a) == "table" and
|
||||||
|
type(a.x) == "number" and
|
||||||
|
type(a.y) == "number"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a zero vec2.
|
||||||
|
-- @tparam vec2 a Vector to be tested
|
||||||
|
-- @treturn boolean is_zero
|
||||||
|
function vec2.is_zero(a)
|
||||||
|
return a.x == 0 and a.y == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return whether either value is NaN
|
||||||
|
-- @tparam vec2 a Vector to be tested
|
||||||
|
-- @treturn boolean if x or y is nan
|
||||||
|
function vec2.has_nan(a)
|
||||||
|
return private.is_nan(a.x) or
|
||||||
|
private.is_nan(a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Convert point from cartesian to polar.
|
||||||
|
-- @tparam vec2 a Vector to convert
|
||||||
|
-- @treturn number radius
|
||||||
|
-- @treturn number theta
|
||||||
|
function vec2.to_polar(a)
|
||||||
|
local radius = sqrt(a.x^2 + a.y^2)
|
||||||
|
local theta = atan2(a.y, a.x)
|
||||||
|
theta = theta > 0 and theta or theta + 2 * math.pi
|
||||||
|
return radius, theta
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Round all components to nearest int (or other precision).
|
||||||
|
-- @tparam vec2 a Vector to round.
|
||||||
|
-- @tparam precision Digits after the decimal (integer if unspecified)
|
||||||
|
-- @treturn vec2 Rounded vector
|
||||||
|
function vec2.round(a, precision)
|
||||||
|
return vec2.new(private.round(a.x, precision), private.round(a.y, precision))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Negate x axis only of vector.
|
||||||
|
-- @tparam vec2 a Vector to x-flip.
|
||||||
|
-- @treturn vec2 x-flipped vector
|
||||||
|
function vec2.flip_x(a)
|
||||||
|
return vec2.new(-a.x, a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Negate y axis only of vector.
|
||||||
|
-- @tparam vec2 a Vector to y-flip.
|
||||||
|
-- @treturn vec2 y-flipped vector
|
||||||
|
function vec2.flip_y(a)
|
||||||
|
return vec2.new(a.x, -a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert vec2 to vec3.
|
||||||
|
-- @tparam vec2 a Vector to convert.
|
||||||
|
-- @tparam number the new z component, or nil for 0
|
||||||
|
-- @treturn vec3 Converted vector
|
||||||
|
function vec2.to_vec3(a, z)
|
||||||
|
return vec3(a.x, a.y, z or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam vec2 a Vector to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function vec2.to_string(a)
|
||||||
|
return string.format("(%+0.3f,%+0.3f)", a.x, a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
vec2_mt.__index = vec2
|
||||||
|
vec2_mt.__tostring = vec2.to_string
|
||||||
|
|
||||||
|
function vec2_mt.__call(_, x, y)
|
||||||
|
return vec2.new(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2_mt.__unm(a)
|
||||||
|
return new(-a.x, -a.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2_mt.__eq(a, b)
|
||||||
|
if not vec2.is_vec2(a) or not vec2.is_vec2(b) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return a.x == b.x and a.y == b.y
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2_mt.__add(a, b)
|
||||||
|
precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||||
|
precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
|
||||||
|
return a:add(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2_mt.__sub(a, b)
|
||||||
|
precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||||
|
precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
|
||||||
|
return a:sub(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2_mt.__mul(a, b)
|
||||||
|
precond.assert(vec2.is_vec2(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||||
|
assert(vec2.is_vec2(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
|
||||||
|
|
||||||
|
if vec2.is_vec2(b) then
|
||||||
|
return a:mul(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a:scale(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2_mt.__div(a, b)
|
||||||
|
precond.assert(vec2.is_vec2(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||||
|
assert(vec2.is_vec2(b) or type(b) == "number", "__div: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
|
||||||
|
|
||||||
|
if vec2.is_vec2(b) then
|
||||||
|
return a:div(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a:scale(1 / b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||||
|
ffi.metatype(new, vec2_mt)
|
||||||
|
end, function() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, vec2_mt)
|
||||||
434
libs/cpml/vec3.lua
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
--- A 3 component vector.
|
||||||
|
-- @module vec3
|
||||||
|
|
||||||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||||
|
local precond = require(modules .. "_private_precond")
|
||||||
|
local private = require(modules .. "_private_utils")
|
||||||
|
local sqrt = math.sqrt
|
||||||
|
local cos = math.cos
|
||||||
|
local sin = math.sin
|
||||||
|
local vec3 = {}
|
||||||
|
local vec3_mt = {}
|
||||||
|
|
||||||
|
-- Private constructor.
|
||||||
|
local function new(x, y, z)
|
||||||
|
return setmetatable({
|
||||||
|
x = x or 0,
|
||||||
|
y = y or 0,
|
||||||
|
z = z or 0
|
||||||
|
}, vec3_mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||||
|
local status, ffi
|
||||||
|
if type(jit) == "table" and jit.status() then
|
||||||
|
status, ffi = pcall(require, "ffi")
|
||||||
|
if status then
|
||||||
|
ffi.cdef "typedef struct { double x, y, z;} cpml_vec3;"
|
||||||
|
new = ffi.typeof("cpml_vec3")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Constants
|
||||||
|
-- @table vec3
|
||||||
|
-- @field unit_x X axis of rotation
|
||||||
|
-- @field unit_y Y axis of rotation
|
||||||
|
-- @field unit_z Z axis of rotation
|
||||||
|
-- @field zero Empty vector
|
||||||
|
vec3.unit_x = new(1, 0, 0)
|
||||||
|
vec3.unit_y = new(0, 1, 0)
|
||||||
|
vec3.unit_z = new(0, 0, 1)
|
||||||
|
vec3.zero = new(0, 0, 0)
|
||||||
|
|
||||||
|
--- The public constructor.
|
||||||
|
-- @param x Can be of three types: </br>
|
||||||
|
-- number X component
|
||||||
|
-- table {x, y, z} or {x=x, y=y, z=z}
|
||||||
|
-- scalar To fill the vector eg. {x, x, x}
|
||||||
|
-- @tparam number y Y component
|
||||||
|
-- @tparam number z Z component
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.new(x, y, z)
|
||||||
|
-- number, number, number
|
||||||
|
if x and y and z then
|
||||||
|
precond.typeof(x, "number", "new: Wrong argument type for x")
|
||||||
|
precond.typeof(y, "number", "new: Wrong argument type for y")
|
||||||
|
precond.typeof(z, "number", "new: Wrong argument type for z")
|
||||||
|
|
||||||
|
return new(x, y, z)
|
||||||
|
|
||||||
|
-- {x, y, z} or {x=x, y=y, z=z}
|
||||||
|
elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
|
||||||
|
local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
|
||||||
|
precond.typeof(xx, "number", "new: Wrong argument type for x")
|
||||||
|
precond.typeof(yy, "number", "new: Wrong argument type for y")
|
||||||
|
precond.typeof(zz, "number", "new: Wrong argument type for z")
|
||||||
|
|
||||||
|
return new(xx, yy, zz)
|
||||||
|
|
||||||
|
-- number
|
||||||
|
elseif type(x) == "number" then
|
||||||
|
return new(x, x, x)
|
||||||
|
else
|
||||||
|
return new()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone a vector.
|
||||||
|
-- @tparam vec3 a Vector to be cloned
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.clone(a)
|
||||||
|
return new(a.x, a.y, a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.add(a, b)
|
||||||
|
return new(
|
||||||
|
a.x + b.x,
|
||||||
|
a.y + b.y,
|
||||||
|
a.z + b.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Subtract one vector from another.
|
||||||
|
-- Order: If a and b are positions, computes the direction and distance from b
|
||||||
|
-- to a.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.sub(a, b)
|
||||||
|
return new(
|
||||||
|
a.x - b.x,
|
||||||
|
a.y - b.y,
|
||||||
|
a.z - b.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Multiply a vector by another vector.
|
||||||
|
-- Component-wise multiplication not matrix multiplication.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.mul(a, b)
|
||||||
|
return new(
|
||||||
|
a.x * b.x,
|
||||||
|
a.y * b.y,
|
||||||
|
a.z * b.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Divide a vector by another.
|
||||||
|
-- Component-wise inv multiplication. Like a non-uniform scale().
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.div(a, b)
|
||||||
|
return new(
|
||||||
|
a.x / b.x,
|
||||||
|
a.y / b.y,
|
||||||
|
a.z / b.z
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scale a vector to unit length (1).
|
||||||
|
-- @tparam vec3 a vector to normalize
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.normalize(a)
|
||||||
|
if a:is_zero() then
|
||||||
|
return new()
|
||||||
|
end
|
||||||
|
return a:scale(1 / a:len())
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scale a vector to unit length (1), and return the input length.
|
||||||
|
-- @tparam vec3 a vector to normalize
|
||||||
|
-- @treturn vec3 out
|
||||||
|
-- @treturn number input vector length
|
||||||
|
function vec3.normalize_len(a)
|
||||||
|
if a:is_zero() then
|
||||||
|
return new(), 0
|
||||||
|
end
|
||||||
|
local len = a:len()
|
||||||
|
return a:scale(1 / len), len
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Trim a vector to a given length
|
||||||
|
-- @tparam vec3 a vector to be trimmed
|
||||||
|
-- @tparam number len Length to trim the vector to
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.trim(a, len)
|
||||||
|
return a:normalize():scale(math.min(a:len(), len))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the cross product of two vectors.
|
||||||
|
-- Resulting direction is right-hand rule normal of plane defined by a and b.
|
||||||
|
-- Magnitude is the area spanned by the parallelograms that a and b span.
|
||||||
|
-- Order: Direction determined by right-hand rule.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.cross(a, b)
|
||||||
|
return new(
|
||||||
|
a.y * b.z - a.z * b.y,
|
||||||
|
a.z * b.x - a.x * b.z,
|
||||||
|
a.x * b.y - a.y * b.x
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the dot product of two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn number dot
|
||||||
|
function vec3.dot(a, b)
|
||||||
|
return a.x * b.x + a.y * b.y + a.z * b.z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the length of a vector.
|
||||||
|
-- @tparam vec3 a Vector to get the length of
|
||||||
|
-- @treturn number len
|
||||||
|
function vec3.len(a)
|
||||||
|
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the squared length of a vector.
|
||||||
|
-- @tparam vec3 a Vector to get the squared length of
|
||||||
|
-- @treturn number len
|
||||||
|
function vec3.len2(a)
|
||||||
|
return a.x * a.x + a.y * a.y + a.z * a.z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the distance between two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn number dist
|
||||||
|
function vec3.dist(a, b)
|
||||||
|
local dx = a.x - b.x
|
||||||
|
local dy = a.y - b.y
|
||||||
|
local dz = a.z - b.z
|
||||||
|
return sqrt(dx * dx + dy * dy + dz * dz)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the squared distance between two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn number dist
|
||||||
|
function vec3.dist2(a, b)
|
||||||
|
local dx = a.x - b.x
|
||||||
|
local dy = a.y - b.y
|
||||||
|
local dz = a.z - b.z
|
||||||
|
return dx * dx + dy * dy + dz * dz
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Scale a vector by a scalar.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam number b Right hand operand
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.scale(a, b)
|
||||||
|
return new(
|
||||||
|
a.x * b,
|
||||||
|
a.y * b,
|
||||||
|
a.z * b
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rotate vector about an axis.
|
||||||
|
-- @tparam vec3 a Vector to rotate
|
||||||
|
-- @tparam number phi Angle to rotate vector by (in radians)
|
||||||
|
-- @tparam vec3 axis Axis to rotate by
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.rotate(a, phi, axis)
|
||||||
|
if not vec3.is_vec3(axis) then
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
local u = axis:normalize()
|
||||||
|
local c = cos(phi)
|
||||||
|
local s = sin(phi)
|
||||||
|
|
||||||
|
-- Calculate generalized rotation matrix
|
||||||
|
local m1 = new((c + u.x * u.x * (1 - c)), (u.x * u.y * (1 - c) - u.z * s), (u.x * u.z * (1 - c) + u.y * s))
|
||||||
|
local m2 = new((u.y * u.x * (1 - c) + u.z * s), (c + u.y * u.y * (1 - c)), (u.y * u.z * (1 - c) - u.x * s))
|
||||||
|
local m3 = new((u.z * u.x * (1 - c) - u.y * s), (u.z * u.y * (1 - c) + u.x * s), (c + u.z * u.z * (1 - c)) )
|
||||||
|
|
||||||
|
return new(
|
||||||
|
a:dot(m1),
|
||||||
|
a:dot(m2),
|
||||||
|
a:dot(m3)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the perpendicular vector of a vector.
|
||||||
|
-- @tparam vec3 a Vector to get perpendicular axes from
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.perpendicular(a)
|
||||||
|
return new(-a.y, a.x, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Lerp between two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @tparam number s Step value
|
||||||
|
-- @treturn vec3 out
|
||||||
|
function vec3.lerp(a, b, s)
|
||||||
|
return a + (b - a) * s
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Round all components to nearest int (or other precision).
|
||||||
|
-- @tparam vec3 a Vector to round.
|
||||||
|
-- @tparam precision Digits after the decimal (round numebr if unspecified)
|
||||||
|
-- @treturn vec3 Rounded vector
|
||||||
|
function vec3.round(a, precision)
|
||||||
|
return vec3.new(private.round(a.x, precision), private.round(a.y, precision), private.round(a.z, precision))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unpack a vector into individual components.
|
||||||
|
-- @tparam vec3 a Vector to unpack
|
||||||
|
-- @treturn number x
|
||||||
|
-- @treturn number y
|
||||||
|
-- @treturn number z
|
||||||
|
function vec3.unpack(a)
|
||||||
|
return a.x, a.y, a.z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the component-wise minimum of two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
|
||||||
|
function vec3.component_min(a, b)
|
||||||
|
return new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return the component-wise maximum of two vectors.
|
||||||
|
-- @tparam vec3 a Left hand operand
|
||||||
|
-- @tparam vec3 b Right hand operand
|
||||||
|
-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
|
||||||
|
function vec3.component_max(a, b)
|
||||||
|
return new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Negate x axis only of vector.
|
||||||
|
-- @tparam vec3 a Vector to x-flip.
|
||||||
|
-- @treturn vec3 x-flipped vector
|
||||||
|
function vec3.flip_x(a)
|
||||||
|
return vec3.new(-a.x, a.y, a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Negate y axis only of vector.
|
||||||
|
-- @tparam vec3 a Vector to y-flip.
|
||||||
|
-- @treturn vec3 y-flipped vector
|
||||||
|
function vec3.flip_y(a)
|
||||||
|
return vec3.new(a.x, -a.y, a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Negate z axis only of vector.
|
||||||
|
-- @tparam vec3 a Vector to z-flip.
|
||||||
|
-- @treturn vec3 z-flipped vector
|
||||||
|
function vec3.flip_z(a)
|
||||||
|
return vec3.new(a.x, a.y, -a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3.angle_to(a, b)
|
||||||
|
local v = a:normalize():dot(b:normalize())
|
||||||
|
return math.acos(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a vec3.
|
||||||
|
-- @tparam vec3 a Vector to be tested
|
||||||
|
-- @treturn boolean is_vec3
|
||||||
|
function vec3.is_vec3(a)
|
||||||
|
if type(a) == "cdata" then
|
||||||
|
return ffi.istype("cpml_vec3", a)
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
type(a) == "table" and
|
||||||
|
type(a.x) == "number" and
|
||||||
|
type(a.y) == "number" and
|
||||||
|
type(a.z) == "number"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a boolean showing if a table is or is not a zero vec3.
|
||||||
|
-- @tparam vec3 a Vector to be tested
|
||||||
|
-- @treturn boolean is_zero
|
||||||
|
function vec3.is_zero(a)
|
||||||
|
return a.x == 0 and a.y == 0 and a.z == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return whether any component is NaN
|
||||||
|
-- @tparam vec3 a Vector to be tested
|
||||||
|
-- @treturn boolean if x,y, or z are nan
|
||||||
|
function vec3.has_nan(a)
|
||||||
|
return private.is_nan(a.x) or
|
||||||
|
private.is_nan(a.y) or
|
||||||
|
private.is_nan(a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Return a formatted string.
|
||||||
|
-- @tparam vec3 a Vector to be turned into a string
|
||||||
|
-- @treturn string formatted
|
||||||
|
function vec3.to_string(a)
|
||||||
|
return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
vec3_mt.__index = vec3
|
||||||
|
vec3_mt.__tostring = vec3.to_string
|
||||||
|
|
||||||
|
function vec3_mt.__call(_, x, y, z)
|
||||||
|
return vec3.new(x, y, z)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3_mt.__unm(a)
|
||||||
|
return new(-a.x, -a.y, -a.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3_mt.__eq(a, b)
|
||||||
|
if not vec3.is_vec3(a) or not vec3.is_vec3(b) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return a.x == b.x and a.y == b.y and a.z == b.z
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3_mt.__add(a, b)
|
||||||
|
precond.assert(vec3.is_vec3(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||||
|
precond.assert(vec3.is_vec3(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
|
||||||
|
return a:add(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3_mt.__sub(a, b)
|
||||||
|
precond.assert(vec3.is_vec3(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||||
|
precond.assert(vec3.is_vec3(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
|
||||||
|
return a:sub(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3_mt.__mul(a, b)
|
||||||
|
precond.assert(vec3.is_vec3(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||||
|
precond.assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
|
||||||
|
|
||||||
|
if vec3.is_vec3(b) then
|
||||||
|
return a:mul(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a:scale(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec3_mt.__div(a, b)
|
||||||
|
precond.assert(vec3.is_vec3(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||||
|
precond.assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
|
||||||
|
|
||||||
|
if vec3.is_vec3(b) then
|
||||||
|
return a:div(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
return a:scale(1 / b)
|
||||||
|
end
|
||||||
|
|
||||||
|
if status then
|
||||||
|
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||||
|
ffi.metatype(new, vec3_mt)
|
||||||
|
end, function() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({}, vec3_mt)
|
||||||
1048
libs/discordGameSDK.lua
Normal file
13
libs/discordGameSDK/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## Discord Game SDK
|
||||||
|
|
||||||
|
> The SDK is currently under extensive development and is subject to change. Suggestions
|
||||||
|
> about the current API are welcome.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
- Create an application on the Discord [developer site](https://discordapp.com/developers/applications/me).
|
||||||
|
- Set a redirect URL. If you don't have one right now, just use <http://127.0.0.1>.
|
||||||
|
- Enable Rich Presence for the application. This enables whitelist access for the SDK.
|
||||||
|
- When you are ready to test with more people, add them to the whitelist.
|
||||||
|
- Copy the **Client ID**.
|
||||||
|
- Use this `CLIENT_ID` when initializing the SDK.
|
||||||
646
libs/discordGameSDK/c/discord_game_sdk.h
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
#ifndef _DISCORD_GAME_SDK_H_
|
||||||
|
#define _DISCORD_GAME_SDK_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#include <stdbool.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DISCORD_VERSION 2
|
||||||
|
#define DISCORD_APPLICATION_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_USER_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_IMAGE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_ACTIVITY_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_LOBBY_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_NETWORK_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_OVERLAY_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_STORAGE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_STORE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_VOICE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_ACHIEVEMENT_MANAGER_VERSION 1
|
||||||
|
|
||||||
|
enum EDiscordResult {
|
||||||
|
DiscordResult_Ok = 0,
|
||||||
|
DiscordResult_ServiceUnavailable = 1,
|
||||||
|
DiscordResult_InvalidVersion = 2,
|
||||||
|
DiscordResult_LockFailed = 3,
|
||||||
|
DiscordResult_InternalError = 4,
|
||||||
|
DiscordResult_InvalidPayload = 5,
|
||||||
|
DiscordResult_InvalidCommand = 6,
|
||||||
|
DiscordResult_InvalidPermissions = 7,
|
||||||
|
DiscordResult_NotFetched = 8,
|
||||||
|
DiscordResult_NotFound = 9,
|
||||||
|
DiscordResult_Conflict = 10,
|
||||||
|
DiscordResult_InvalidSecret = 11,
|
||||||
|
DiscordResult_InvalidJoinSecret = 12,
|
||||||
|
DiscordResult_NoEligibleActivity = 13,
|
||||||
|
DiscordResult_InvalidInvite = 14,
|
||||||
|
DiscordResult_NotAuthenticated = 15,
|
||||||
|
DiscordResult_InvalidAccessToken = 16,
|
||||||
|
DiscordResult_ApplicationMismatch = 17,
|
||||||
|
DiscordResult_InvalidDataUrl = 18,
|
||||||
|
DiscordResult_InvalidBase64 = 19,
|
||||||
|
DiscordResult_NotFiltered = 20,
|
||||||
|
DiscordResult_LobbyFull = 21,
|
||||||
|
DiscordResult_InvalidLobbySecret = 22,
|
||||||
|
DiscordResult_InvalidFilename = 23,
|
||||||
|
DiscordResult_InvalidFileSize = 24,
|
||||||
|
DiscordResult_InvalidEntitlement = 25,
|
||||||
|
DiscordResult_NotInstalled = 26,
|
||||||
|
DiscordResult_NotRunning = 27,
|
||||||
|
DiscordResult_InsufficientBuffer = 28,
|
||||||
|
DiscordResult_PurchaseCanceled = 29,
|
||||||
|
DiscordResult_InvalidGuild = 30,
|
||||||
|
DiscordResult_InvalidEvent = 31,
|
||||||
|
DiscordResult_InvalidChannel = 32,
|
||||||
|
DiscordResult_InvalidOrigin = 33,
|
||||||
|
DiscordResult_RateLimited = 34,
|
||||||
|
DiscordResult_OAuth2Error = 35,
|
||||||
|
DiscordResult_SelectChannelTimeout = 36,
|
||||||
|
DiscordResult_GetGuildTimeout = 37,
|
||||||
|
DiscordResult_SelectVoiceForceRequired = 38,
|
||||||
|
DiscordResult_CaptureShortcutAlreadyListening = 39,
|
||||||
|
DiscordResult_UnauthorizedForAchievement = 40,
|
||||||
|
DiscordResult_InvalidGiftCode = 41,
|
||||||
|
DiscordResult_PurchaseError = 42,
|
||||||
|
DiscordResult_TransactionAborted = 43,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordCreateFlags {
|
||||||
|
DiscordCreateFlags_Default = 0,
|
||||||
|
DiscordCreateFlags_NoRequireDiscord = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLogLevel {
|
||||||
|
DiscordLogLevel_Error = 1,
|
||||||
|
DiscordLogLevel_Warn,
|
||||||
|
DiscordLogLevel_Info,
|
||||||
|
DiscordLogLevel_Debug,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordUserFlag {
|
||||||
|
DiscordUserFlag_Partner = 2,
|
||||||
|
DiscordUserFlag_HypeSquadEvents = 4,
|
||||||
|
DiscordUserFlag_HypeSquadHouse1 = 64,
|
||||||
|
DiscordUserFlag_HypeSquadHouse2 = 128,
|
||||||
|
DiscordUserFlag_HypeSquadHouse3 = 256,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordPremiumType {
|
||||||
|
DiscordPremiumType_None = 0,
|
||||||
|
DiscordPremiumType_Tier1 = 1,
|
||||||
|
DiscordPremiumType_Tier2 = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordImageType {
|
||||||
|
DiscordImageType_User,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordActivityType {
|
||||||
|
DiscordActivityType_Playing,
|
||||||
|
DiscordActivityType_Streaming,
|
||||||
|
DiscordActivityType_Listening,
|
||||||
|
DiscordActivityType_Watching,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordActivityActionType {
|
||||||
|
DiscordActivityActionType_Join = 1,
|
||||||
|
DiscordActivityActionType_Spectate,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordActivityJoinRequestReply {
|
||||||
|
DiscordActivityJoinRequestReply_No,
|
||||||
|
DiscordActivityJoinRequestReply_Yes,
|
||||||
|
DiscordActivityJoinRequestReply_Ignore,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordStatus {
|
||||||
|
DiscordStatus_Offline = 0,
|
||||||
|
DiscordStatus_Online = 1,
|
||||||
|
DiscordStatus_Idle = 2,
|
||||||
|
DiscordStatus_DoNotDisturb = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordRelationshipType {
|
||||||
|
DiscordRelationshipType_None,
|
||||||
|
DiscordRelationshipType_Friend,
|
||||||
|
DiscordRelationshipType_Blocked,
|
||||||
|
DiscordRelationshipType_PendingIncoming,
|
||||||
|
DiscordRelationshipType_PendingOutgoing,
|
||||||
|
DiscordRelationshipType_Implicit,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbyType {
|
||||||
|
DiscordLobbyType_Private = 1,
|
||||||
|
DiscordLobbyType_Public,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbySearchComparison {
|
||||||
|
DiscordLobbySearchComparison_LessThanOrEqual = -2,
|
||||||
|
DiscordLobbySearchComparison_LessThan,
|
||||||
|
DiscordLobbySearchComparison_Equal,
|
||||||
|
DiscordLobbySearchComparison_GreaterThan,
|
||||||
|
DiscordLobbySearchComparison_GreaterThanOrEqual,
|
||||||
|
DiscordLobbySearchComparison_NotEqual,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbySearchCast {
|
||||||
|
DiscordLobbySearchCast_String = 1,
|
||||||
|
DiscordLobbySearchCast_Number,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbySearchDistance {
|
||||||
|
DiscordLobbySearchDistance_Local,
|
||||||
|
DiscordLobbySearchDistance_Default,
|
||||||
|
DiscordLobbySearchDistance_Extended,
|
||||||
|
DiscordLobbySearchDistance_Global,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordEntitlementType {
|
||||||
|
DiscordEntitlementType_Purchase = 1,
|
||||||
|
DiscordEntitlementType_PremiumSubscription,
|
||||||
|
DiscordEntitlementType_DeveloperGift,
|
||||||
|
DiscordEntitlementType_TestModePurchase,
|
||||||
|
DiscordEntitlementType_FreePurchase,
|
||||||
|
DiscordEntitlementType_UserGift,
|
||||||
|
DiscordEntitlementType_PremiumPurchase,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordSkuType {
|
||||||
|
DiscordSkuType_Application = 1,
|
||||||
|
DiscordSkuType_DLC,
|
||||||
|
DiscordSkuType_Consumable,
|
||||||
|
DiscordSkuType_Bundle,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordInputModeType {
|
||||||
|
DiscordInputModeType_VoiceActivity = 0,
|
||||||
|
DiscordInputModeType_PushToTalk,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef int64_t DiscordClientId;
|
||||||
|
typedef int32_t DiscordVersion;
|
||||||
|
typedef int64_t DiscordSnowflake;
|
||||||
|
typedef int64_t DiscordTimestamp;
|
||||||
|
typedef DiscordSnowflake DiscordUserId;
|
||||||
|
typedef char DiscordLocale[128];
|
||||||
|
typedef char DiscordBranch[4096];
|
||||||
|
typedef DiscordSnowflake DiscordLobbyId;
|
||||||
|
typedef char DiscordLobbySecret[128];
|
||||||
|
typedef char DiscordMetadataKey[256];
|
||||||
|
typedef char DiscordMetadataValue[4096];
|
||||||
|
typedef uint64_t DiscordNetworkPeerId;
|
||||||
|
typedef uint8_t DiscordNetworkChannelId;
|
||||||
|
typedef char DiscordPath[4096];
|
||||||
|
typedef char DiscordDateTime[64];
|
||||||
|
|
||||||
|
struct DiscordUser {
|
||||||
|
DiscordUserId id;
|
||||||
|
char username[256];
|
||||||
|
char discriminator[8];
|
||||||
|
char avatar[128];
|
||||||
|
bool bot;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordOAuth2Token {
|
||||||
|
char access_token[128];
|
||||||
|
char scopes[1024];
|
||||||
|
DiscordTimestamp expires;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordImageHandle {
|
||||||
|
enum EDiscordImageType type;
|
||||||
|
int64_t id;
|
||||||
|
uint32_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordImageDimensions {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivityTimestamps {
|
||||||
|
DiscordTimestamp start;
|
||||||
|
DiscordTimestamp end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivityAssets {
|
||||||
|
char large_image[128];
|
||||||
|
char large_text[128];
|
||||||
|
char small_image[128];
|
||||||
|
char small_text[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordPartySize {
|
||||||
|
int32_t current_size;
|
||||||
|
int32_t max_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivityParty {
|
||||||
|
char id[128];
|
||||||
|
struct DiscordPartySize size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivitySecrets {
|
||||||
|
char match[128];
|
||||||
|
char join[128];
|
||||||
|
char spectate[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivity {
|
||||||
|
enum EDiscordActivityType type;
|
||||||
|
int64_t application_id;
|
||||||
|
char name[128];
|
||||||
|
char state[128];
|
||||||
|
char details[128];
|
||||||
|
struct DiscordActivityTimestamps timestamps;
|
||||||
|
struct DiscordActivityAssets assets;
|
||||||
|
struct DiscordActivityParty party;
|
||||||
|
struct DiscordActivitySecrets secrets;
|
||||||
|
bool instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordPresence {
|
||||||
|
enum EDiscordStatus status;
|
||||||
|
struct DiscordActivity activity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordRelationship {
|
||||||
|
enum EDiscordRelationshipType type;
|
||||||
|
struct DiscordUser user;
|
||||||
|
struct DiscordPresence presence;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordLobby {
|
||||||
|
DiscordLobbyId id;
|
||||||
|
enum EDiscordLobbyType type;
|
||||||
|
DiscordUserId owner_id;
|
||||||
|
DiscordLobbySecret secret;
|
||||||
|
uint32_t capacity;
|
||||||
|
bool locked;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordFileStat {
|
||||||
|
char filename[260];
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t last_modified;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordEntitlement {
|
||||||
|
DiscordSnowflake id;
|
||||||
|
enum EDiscordEntitlementType type;
|
||||||
|
DiscordSnowflake sku_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordSkuPrice {
|
||||||
|
uint32_t amount;
|
||||||
|
char currency[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordSku {
|
||||||
|
DiscordSnowflake id;
|
||||||
|
enum EDiscordSkuType type;
|
||||||
|
char name[256];
|
||||||
|
struct DiscordSkuPrice price;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordInputMode {
|
||||||
|
enum EDiscordInputModeType type;
|
||||||
|
char shortcut[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordUserAchievement {
|
||||||
|
DiscordSnowflake user_id;
|
||||||
|
DiscordSnowflake achievement_id;
|
||||||
|
uint8_t percent_complete;
|
||||||
|
DiscordDateTime unlocked_at;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyTransaction {
|
||||||
|
enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction, enum EDiscordLobbyType type);
|
||||||
|
enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction, DiscordUserId owner_id);
|
||||||
|
enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction, uint32_t capacity);
|
||||||
|
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction, DiscordMetadataKey key, DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction, DiscordMetadataKey key);
|
||||||
|
enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction, bool locked);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyMemberTransaction {
|
||||||
|
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyMemberTransaction* lobby_member_transaction, DiscordMetadataKey key, DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyMemberTransaction* lobby_member_transaction, DiscordMetadataKey key);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbySearchQuery {
|
||||||
|
enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query, DiscordMetadataKey key, enum EDiscordLobbySearchComparison comparison, enum EDiscordLobbySearchCast cast, DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query, DiscordMetadataKey key, enum EDiscordLobbySearchCast cast, DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query, uint32_t limit);
|
||||||
|
enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query, enum EDiscordLobbySearchDistance distance);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordApplicationEvents;
|
||||||
|
|
||||||
|
struct IDiscordApplicationManager {
|
||||||
|
void (*validate_or_exit)(struct IDiscordApplicationManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale);
|
||||||
|
void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch);
|
||||||
|
void (*get_oauth2_token)(struct IDiscordApplicationManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordOAuth2Token* oauth2_token));
|
||||||
|
void (*get_ticket)(struct IDiscordApplicationManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, const char* data));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordUserEvents {
|
||||||
|
void (*on_current_user_update)(void* event_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordUserManager {
|
||||||
|
enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager, struct DiscordUser* current_user);
|
||||||
|
void (*get_user)(struct IDiscordUserManager* manager, DiscordUserId user_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordUser* user));
|
||||||
|
enum EDiscordResult (*get_current_user_premium_type)(struct IDiscordUserManager* manager, enum EDiscordPremiumType* premium_type);
|
||||||
|
enum EDiscordResult (*current_user_has_flag)(struct IDiscordUserManager* manager, enum EDiscordUserFlag flag, bool* has_flag);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordImageEvents;
|
||||||
|
|
||||||
|
struct IDiscordImageManager {
|
||||||
|
void (*fetch)(struct IDiscordImageManager* manager, struct DiscordImageHandle handle, bool refresh, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordImageHandle handle_result));
|
||||||
|
enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager, struct DiscordImageHandle handle, struct DiscordImageDimensions* dimensions);
|
||||||
|
enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager, struct DiscordImageHandle handle, uint8_t* data, uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordActivityEvents {
|
||||||
|
void (*on_activity_join)(void* event_data, const char* secret);
|
||||||
|
void (*on_activity_spectate)(void* event_data, const char* secret);
|
||||||
|
void (*on_activity_join_request)(void* event_data, struct DiscordUser* user);
|
||||||
|
void (*on_activity_invite)(void* event_data, enum EDiscordActivityActionType type, struct DiscordUser* user, struct DiscordActivity* activity);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordActivityManager {
|
||||||
|
enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager, const char* command);
|
||||||
|
enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager, uint32_t steam_id);
|
||||||
|
void (*update_activity)(struct IDiscordActivityManager* manager, struct DiscordActivity* activity, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*clear_activity)(struct IDiscordActivityManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*send_request_reply)(struct IDiscordActivityManager* manager, DiscordUserId user_id, enum EDiscordActivityJoinRequestReply reply, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*send_invite)(struct IDiscordActivityManager* manager, DiscordUserId user_id, enum EDiscordActivityActionType type, const char* content, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*accept_invite)(struct IDiscordActivityManager* manager, DiscordUserId user_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordRelationshipEvents {
|
||||||
|
void (*on_refresh)(void* event_data);
|
||||||
|
void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordRelationshipManager {
|
||||||
|
void (*filter)(struct IDiscordRelationshipManager* manager, void* filter_data, bool (*filter)(void* filter_data, struct DiscordRelationship* relationship));
|
||||||
|
enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager, DiscordUserId user_id, struct DiscordRelationship* relationship);
|
||||||
|
enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager, uint32_t index, struct DiscordRelationship* relationship);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyEvents {
|
||||||
|
void (*on_lobby_update)(void* event_data, int64_t lobby_id);
|
||||||
|
void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason);
|
||||||
|
void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||||
|
void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||||
|
void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||||
|
void (*on_lobby_message)(void* event_data, int64_t lobby_id, int64_t user_id, uint8_t* data, uint32_t data_length);
|
||||||
|
void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking);
|
||||||
|
void (*on_network_message)(void* event_data, int64_t lobby_id, int64_t user_id, uint8_t channel_id, uint8_t* data, uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyManager {
|
||||||
|
enum EDiscordResult (*get_lobby_create_transaction)(struct IDiscordLobbyManager* manager, struct IDiscordLobbyTransaction** transaction);
|
||||||
|
enum EDiscordResult (*get_lobby_update_transaction)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, struct IDiscordLobbyTransaction** transaction);
|
||||||
|
enum EDiscordResult (*get_member_update_transaction)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, struct IDiscordLobbyMemberTransaction** transaction);
|
||||||
|
void (*create_lobby)(struct IDiscordLobbyManager* manager, struct IDiscordLobbyTransaction* transaction, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordLobby* lobby));
|
||||||
|
void (*update_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, struct IDiscordLobbyTransaction* transaction, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*delete_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*connect_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordLobbySecret secret, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordLobby* lobby));
|
||||||
|
void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager, DiscordLobbySecret activity_secret, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordLobby* lobby));
|
||||||
|
void (*disconnect_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, struct DiscordLobby* lobby);
|
||||||
|
enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordLobbySecret* secret);
|
||||||
|
enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordMetadataKey key, DiscordMetadataValue* value);
|
||||||
|
enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t index, DiscordMetadataKey* key);
|
||||||
|
enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t* count);
|
||||||
|
enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t index, DiscordUserId* user_id);
|
||||||
|
enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, struct DiscordUser* user);
|
||||||
|
enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, DiscordMetadataKey key, DiscordMetadataValue* value);
|
||||||
|
enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, int32_t index, DiscordMetadataKey* key);
|
||||||
|
enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, int32_t* count);
|
||||||
|
void (*update_member)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, struct IDiscordLobbyMemberTransaction* transaction, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*send_lobby_message)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, uint8_t* data, uint32_t data_length, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager, struct IDiscordLobbySearchQuery** query);
|
||||||
|
void (*search)(struct IDiscordLobbyManager* manager, struct IDiscordLobbySearchQuery* query, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager, int32_t index, DiscordLobbyId* lobby_id);
|
||||||
|
void (*connect_voice)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*disconnect_voice)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id);
|
||||||
|
enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id);
|
||||||
|
enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager);
|
||||||
|
enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, uint8_t channel_id, bool reliable);
|
||||||
|
enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, uint8_t channel_id, uint8_t* data, uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordNetworkEvents {
|
||||||
|
void (*on_message)(void* event_data, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id, uint8_t* data, uint32_t data_length);
|
||||||
|
void (*on_route_update)(void* event_data, const char* route_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordNetworkManager {
|
||||||
|
/**
|
||||||
|
* Get the local peer ID for this process.
|
||||||
|
*/
|
||||||
|
void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id);
|
||||||
|
/**
|
||||||
|
* Send pending network messages.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager);
|
||||||
|
/**
|
||||||
|
* Open a connection to a remote peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, const char* route_data);
|
||||||
|
/**
|
||||||
|
* Update the route data for a connected peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, const char* route_data);
|
||||||
|
/**
|
||||||
|
* Close the connection to a remote peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id);
|
||||||
|
/**
|
||||||
|
* Open a message channel to a connected peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id, bool reliable);
|
||||||
|
/**
|
||||||
|
* Close a message channel to a connected peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id);
|
||||||
|
/**
|
||||||
|
* Send a message to a connected peer over an opened message channel.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id, uint8_t* data, uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordOverlayEvents {
|
||||||
|
void (*on_toggle)(void* event_data, bool locked);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordOverlayManager {
|
||||||
|
void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled);
|
||||||
|
void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked);
|
||||||
|
void (*set_locked)(struct IDiscordOverlayManager* manager, bool locked, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*open_activity_invite)(struct IDiscordOverlayManager* manager, enum EDiscordActivityActionType type, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*open_guild_invite)(struct IDiscordOverlayManager* manager, const char* code, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*open_voice_settings)(struct IDiscordOverlayManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordStorageEvents;
|
||||||
|
|
||||||
|
struct IDiscordStorageManager {
|
||||||
|
enum EDiscordResult (*read)(struct IDiscordStorageManager* manager, const char* name, uint8_t* data, uint32_t data_length, uint32_t* read);
|
||||||
|
void (*read_async)(struct IDiscordStorageManager* manager, const char* name, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, uint8_t* data, uint32_t data_length));
|
||||||
|
void (*read_async_partial)(struct IDiscordStorageManager* manager, const char* name, uint64_t offset, uint64_t length, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, uint8_t* data, uint32_t data_length));
|
||||||
|
enum EDiscordResult (*write)(struct IDiscordStorageManager* manager, const char* name, uint8_t* data, uint32_t data_length);
|
||||||
|
void (*write_async)(struct IDiscordStorageManager* manager, const char* name, uint8_t* data, uint32_t data_length, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name);
|
||||||
|
enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager, const char* name, bool* exists);
|
||||||
|
void (*count)(struct IDiscordStorageManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager, const char* name, struct DiscordFileStat* stat);
|
||||||
|
enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager, int32_t index, struct DiscordFileStat* stat);
|
||||||
|
enum EDiscordResult (*get_path)(struct IDiscordStorageManager* manager, DiscordPath* path);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordStoreEvents {
|
||||||
|
void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||||
|
void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordStoreManager {
|
||||||
|
void (*fetch_skus)(struct IDiscordStoreManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager, DiscordSnowflake sku_id, struct DiscordSku* sku);
|
||||||
|
enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager, int32_t index, struct DiscordSku* sku);
|
||||||
|
void (*fetch_entitlements)(struct IDiscordStoreManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager, DiscordSnowflake entitlement_id, struct DiscordEntitlement* entitlement);
|
||||||
|
enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager, int32_t index, struct DiscordEntitlement* entitlement);
|
||||||
|
enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager, DiscordSnowflake sku_id, bool* has_entitlement);
|
||||||
|
void (*start_purchase)(struct IDiscordStoreManager* manager, DiscordSnowflake sku_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordVoiceEvents {
|
||||||
|
void (*on_settings_update)(void* event_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordVoiceManager {
|
||||||
|
enum EDiscordResult (*get_input_mode)(struct IDiscordVoiceManager* manager, struct DiscordInputMode* input_mode);
|
||||||
|
void (*set_input_mode)(struct IDiscordVoiceManager* manager, struct DiscordInputMode input_mode, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*is_self_mute)(struct IDiscordVoiceManager* manager, bool* mute);
|
||||||
|
enum EDiscordResult (*set_self_mute)(struct IDiscordVoiceManager* manager, bool mute);
|
||||||
|
enum EDiscordResult (*is_self_deaf)(struct IDiscordVoiceManager* manager, bool* deaf);
|
||||||
|
enum EDiscordResult (*set_self_deaf)(struct IDiscordVoiceManager* manager, bool deaf);
|
||||||
|
enum EDiscordResult (*is_local_mute)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, bool* mute);
|
||||||
|
enum EDiscordResult (*set_local_mute)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, bool mute);
|
||||||
|
enum EDiscordResult (*get_local_volume)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, uint8_t* volume);
|
||||||
|
enum EDiscordResult (*set_local_volume)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, uint8_t volume);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordAchievementEvents {
|
||||||
|
void (*on_user_achievement_update)(void* event_data, struct DiscordUserAchievement* user_achievement);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordAchievementManager {
|
||||||
|
void (*set_user_achievement)(struct IDiscordAchievementManager* manager, DiscordSnowflake achievement_id, uint8_t percent_complete, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*fetch_user_achievements)(struct IDiscordAchievementManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*count_user_achievements)(struct IDiscordAchievementManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_user_achievement)(struct IDiscordAchievementManager* manager, DiscordSnowflake user_achievement_id, struct DiscordUserAchievement* user_achievement);
|
||||||
|
enum EDiscordResult (*get_user_achievement_at)(struct IDiscordAchievementManager* manager, int32_t index, struct DiscordUserAchievement* user_achievement);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordCoreEvents;
|
||||||
|
|
||||||
|
struct IDiscordCore {
|
||||||
|
void (*destroy)(struct IDiscordCore* core);
|
||||||
|
enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core);
|
||||||
|
void (*set_log_hook)(struct IDiscordCore* core, enum EDiscordLogLevel min_level, void* hook_data, void (*hook)(void* hook_data, enum EDiscordLogLevel level, const char* message));
|
||||||
|
struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordVoiceManager* (*get_voice_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordAchievementManager* (*get_achievement_manager)(struct IDiscordCore* core);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordCreateParams {
|
||||||
|
DiscordClientId client_id;
|
||||||
|
uint64_t flags;
|
||||||
|
IDiscordCoreEvents* events;
|
||||||
|
void* event_data;
|
||||||
|
IDiscordApplicationEvents* application_events;
|
||||||
|
DiscordVersion application_version;
|
||||||
|
struct IDiscordUserEvents* user_events;
|
||||||
|
DiscordVersion user_version;
|
||||||
|
IDiscordImageEvents* image_events;
|
||||||
|
DiscordVersion image_version;
|
||||||
|
struct IDiscordActivityEvents* activity_events;
|
||||||
|
DiscordVersion activity_version;
|
||||||
|
struct IDiscordRelationshipEvents* relationship_events;
|
||||||
|
DiscordVersion relationship_version;
|
||||||
|
struct IDiscordLobbyEvents* lobby_events;
|
||||||
|
DiscordVersion lobby_version;
|
||||||
|
struct IDiscordNetworkEvents* network_events;
|
||||||
|
DiscordVersion network_version;
|
||||||
|
struct IDiscordOverlayEvents* overlay_events;
|
||||||
|
DiscordVersion overlay_version;
|
||||||
|
IDiscordStorageEvents* storage_events;
|
||||||
|
DiscordVersion storage_version;
|
||||||
|
struct IDiscordStoreEvents* store_events;
|
||||||
|
DiscordVersion store_version;
|
||||||
|
struct IDiscordVoiceEvents* voice_events;
|
||||||
|
DiscordVersion voice_version;
|
||||||
|
struct IDiscordAchievementEvents* achievement_events;
|
||||||
|
DiscordVersion achievement_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
inline
|
||||||
|
#else
|
||||||
|
static
|
||||||
|
#endif
|
||||||
|
void DiscordCreateParamsSetDefault(struct DiscordCreateParams* params)
|
||||||
|
{
|
||||||
|
memset(params, 0, sizeof(struct DiscordCreateParams));
|
||||||
|
params->application_version = DISCORD_APPLICATION_MANAGER_VERSION;
|
||||||
|
params->user_version = DISCORD_USER_MANAGER_VERSION;
|
||||||
|
params->image_version = DISCORD_IMAGE_MANAGER_VERSION;
|
||||||
|
params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION;
|
||||||
|
params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION;
|
||||||
|
params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION;
|
||||||
|
params->network_version = DISCORD_NETWORK_MANAGER_VERSION;
|
||||||
|
params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION;
|
||||||
|
params->storage_version = DISCORD_STORAGE_MANAGER_VERSION;
|
||||||
|
params->store_version = DISCORD_STORE_MANAGER_VERSION;
|
||||||
|
params->voice_version = DISCORD_VOICE_MANAGER_VERSION;
|
||||||
|
params->achievement_version = DISCORD_ACHIEVEMENT_MANAGER_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EDiscordResult DiscordCreate(DiscordVersion version, struct DiscordCreateParams* params, struct IDiscordCore** result);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
98
libs/discordGameSDK/cpp/achievement_manager.cpp
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "achievement_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class AchievementEvents final {
|
||||||
|
public:
|
||||||
|
static void OnUserAchievementUpdate(void* callbackData, DiscordUserAchievement* userAchievement)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->AchievementManager();
|
||||||
|
module.OnUserAchievementUpdate(*reinterpret_cast<UserAchievement const*>(userAchievement));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordAchievementEvents AchievementManager::events_{
|
||||||
|
&AchievementEvents::OnUserAchievementUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
void AchievementManager::SetUserAchievement(Snowflake achievementId,
|
||||||
|
std::uint8_t percentComplete,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->set_user_achievement(
|
||||||
|
internal_, achievementId, percentComplete, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementManager::FetchUserAchievements(std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->fetch_user_achievements(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AchievementManager::CountUserAchievements(std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->count_user_achievements(internal_, reinterpret_cast<int32_t*>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AchievementManager::GetUserAchievement(Snowflake userAchievementId,
|
||||||
|
UserAchievement* userAchievement)
|
||||||
|
{
|
||||||
|
if (!userAchievement) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_user_achievement(
|
||||||
|
internal_, userAchievementId, reinterpret_cast<DiscordUserAchievement*>(userAchievement));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AchievementManager::GetUserAchievementAt(std::int32_t index,
|
||||||
|
UserAchievement* userAchievement)
|
||||||
|
{
|
||||||
|
if (!userAchievement) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_user_achievement_at(
|
||||||
|
internal_, index, reinterpret_cast<DiscordUserAchievement*>(userAchievement));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
34
libs/discordGameSDK/cpp/achievement_manager.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class AchievementManager final {
|
||||||
|
public:
|
||||||
|
~AchievementManager() = default;
|
||||||
|
|
||||||
|
void SetUserAchievement(Snowflake achievementId,
|
||||||
|
std::uint8_t percentComplete,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
void FetchUserAchievements(std::function<void(Result)> callback);
|
||||||
|
void CountUserAchievements(std::int32_t* count);
|
||||||
|
Result GetUserAchievement(Snowflake userAchievementId, UserAchievement* userAchievement);
|
||||||
|
Result GetUserAchievementAt(std::int32_t index, UserAchievement* userAchievement);
|
||||||
|
|
||||||
|
Event<UserAchievement const&> OnUserAchievementUpdate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
AchievementManager() = default;
|
||||||
|
AchievementManager(AchievementManager const& rhs) = delete;
|
||||||
|
AchievementManager& operator=(AchievementManager const& rhs) = delete;
|
||||||
|
AchievementManager(AchievementManager&& rhs) = delete;
|
||||||
|
AchievementManager& operator=(AchievementManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordAchievementManager* internal_;
|
||||||
|
static IDiscordAchievementEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
177
libs/discordGameSDK/cpp/activity_manager.cpp
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "activity_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class ActivityEvents final {
|
||||||
|
public:
|
||||||
|
static void OnActivityJoin(void* callbackData, char const* secret)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->ActivityManager();
|
||||||
|
module.OnActivityJoin(static_cast<const char*>(secret));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnActivitySpectate(void* callbackData, char const* secret)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->ActivityManager();
|
||||||
|
module.OnActivitySpectate(static_cast<const char*>(secret));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnActivityJoinRequest(void* callbackData, DiscordUser* user)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->ActivityManager();
|
||||||
|
module.OnActivityJoinRequest(*reinterpret_cast<User const*>(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnActivityInvite(void* callbackData,
|
||||||
|
EDiscordActivityActionType type,
|
||||||
|
DiscordUser* user,
|
||||||
|
DiscordActivity* activity)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->ActivityManager();
|
||||||
|
module.OnActivityInvite(static_cast<ActivityActionType>(type),
|
||||||
|
*reinterpret_cast<User const*>(user),
|
||||||
|
*reinterpret_cast<Activity const*>(activity));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordActivityEvents ActivityManager::events_{
|
||||||
|
&ActivityEvents::OnActivityJoin,
|
||||||
|
&ActivityEvents::OnActivitySpectate,
|
||||||
|
&ActivityEvents::OnActivityJoinRequest,
|
||||||
|
&ActivityEvents::OnActivityInvite,
|
||||||
|
};
|
||||||
|
|
||||||
|
Result ActivityManager::RegisterCommand(char const* command)
|
||||||
|
{
|
||||||
|
auto result = internal_->register_command(internal_, const_cast<char*>(command));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ActivityManager::RegisterSteam(std::uint32_t steamId)
|
||||||
|
{
|
||||||
|
auto result = internal_->register_steam(internal_, steamId);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityManager::UpdateActivity(Activity const& activity, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->update_activity(internal_,
|
||||||
|
reinterpret_cast<DiscordActivity*>(const_cast<Activity*>(&activity)),
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityManager::ClearActivity(std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->clear_activity(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityManager::SendRequestReply(UserId userId,
|
||||||
|
ActivityJoinRequestReply reply,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->send_request_reply(internal_,
|
||||||
|
userId,
|
||||||
|
static_cast<EDiscordActivityJoinRequestReply>(reply),
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityManager::SendInvite(UserId userId,
|
||||||
|
ActivityActionType type,
|
||||||
|
char const* content,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->send_invite(internal_,
|
||||||
|
userId,
|
||||||
|
static_cast<EDiscordActivityActionType>(type),
|
||||||
|
const_cast<char*>(content),
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityManager::AcceptInvite(UserId userId, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->accept_invite(internal_, userId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
42
libs/discordGameSDK/cpp/activity_manager.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class ActivityManager final {
|
||||||
|
public:
|
||||||
|
~ActivityManager() = default;
|
||||||
|
|
||||||
|
Result RegisterCommand(char const* command);
|
||||||
|
Result RegisterSteam(std::uint32_t steamId);
|
||||||
|
void UpdateActivity(Activity const& activity, std::function<void(Result)> callback);
|
||||||
|
void ClearActivity(std::function<void(Result)> callback);
|
||||||
|
void SendRequestReply(UserId userId,
|
||||||
|
ActivityJoinRequestReply reply,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
void SendInvite(UserId userId,
|
||||||
|
ActivityActionType type,
|
||||||
|
char const* content,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
void AcceptInvite(UserId userId, std::function<void(Result)> callback);
|
||||||
|
|
||||||
|
Event<char const*> OnActivityJoin;
|
||||||
|
Event<char const*> OnActivitySpectate;
|
||||||
|
Event<User const&> OnActivityJoinRequest;
|
||||||
|
Event<ActivityActionType, User const&, Activity const&> OnActivityInvite;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
ActivityManager() = default;
|
||||||
|
ActivityManager(ActivityManager const& rhs) = delete;
|
||||||
|
ActivityManager& operator=(ActivityManager const& rhs) = delete;
|
||||||
|
ActivityManager(ActivityManager&& rhs) = delete;
|
||||||
|
ActivityManager& operator=(ActivityManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordActivityManager* internal_;
|
||||||
|
static IDiscordActivityEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
78
libs/discordGameSDK/cpp/application_manager.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "application_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
void ApplicationManager::ValidateOrExit(std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->validate_or_exit(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationManager::GetCurrentLocale(char locale[128])
|
||||||
|
{
|
||||||
|
if (!locale) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->get_current_locale(internal_, reinterpret_cast<DiscordLocale*>(locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationManager::GetCurrentBranch(char branch[4096])
|
||||||
|
{
|
||||||
|
if (!branch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->get_current_branch(internal_, reinterpret_cast<DiscordBranch*>(branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationManager::GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, DiscordOAuth2Token* oauth2Token) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, OAuth2Token const&)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), *reinterpret_cast<OAuth2Token const*>(oauth2Token));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, OAuth2Token const&)>(std::move(callback)));
|
||||||
|
internal_->get_oauth2_token(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationManager::GetTicket(std::function<void(Result, char const*)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result, char const* data) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, char const*)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, char const*)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), static_cast<const char*>(data));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, char const*)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, char const*)>(std::move(callback)));
|
||||||
|
internal_->get_ticket(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
30
libs/discordGameSDK/cpp/application_manager.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class ApplicationManager final {
|
||||||
|
public:
|
||||||
|
~ApplicationManager() = default;
|
||||||
|
|
||||||
|
void ValidateOrExit(std::function<void(Result)> callback);
|
||||||
|
void GetCurrentLocale(char locale[128]);
|
||||||
|
void GetCurrentBranch(char branch[4096]);
|
||||||
|
void GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback);
|
||||||
|
void GetTicket(std::function<void(Result, char const*)> callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
ApplicationManager() = default;
|
||||||
|
ApplicationManager(ApplicationManager const& rhs) = delete;
|
||||||
|
ApplicationManager& operator=(ApplicationManager const& rhs) = delete;
|
||||||
|
ApplicationManager(ApplicationManager&& rhs) = delete;
|
||||||
|
ApplicationManager& operator=(ApplicationManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordApplicationManager* internal_;
|
||||||
|
static IDiscordApplicationEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
182
libs/discordGameSDK/cpp/core.cpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
Result Core::Create(ClientId clientId, std::uint64_t flags, Core** instance)
|
||||||
|
{
|
||||||
|
if (!instance) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*instance) = new Core();
|
||||||
|
DiscordCreateParams params{};
|
||||||
|
DiscordCreateParamsSetDefault(¶ms);
|
||||||
|
params.client_id = clientId;
|
||||||
|
params.flags = flags;
|
||||||
|
params.events = nullptr;
|
||||||
|
params.event_data = *instance;
|
||||||
|
params.user_events = &UserManager::events_;
|
||||||
|
params.activity_events = &ActivityManager::events_;
|
||||||
|
params.relationship_events = &RelationshipManager::events_;
|
||||||
|
params.lobby_events = &LobbyManager::events_;
|
||||||
|
params.network_events = &NetworkManager::events_;
|
||||||
|
params.overlay_events = &OverlayManager::events_;
|
||||||
|
params.store_events = &StoreManager::events_;
|
||||||
|
params.voice_events = &VoiceManager::events_;
|
||||||
|
params.achievement_events = &AchievementManager::events_;
|
||||||
|
auto result = DiscordCreate(DISCORD_VERSION, ¶ms, &((*instance)->internal_));
|
||||||
|
if (result != DiscordResult_Ok || !(*instance)->internal_) {
|
||||||
|
delete (*instance);
|
||||||
|
(*instance) = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::~Core()
|
||||||
|
{
|
||||||
|
if (internal_) {
|
||||||
|
internal_->destroy(internal_);
|
||||||
|
internal_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Core::RunCallbacks()
|
||||||
|
{
|
||||||
|
auto result = internal_->run_callbacks(internal_);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Core::SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook)
|
||||||
|
{
|
||||||
|
setLogHook_.DisconnectAll();
|
||||||
|
setLogHook_.Connect(std::move(hook));
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordLogLevel level, char const* message) -> void {
|
||||||
|
auto cb(reinterpret_cast<decltype(setLogHook_)*>(callbackData));
|
||||||
|
if (!cb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<LogLevel>(level), static_cast<const char*>(message));
|
||||||
|
};
|
||||||
|
|
||||||
|
internal_->set_log_hook(
|
||||||
|
internal_, static_cast<EDiscordLogLevel>(minLevel), &setLogHook_, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::ApplicationManager& Core::ApplicationManager()
|
||||||
|
{
|
||||||
|
if (!applicationManager_.internal_) {
|
||||||
|
applicationManager_.internal_ = internal_->get_application_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::UserManager& Core::UserManager()
|
||||||
|
{
|
||||||
|
if (!userManager_.internal_) {
|
||||||
|
userManager_.internal_ = internal_->get_user_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::ImageManager& Core::ImageManager()
|
||||||
|
{
|
||||||
|
if (!imageManager_.internal_) {
|
||||||
|
imageManager_.internal_ = internal_->get_image_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::ActivityManager& Core::ActivityManager()
|
||||||
|
{
|
||||||
|
if (!activityManager_.internal_) {
|
||||||
|
activityManager_.internal_ = internal_->get_activity_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return activityManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::RelationshipManager& Core::RelationshipManager()
|
||||||
|
{
|
||||||
|
if (!relationshipManager_.internal_) {
|
||||||
|
relationshipManager_.internal_ = internal_->get_relationship_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return relationshipManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::LobbyManager& Core::LobbyManager()
|
||||||
|
{
|
||||||
|
if (!lobbyManager_.internal_) {
|
||||||
|
lobbyManager_.internal_ = internal_->get_lobby_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lobbyManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::NetworkManager& Core::NetworkManager()
|
||||||
|
{
|
||||||
|
if (!networkManager_.internal_) {
|
||||||
|
networkManager_.internal_ = internal_->get_network_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::OverlayManager& Core::OverlayManager()
|
||||||
|
{
|
||||||
|
if (!overlayManager_.internal_) {
|
||||||
|
overlayManager_.internal_ = internal_->get_overlay_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlayManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::StorageManager& Core::StorageManager()
|
||||||
|
{
|
||||||
|
if (!storageManager_.internal_) {
|
||||||
|
storageManager_.internal_ = internal_->get_storage_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::StoreManager& Core::StoreManager()
|
||||||
|
{
|
||||||
|
if (!storeManager_.internal_) {
|
||||||
|
storeManager_.internal_ = internal_->get_store_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return storeManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::VoiceManager& Core::VoiceManager()
|
||||||
|
{
|
||||||
|
if (!voiceManager_.internal_) {
|
||||||
|
voiceManager_.internal_ = internal_->get_voice_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return voiceManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::AchievementManager& Core::AchievementManager()
|
||||||
|
{
|
||||||
|
if (!achievementManager_.internal_) {
|
||||||
|
achievementManager_.internal_ = internal_->get_achievement_manager(internal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return achievementManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
64
libs/discordGameSDK/cpp/core.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "application_manager.h"
|
||||||
|
#include "user_manager.h"
|
||||||
|
#include "image_manager.h"
|
||||||
|
#include "activity_manager.h"
|
||||||
|
#include "relationship_manager.h"
|
||||||
|
#include "lobby_manager.h"
|
||||||
|
#include "network_manager.h"
|
||||||
|
#include "overlay_manager.h"
|
||||||
|
#include "storage_manager.h"
|
||||||
|
#include "store_manager.h"
|
||||||
|
#include "voice_manager.h"
|
||||||
|
#include "achievement_manager.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class Core final {
|
||||||
|
public:
|
||||||
|
static Result Create(ClientId clientId, std::uint64_t flags, Core** instance);
|
||||||
|
|
||||||
|
~Core();
|
||||||
|
|
||||||
|
Result RunCallbacks();
|
||||||
|
void SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook);
|
||||||
|
|
||||||
|
discord::ApplicationManager& ApplicationManager();
|
||||||
|
discord::UserManager& UserManager();
|
||||||
|
discord::ImageManager& ImageManager();
|
||||||
|
discord::ActivityManager& ActivityManager();
|
||||||
|
discord::RelationshipManager& RelationshipManager();
|
||||||
|
discord::LobbyManager& LobbyManager();
|
||||||
|
discord::NetworkManager& NetworkManager();
|
||||||
|
discord::OverlayManager& OverlayManager();
|
||||||
|
discord::StorageManager& StorageManager();
|
||||||
|
discord::StoreManager& StoreManager();
|
||||||
|
discord::VoiceManager& VoiceManager();
|
||||||
|
discord::AchievementManager& AchievementManager();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Core() = default;
|
||||||
|
Core(Core const& rhs) = delete;
|
||||||
|
Core& operator=(Core const& rhs) = delete;
|
||||||
|
Core(Core&& rhs) = delete;
|
||||||
|
Core& operator=(Core&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordCore* internal_;
|
||||||
|
Event<LogLevel, char const*> setLogHook_;
|
||||||
|
discord::ApplicationManager applicationManager_;
|
||||||
|
discord::UserManager userManager_;
|
||||||
|
discord::ImageManager imageManager_;
|
||||||
|
discord::ActivityManager activityManager_;
|
||||||
|
discord::RelationshipManager relationshipManager_;
|
||||||
|
discord::LobbyManager lobbyManager_;
|
||||||
|
discord::NetworkManager networkManager_;
|
||||||
|
discord::OverlayManager overlayManager_;
|
||||||
|
discord::StorageManager storageManager_;
|
||||||
|
discord::StoreManager storeManager_;
|
||||||
|
discord::VoiceManager voiceManager_;
|
||||||
|
discord::AchievementManager achievementManager_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
16
libs/discordGameSDK/cpp/discord.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "core.h"
|
||||||
|
#include "application_manager.h"
|
||||||
|
#include "user_manager.h"
|
||||||
|
#include "image_manager.h"
|
||||||
|
#include "activity_manager.h"
|
||||||
|
#include "relationship_manager.h"
|
||||||
|
#include "lobby_manager.h"
|
||||||
|
#include "network_manager.h"
|
||||||
|
#include "overlay_manager.h"
|
||||||
|
#include "storage_manager.h"
|
||||||
|
#include "store_manager.h"
|
||||||
|
#include "voice_manager.h"
|
||||||
|
#include "achievement_manager.h"
|
||||||
59
libs/discordGameSDK/cpp/event.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
class Event final {
|
||||||
|
public:
|
||||||
|
using Token = int;
|
||||||
|
|
||||||
|
Event() { slots_.reserve(4); }
|
||||||
|
|
||||||
|
Event(Event const&) = default;
|
||||||
|
Event(Event&&) = default;
|
||||||
|
~Event() = default;
|
||||||
|
|
||||||
|
Event& operator=(Event const&) = default;
|
||||||
|
Event& operator=(Event&&) = default;
|
||||||
|
|
||||||
|
template <typename EventHandler>
|
||||||
|
Token Connect(EventHandler slot)
|
||||||
|
{
|
||||||
|
slots_.emplace_back(Slot{nextToken_, std::move(slot)});
|
||||||
|
return nextToken_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disconnect(Token token)
|
||||||
|
{
|
||||||
|
for (auto& slot : slots_) {
|
||||||
|
if (slot.token == token) {
|
||||||
|
slot = slots_.back();
|
||||||
|
slots_.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisconnectAll() { slots_ = {}; }
|
||||||
|
|
||||||
|
void operator()(Args... args)
|
||||||
|
{
|
||||||
|
for (auto const& slot : slots_) {
|
||||||
|
slot.fn(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Slot {
|
||||||
|
Token token;
|
||||||
|
std::function<void(Args...)> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
Token nextToken_{};
|
||||||
|
std::vector<Slot> slots_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
942
libs/discordGameSDK/cpp/ffi.h
Normal file
@@ -0,0 +1,942 @@
|
|||||||
|
#ifndef _DISCORD_GAME_SDK_H_
|
||||||
|
#define _DISCORD_GAME_SDK_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#include <stdbool.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DISCORD_VERSION 2
|
||||||
|
#define DISCORD_APPLICATION_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_USER_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_IMAGE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_ACTIVITY_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_LOBBY_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_NETWORK_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_OVERLAY_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_STORAGE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_STORE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_VOICE_MANAGER_VERSION 1
|
||||||
|
#define DISCORD_ACHIEVEMENT_MANAGER_VERSION 1
|
||||||
|
|
||||||
|
enum EDiscordResult {
|
||||||
|
DiscordResult_Ok = 0,
|
||||||
|
DiscordResult_ServiceUnavailable = 1,
|
||||||
|
DiscordResult_InvalidVersion = 2,
|
||||||
|
DiscordResult_LockFailed = 3,
|
||||||
|
DiscordResult_InternalError = 4,
|
||||||
|
DiscordResult_InvalidPayload = 5,
|
||||||
|
DiscordResult_InvalidCommand = 6,
|
||||||
|
DiscordResult_InvalidPermissions = 7,
|
||||||
|
DiscordResult_NotFetched = 8,
|
||||||
|
DiscordResult_NotFound = 9,
|
||||||
|
DiscordResult_Conflict = 10,
|
||||||
|
DiscordResult_InvalidSecret = 11,
|
||||||
|
DiscordResult_InvalidJoinSecret = 12,
|
||||||
|
DiscordResult_NoEligibleActivity = 13,
|
||||||
|
DiscordResult_InvalidInvite = 14,
|
||||||
|
DiscordResult_NotAuthenticated = 15,
|
||||||
|
DiscordResult_InvalidAccessToken = 16,
|
||||||
|
DiscordResult_ApplicationMismatch = 17,
|
||||||
|
DiscordResult_InvalidDataUrl = 18,
|
||||||
|
DiscordResult_InvalidBase64 = 19,
|
||||||
|
DiscordResult_NotFiltered = 20,
|
||||||
|
DiscordResult_LobbyFull = 21,
|
||||||
|
DiscordResult_InvalidLobbySecret = 22,
|
||||||
|
DiscordResult_InvalidFilename = 23,
|
||||||
|
DiscordResult_InvalidFileSize = 24,
|
||||||
|
DiscordResult_InvalidEntitlement = 25,
|
||||||
|
DiscordResult_NotInstalled = 26,
|
||||||
|
DiscordResult_NotRunning = 27,
|
||||||
|
DiscordResult_InsufficientBuffer = 28,
|
||||||
|
DiscordResult_PurchaseCanceled = 29,
|
||||||
|
DiscordResult_InvalidGuild = 30,
|
||||||
|
DiscordResult_InvalidEvent = 31,
|
||||||
|
DiscordResult_InvalidChannel = 32,
|
||||||
|
DiscordResult_InvalidOrigin = 33,
|
||||||
|
DiscordResult_RateLimited = 34,
|
||||||
|
DiscordResult_OAuth2Error = 35,
|
||||||
|
DiscordResult_SelectChannelTimeout = 36,
|
||||||
|
DiscordResult_GetGuildTimeout = 37,
|
||||||
|
DiscordResult_SelectVoiceForceRequired = 38,
|
||||||
|
DiscordResult_CaptureShortcutAlreadyListening = 39,
|
||||||
|
DiscordResult_UnauthorizedForAchievement = 40,
|
||||||
|
DiscordResult_InvalidGiftCode = 41,
|
||||||
|
DiscordResult_PurchaseError = 42,
|
||||||
|
DiscordResult_TransactionAborted = 43,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordCreateFlags {
|
||||||
|
DiscordCreateFlags_Default = 0,
|
||||||
|
DiscordCreateFlags_NoRequireDiscord = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLogLevel {
|
||||||
|
DiscordLogLevel_Error = 1,
|
||||||
|
DiscordLogLevel_Warn,
|
||||||
|
DiscordLogLevel_Info,
|
||||||
|
DiscordLogLevel_Debug,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordUserFlag {
|
||||||
|
DiscordUserFlag_Partner = 2,
|
||||||
|
DiscordUserFlag_HypeSquadEvents = 4,
|
||||||
|
DiscordUserFlag_HypeSquadHouse1 = 64,
|
||||||
|
DiscordUserFlag_HypeSquadHouse2 = 128,
|
||||||
|
DiscordUserFlag_HypeSquadHouse3 = 256,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordPremiumType {
|
||||||
|
DiscordPremiumType_None = 0,
|
||||||
|
DiscordPremiumType_Tier1 = 1,
|
||||||
|
DiscordPremiumType_Tier2 = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordImageType {
|
||||||
|
DiscordImageType_User,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordActivityType {
|
||||||
|
DiscordActivityType_Playing,
|
||||||
|
DiscordActivityType_Streaming,
|
||||||
|
DiscordActivityType_Listening,
|
||||||
|
DiscordActivityType_Watching,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordActivityActionType {
|
||||||
|
DiscordActivityActionType_Join = 1,
|
||||||
|
DiscordActivityActionType_Spectate,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordActivityJoinRequestReply {
|
||||||
|
DiscordActivityJoinRequestReply_No,
|
||||||
|
DiscordActivityJoinRequestReply_Yes,
|
||||||
|
DiscordActivityJoinRequestReply_Ignore,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordStatus {
|
||||||
|
DiscordStatus_Offline = 0,
|
||||||
|
DiscordStatus_Online = 1,
|
||||||
|
DiscordStatus_Idle = 2,
|
||||||
|
DiscordStatus_DoNotDisturb = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordRelationshipType {
|
||||||
|
DiscordRelationshipType_None,
|
||||||
|
DiscordRelationshipType_Friend,
|
||||||
|
DiscordRelationshipType_Blocked,
|
||||||
|
DiscordRelationshipType_PendingIncoming,
|
||||||
|
DiscordRelationshipType_PendingOutgoing,
|
||||||
|
DiscordRelationshipType_Implicit,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbyType {
|
||||||
|
DiscordLobbyType_Private = 1,
|
||||||
|
DiscordLobbyType_Public,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbySearchComparison {
|
||||||
|
DiscordLobbySearchComparison_LessThanOrEqual = -2,
|
||||||
|
DiscordLobbySearchComparison_LessThan,
|
||||||
|
DiscordLobbySearchComparison_Equal,
|
||||||
|
DiscordLobbySearchComparison_GreaterThan,
|
||||||
|
DiscordLobbySearchComparison_GreaterThanOrEqual,
|
||||||
|
DiscordLobbySearchComparison_NotEqual,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbySearchCast {
|
||||||
|
DiscordLobbySearchCast_String = 1,
|
||||||
|
DiscordLobbySearchCast_Number,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordLobbySearchDistance {
|
||||||
|
DiscordLobbySearchDistance_Local,
|
||||||
|
DiscordLobbySearchDistance_Default,
|
||||||
|
DiscordLobbySearchDistance_Extended,
|
||||||
|
DiscordLobbySearchDistance_Global,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordEntitlementType {
|
||||||
|
DiscordEntitlementType_Purchase = 1,
|
||||||
|
DiscordEntitlementType_PremiumSubscription,
|
||||||
|
DiscordEntitlementType_DeveloperGift,
|
||||||
|
DiscordEntitlementType_TestModePurchase,
|
||||||
|
DiscordEntitlementType_FreePurchase,
|
||||||
|
DiscordEntitlementType_UserGift,
|
||||||
|
DiscordEntitlementType_PremiumPurchase,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordSkuType {
|
||||||
|
DiscordSkuType_Application = 1,
|
||||||
|
DiscordSkuType_DLC,
|
||||||
|
DiscordSkuType_Consumable,
|
||||||
|
DiscordSkuType_Bundle,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EDiscordInputModeType {
|
||||||
|
DiscordInputModeType_VoiceActivity = 0,
|
||||||
|
DiscordInputModeType_PushToTalk,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef int64_t DiscordClientId;
|
||||||
|
typedef int32_t DiscordVersion;
|
||||||
|
typedef int64_t DiscordSnowflake;
|
||||||
|
typedef int64_t DiscordTimestamp;
|
||||||
|
typedef DiscordSnowflake DiscordUserId;
|
||||||
|
typedef char DiscordLocale[128];
|
||||||
|
typedef char DiscordBranch[4096];
|
||||||
|
typedef DiscordSnowflake DiscordLobbyId;
|
||||||
|
typedef char DiscordLobbySecret[128];
|
||||||
|
typedef char DiscordMetadataKey[256];
|
||||||
|
typedef char DiscordMetadataValue[4096];
|
||||||
|
typedef uint64_t DiscordNetworkPeerId;
|
||||||
|
typedef uint8_t DiscordNetworkChannelId;
|
||||||
|
typedef char DiscordPath[4096];
|
||||||
|
typedef char DiscordDateTime[64];
|
||||||
|
|
||||||
|
struct DiscordUser {
|
||||||
|
DiscordUserId id;
|
||||||
|
char username[256];
|
||||||
|
char discriminator[8];
|
||||||
|
char avatar[128];
|
||||||
|
bool bot;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordOAuth2Token {
|
||||||
|
char access_token[128];
|
||||||
|
char scopes[1024];
|
||||||
|
DiscordTimestamp expires;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordImageHandle {
|
||||||
|
enum EDiscordImageType type;
|
||||||
|
int64_t id;
|
||||||
|
uint32_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordImageDimensions {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivityTimestamps {
|
||||||
|
DiscordTimestamp start;
|
||||||
|
DiscordTimestamp end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivityAssets {
|
||||||
|
char large_image[128];
|
||||||
|
char large_text[128];
|
||||||
|
char small_image[128];
|
||||||
|
char small_text[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordPartySize {
|
||||||
|
int32_t current_size;
|
||||||
|
int32_t max_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivityParty {
|
||||||
|
char id[128];
|
||||||
|
struct DiscordPartySize size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivitySecrets {
|
||||||
|
char match[128];
|
||||||
|
char join[128];
|
||||||
|
char spectate[128];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordActivity {
|
||||||
|
enum EDiscordActivityType type;
|
||||||
|
int64_t application_id;
|
||||||
|
char name[128];
|
||||||
|
char state[128];
|
||||||
|
char details[128];
|
||||||
|
struct DiscordActivityTimestamps timestamps;
|
||||||
|
struct DiscordActivityAssets assets;
|
||||||
|
struct DiscordActivityParty party;
|
||||||
|
struct DiscordActivitySecrets secrets;
|
||||||
|
bool instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordPresence {
|
||||||
|
enum EDiscordStatus status;
|
||||||
|
struct DiscordActivity activity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordRelationship {
|
||||||
|
enum EDiscordRelationshipType type;
|
||||||
|
struct DiscordUser user;
|
||||||
|
struct DiscordPresence presence;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordLobby {
|
||||||
|
DiscordLobbyId id;
|
||||||
|
enum EDiscordLobbyType type;
|
||||||
|
DiscordUserId owner_id;
|
||||||
|
DiscordLobbySecret secret;
|
||||||
|
uint32_t capacity;
|
||||||
|
bool locked;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordFileStat {
|
||||||
|
char filename[260];
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t last_modified;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordEntitlement {
|
||||||
|
DiscordSnowflake id;
|
||||||
|
enum EDiscordEntitlementType type;
|
||||||
|
DiscordSnowflake sku_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordSkuPrice {
|
||||||
|
uint32_t amount;
|
||||||
|
char currency[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordSku {
|
||||||
|
DiscordSnowflake id;
|
||||||
|
enum EDiscordSkuType type;
|
||||||
|
char name[256];
|
||||||
|
struct DiscordSkuPrice price;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordInputMode {
|
||||||
|
enum EDiscordInputModeType type;
|
||||||
|
char shortcut[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordUserAchievement {
|
||||||
|
DiscordSnowflake user_id;
|
||||||
|
DiscordSnowflake achievement_id;
|
||||||
|
uint8_t percent_complete;
|
||||||
|
DiscordDateTime unlocked_at;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyTransaction {
|
||||||
|
enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||||
|
enum EDiscordLobbyType type);
|
||||||
|
enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||||
|
DiscordUserId owner_id);
|
||||||
|
enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||||
|
uint32_t capacity);
|
||||||
|
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||||
|
DiscordMetadataKey key,
|
||||||
|
DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||||
|
DiscordMetadataKey key);
|
||||||
|
enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||||
|
bool locked);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyMemberTransaction {
|
||||||
|
enum EDiscordResult (*set_metadata)(
|
||||||
|
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
|
||||||
|
DiscordMetadataKey key,
|
||||||
|
DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*delete_metadata)(
|
||||||
|
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
|
||||||
|
DiscordMetadataKey key);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbySearchQuery {
|
||||||
|
enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||||
|
DiscordMetadataKey key,
|
||||||
|
enum EDiscordLobbySearchComparison comparison,
|
||||||
|
enum EDiscordLobbySearchCast cast,
|
||||||
|
DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||||
|
DiscordMetadataKey key,
|
||||||
|
enum EDiscordLobbySearchCast cast,
|
||||||
|
DiscordMetadataValue value);
|
||||||
|
enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||||
|
uint32_t limit);
|
||||||
|
enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||||
|
enum EDiscordLobbySearchDistance distance);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordApplicationEvents;
|
||||||
|
|
||||||
|
struct IDiscordApplicationManager {
|
||||||
|
void (*validate_or_exit)(struct IDiscordApplicationManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale);
|
||||||
|
void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch);
|
||||||
|
void (*get_oauth2_token)(struct IDiscordApplicationManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
struct DiscordOAuth2Token* oauth2_token));
|
||||||
|
void (*get_ticket)(struct IDiscordApplicationManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
const char* data));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordUserEvents {
|
||||||
|
void (*on_current_user_update)(void* event_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordUserManager {
|
||||||
|
enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager,
|
||||||
|
struct DiscordUser* current_user);
|
||||||
|
void (*get_user)(struct IDiscordUserManager* manager,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
struct DiscordUser* user));
|
||||||
|
enum EDiscordResult (*get_current_user_premium_type)(struct IDiscordUserManager* manager,
|
||||||
|
enum EDiscordPremiumType* premium_type);
|
||||||
|
enum EDiscordResult (*current_user_has_flag)(struct IDiscordUserManager* manager,
|
||||||
|
enum EDiscordUserFlag flag,
|
||||||
|
bool* has_flag);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordImageEvents;
|
||||||
|
|
||||||
|
struct IDiscordImageManager {
|
||||||
|
void (*fetch)(struct IDiscordImageManager* manager,
|
||||||
|
struct DiscordImageHandle handle,
|
||||||
|
bool refresh,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
struct DiscordImageHandle handle_result));
|
||||||
|
enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager,
|
||||||
|
struct DiscordImageHandle handle,
|
||||||
|
struct DiscordImageDimensions* dimensions);
|
||||||
|
enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager,
|
||||||
|
struct DiscordImageHandle handle,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordActivityEvents {
|
||||||
|
void (*on_activity_join)(void* event_data, const char* secret);
|
||||||
|
void (*on_activity_spectate)(void* event_data, const char* secret);
|
||||||
|
void (*on_activity_join_request)(void* event_data, struct DiscordUser* user);
|
||||||
|
void (*on_activity_invite)(void* event_data,
|
||||||
|
enum EDiscordActivityActionType type,
|
||||||
|
struct DiscordUser* user,
|
||||||
|
struct DiscordActivity* activity);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordActivityManager {
|
||||||
|
enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager,
|
||||||
|
const char* command);
|
||||||
|
enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager,
|
||||||
|
uint32_t steam_id);
|
||||||
|
void (*update_activity)(struct IDiscordActivityManager* manager,
|
||||||
|
struct DiscordActivity* activity,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*clear_activity)(struct IDiscordActivityManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*send_request_reply)(struct IDiscordActivityManager* manager,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
enum EDiscordActivityJoinRequestReply reply,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*send_invite)(struct IDiscordActivityManager* manager,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
enum EDiscordActivityActionType type,
|
||||||
|
const char* content,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*accept_invite)(struct IDiscordActivityManager* manager,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordRelationshipEvents {
|
||||||
|
void (*on_refresh)(void* event_data);
|
||||||
|
void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordRelationshipManager {
|
||||||
|
void (*filter)(struct IDiscordRelationshipManager* manager,
|
||||||
|
void* filter_data,
|
||||||
|
bool (*filter)(void* filter_data, struct DiscordRelationship* relationship));
|
||||||
|
enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
struct DiscordRelationship* relationship);
|
||||||
|
enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager,
|
||||||
|
uint32_t index,
|
||||||
|
struct DiscordRelationship* relationship);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyEvents {
|
||||||
|
void (*on_lobby_update)(void* event_data, int64_t lobby_id);
|
||||||
|
void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason);
|
||||||
|
void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||||
|
void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||||
|
void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||||
|
void (*on_lobby_message)(void* event_data,
|
||||||
|
int64_t lobby_id,
|
||||||
|
int64_t user_id,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking);
|
||||||
|
void (*on_network_message)(void* event_data,
|
||||||
|
int64_t lobby_id,
|
||||||
|
int64_t user_id,
|
||||||
|
uint8_t channel_id,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordLobbyManager {
|
||||||
|
enum EDiscordResult (*get_lobby_create_transaction)(
|
||||||
|
struct IDiscordLobbyManager* manager,
|
||||||
|
struct IDiscordLobbyTransaction** transaction);
|
||||||
|
enum EDiscordResult (*get_lobby_update_transaction)(
|
||||||
|
struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
struct IDiscordLobbyTransaction** transaction);
|
||||||
|
enum EDiscordResult (*get_member_update_transaction)(
|
||||||
|
struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
struct IDiscordLobbyMemberTransaction** transaction);
|
||||||
|
void (*create_lobby)(struct IDiscordLobbyManager* manager,
|
||||||
|
struct IDiscordLobbyTransaction* transaction,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
struct DiscordLobby* lobby));
|
||||||
|
void (*update_lobby)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
struct IDiscordLobbyTransaction* transaction,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*delete_lobby)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*connect_lobby)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordLobbySecret secret,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
struct DiscordLobby* lobby));
|
||||||
|
void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbySecret activity_secret,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
struct DiscordLobby* lobby));
|
||||||
|
void (*disconnect_lobby)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
struct DiscordLobby* lobby);
|
||||||
|
enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordLobbySecret* secret);
|
||||||
|
enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordMetadataKey key,
|
||||||
|
DiscordMetadataValue* value);
|
||||||
|
enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
int32_t index,
|
||||||
|
DiscordMetadataKey* key);
|
||||||
|
enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
int32_t* count);
|
||||||
|
enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
int32_t* count);
|
||||||
|
enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
int32_t index,
|
||||||
|
DiscordUserId* user_id);
|
||||||
|
enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
struct DiscordUser* user);
|
||||||
|
enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
DiscordMetadataKey key,
|
||||||
|
DiscordMetadataValue* value);
|
||||||
|
enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
int32_t index,
|
||||||
|
DiscordMetadataKey* key);
|
||||||
|
enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
int32_t* count);
|
||||||
|
void (*update_member)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
struct IDiscordLobbyMemberTransaction* transaction,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*send_lobby_message)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager,
|
||||||
|
struct IDiscordLobbySearchQuery** query);
|
||||||
|
void (*search)(struct IDiscordLobbyManager* manager,
|
||||||
|
struct IDiscordLobbySearchQuery* query,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager,
|
||||||
|
int32_t index,
|
||||||
|
DiscordLobbyId* lobby_id);
|
||||||
|
void (*connect_voice)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*disconnect_voice)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id);
|
||||||
|
enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id);
|
||||||
|
enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager);
|
||||||
|
enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
uint8_t channel_id,
|
||||||
|
bool reliable);
|
||||||
|
enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager,
|
||||||
|
DiscordLobbyId lobby_id,
|
||||||
|
DiscordUserId user_id,
|
||||||
|
uint8_t channel_id,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordNetworkEvents {
|
||||||
|
void (*on_message)(void* event_data,
|
||||||
|
DiscordNetworkPeerId peer_id,
|
||||||
|
DiscordNetworkChannelId channel_id,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
void (*on_route_update)(void* event_data, const char* route_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordNetworkManager {
|
||||||
|
/**
|
||||||
|
* Get the local peer ID for this process.
|
||||||
|
*/
|
||||||
|
void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id);
|
||||||
|
/**
|
||||||
|
* Send pending network messages.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager);
|
||||||
|
/**
|
||||||
|
* Open a connection to a remote peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager,
|
||||||
|
DiscordNetworkPeerId peer_id,
|
||||||
|
const char* route_data);
|
||||||
|
/**
|
||||||
|
* Update the route data for a connected peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager,
|
||||||
|
DiscordNetworkPeerId peer_id,
|
||||||
|
const char* route_data);
|
||||||
|
/**
|
||||||
|
* Close the connection to a remote peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager,
|
||||||
|
DiscordNetworkPeerId peer_id);
|
||||||
|
/**
|
||||||
|
* Open a message channel to a connected peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager,
|
||||||
|
DiscordNetworkPeerId peer_id,
|
||||||
|
DiscordNetworkChannelId channel_id,
|
||||||
|
bool reliable);
|
||||||
|
/**
|
||||||
|
* Close a message channel to a connected peer.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager,
|
||||||
|
DiscordNetworkPeerId peer_id,
|
||||||
|
DiscordNetworkChannelId channel_id);
|
||||||
|
/**
|
||||||
|
* Send a message to a connected peer over an opened message channel.
|
||||||
|
*/
|
||||||
|
enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager,
|
||||||
|
DiscordNetworkPeerId peer_id,
|
||||||
|
DiscordNetworkChannelId channel_id,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordOverlayEvents {
|
||||||
|
void (*on_toggle)(void* event_data, bool locked);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordOverlayManager {
|
||||||
|
void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled);
|
||||||
|
void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked);
|
||||||
|
void (*set_locked)(struct IDiscordOverlayManager* manager,
|
||||||
|
bool locked,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*open_activity_invite)(struct IDiscordOverlayManager* manager,
|
||||||
|
enum EDiscordActivityActionType type,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*open_guild_invite)(struct IDiscordOverlayManager* manager,
|
||||||
|
const char* code,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*open_voice_settings)(struct IDiscordOverlayManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordStorageEvents;
|
||||||
|
|
||||||
|
struct IDiscordStorageManager {
|
||||||
|
enum EDiscordResult (*read)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length,
|
||||||
|
uint32_t* read);
|
||||||
|
void (*read_async)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length));
|
||||||
|
void (*read_async_partial)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
uint64_t offset,
|
||||||
|
uint64_t length,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length));
|
||||||
|
enum EDiscordResult (*write)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length);
|
||||||
|
void (*write_async)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t data_length,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name);
|
||||||
|
enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
bool* exists);
|
||||||
|
void (*count)(struct IDiscordStorageManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager,
|
||||||
|
const char* name,
|
||||||
|
struct DiscordFileStat* stat);
|
||||||
|
enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager,
|
||||||
|
int32_t index,
|
||||||
|
struct DiscordFileStat* stat);
|
||||||
|
enum EDiscordResult (*get_path)(struct IDiscordStorageManager* manager, DiscordPath* path);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordStoreEvents {
|
||||||
|
void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||||
|
void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordStoreManager {
|
||||||
|
void (*fetch_skus)(struct IDiscordStoreManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager,
|
||||||
|
DiscordSnowflake sku_id,
|
||||||
|
struct DiscordSku* sku);
|
||||||
|
enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager,
|
||||||
|
int32_t index,
|
||||||
|
struct DiscordSku* sku);
|
||||||
|
void (*fetch_entitlements)(struct IDiscordStoreManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager,
|
||||||
|
DiscordSnowflake entitlement_id,
|
||||||
|
struct DiscordEntitlement* entitlement);
|
||||||
|
enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager,
|
||||||
|
int32_t index,
|
||||||
|
struct DiscordEntitlement* entitlement);
|
||||||
|
enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager,
|
||||||
|
DiscordSnowflake sku_id,
|
||||||
|
bool* has_entitlement);
|
||||||
|
void (*start_purchase)(struct IDiscordStoreManager* manager,
|
||||||
|
DiscordSnowflake sku_id,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordVoiceEvents {
|
||||||
|
void (*on_settings_update)(void* event_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordVoiceManager {
|
||||||
|
enum EDiscordResult (*get_input_mode)(struct IDiscordVoiceManager* manager,
|
||||||
|
struct DiscordInputMode* input_mode);
|
||||||
|
void (*set_input_mode)(struct IDiscordVoiceManager* manager,
|
||||||
|
struct DiscordInputMode input_mode,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
enum EDiscordResult (*is_self_mute)(struct IDiscordVoiceManager* manager, bool* mute);
|
||||||
|
enum EDiscordResult (*set_self_mute)(struct IDiscordVoiceManager* manager, bool mute);
|
||||||
|
enum EDiscordResult (*is_self_deaf)(struct IDiscordVoiceManager* manager, bool* deaf);
|
||||||
|
enum EDiscordResult (*set_self_deaf)(struct IDiscordVoiceManager* manager, bool deaf);
|
||||||
|
enum EDiscordResult (*is_local_mute)(struct IDiscordVoiceManager* manager,
|
||||||
|
DiscordSnowflake user_id,
|
||||||
|
bool* mute);
|
||||||
|
enum EDiscordResult (*set_local_mute)(struct IDiscordVoiceManager* manager,
|
||||||
|
DiscordSnowflake user_id,
|
||||||
|
bool mute);
|
||||||
|
enum EDiscordResult (*get_local_volume)(struct IDiscordVoiceManager* manager,
|
||||||
|
DiscordSnowflake user_id,
|
||||||
|
uint8_t* volume);
|
||||||
|
enum EDiscordResult (*set_local_volume)(struct IDiscordVoiceManager* manager,
|
||||||
|
DiscordSnowflake user_id,
|
||||||
|
uint8_t volume);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordAchievementEvents {
|
||||||
|
void (*on_user_achievement_update)(void* event_data,
|
||||||
|
struct DiscordUserAchievement* user_achievement);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IDiscordAchievementManager {
|
||||||
|
void (*set_user_achievement)(struct IDiscordAchievementManager* manager,
|
||||||
|
DiscordSnowflake achievement_id,
|
||||||
|
uint8_t percent_complete,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||||
|
void (*fetch_user_achievements)(struct IDiscordAchievementManager* manager,
|
||||||
|
void* callback_data,
|
||||||
|
void (*callback)(void* callback_data,
|
||||||
|
enum EDiscordResult result));
|
||||||
|
void (*count_user_achievements)(struct IDiscordAchievementManager* manager, int32_t* count);
|
||||||
|
enum EDiscordResult (*get_user_achievement)(struct IDiscordAchievementManager* manager,
|
||||||
|
DiscordSnowflake user_achievement_id,
|
||||||
|
struct DiscordUserAchievement* user_achievement);
|
||||||
|
enum EDiscordResult (*get_user_achievement_at)(struct IDiscordAchievementManager* manager,
|
||||||
|
int32_t index,
|
||||||
|
struct DiscordUserAchievement* user_achievement);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void* IDiscordCoreEvents;
|
||||||
|
|
||||||
|
struct IDiscordCore {
|
||||||
|
void (*destroy)(struct IDiscordCore* core);
|
||||||
|
enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core);
|
||||||
|
void (*set_log_hook)(struct IDiscordCore* core,
|
||||||
|
enum EDiscordLogLevel min_level,
|
||||||
|
void* hook_data,
|
||||||
|
void (*hook)(void* hook_data,
|
||||||
|
enum EDiscordLogLevel level,
|
||||||
|
const char* message));
|
||||||
|
struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordVoiceManager* (*get_voice_manager)(struct IDiscordCore* core);
|
||||||
|
struct IDiscordAchievementManager* (*get_achievement_manager)(struct IDiscordCore* core);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscordCreateParams {
|
||||||
|
DiscordClientId client_id;
|
||||||
|
uint64_t flags;
|
||||||
|
IDiscordCoreEvents* events;
|
||||||
|
void* event_data;
|
||||||
|
IDiscordApplicationEvents* application_events;
|
||||||
|
DiscordVersion application_version;
|
||||||
|
struct IDiscordUserEvents* user_events;
|
||||||
|
DiscordVersion user_version;
|
||||||
|
IDiscordImageEvents* image_events;
|
||||||
|
DiscordVersion image_version;
|
||||||
|
struct IDiscordActivityEvents* activity_events;
|
||||||
|
DiscordVersion activity_version;
|
||||||
|
struct IDiscordRelationshipEvents* relationship_events;
|
||||||
|
DiscordVersion relationship_version;
|
||||||
|
struct IDiscordLobbyEvents* lobby_events;
|
||||||
|
DiscordVersion lobby_version;
|
||||||
|
struct IDiscordNetworkEvents* network_events;
|
||||||
|
DiscordVersion network_version;
|
||||||
|
struct IDiscordOverlayEvents* overlay_events;
|
||||||
|
DiscordVersion overlay_version;
|
||||||
|
IDiscordStorageEvents* storage_events;
|
||||||
|
DiscordVersion storage_version;
|
||||||
|
struct IDiscordStoreEvents* store_events;
|
||||||
|
DiscordVersion store_version;
|
||||||
|
struct IDiscordVoiceEvents* voice_events;
|
||||||
|
DiscordVersion voice_version;
|
||||||
|
struct IDiscordAchievementEvents* achievement_events;
|
||||||
|
DiscordVersion achievement_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
inline
|
||||||
|
#else
|
||||||
|
static
|
||||||
|
#endif
|
||||||
|
void
|
||||||
|
DiscordCreateParamsSetDefault(struct DiscordCreateParams* params)
|
||||||
|
{
|
||||||
|
memset(params, 0, sizeof(struct DiscordCreateParams));
|
||||||
|
params->application_version = DISCORD_APPLICATION_MANAGER_VERSION;
|
||||||
|
params->user_version = DISCORD_USER_MANAGER_VERSION;
|
||||||
|
params->image_version = DISCORD_IMAGE_MANAGER_VERSION;
|
||||||
|
params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION;
|
||||||
|
params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION;
|
||||||
|
params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION;
|
||||||
|
params->network_version = DISCORD_NETWORK_MANAGER_VERSION;
|
||||||
|
params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION;
|
||||||
|
params->storage_version = DISCORD_STORAGE_MANAGER_VERSION;
|
||||||
|
params->store_version = DISCORD_STORE_MANAGER_VERSION;
|
||||||
|
params->voice_version = DISCORD_VOICE_MANAGER_VERSION;
|
||||||
|
params->achievement_version = DISCORD_ACHIEVEMENT_MANAGER_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EDiscordResult DiscordCreate(DiscordVersion version,
|
||||||
|
struct DiscordCreateParams* params,
|
||||||
|
struct IDiscordCore** result);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
57
libs/discordGameSDK/cpp/image_manager.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "image_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
void ImageManager::Fetch(ImageHandle handle,
|
||||||
|
bool refresh,
|
||||||
|
std::function<void(Result, ImageHandle)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, DiscordImageHandle handleResult) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, ImageHandle)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), *reinterpret_cast<ImageHandle const*>(&handleResult));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, ImageHandle)>(std::move(callback)));
|
||||||
|
internal_->fetch(internal_,
|
||||||
|
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||||
|
(refresh ? 1 : 0),
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ImageManager::GetDimensions(ImageHandle handle, ImageDimensions* dimensions)
|
||||||
|
{
|
||||||
|
if (!dimensions) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_dimensions(internal_,
|
||||||
|
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||||
|
reinterpret_cast<DiscordImageDimensions*>(dimensions));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ImageManager::GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto result = internal_->get_data(internal_,
|
||||||
|
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||||
|
reinterpret_cast<uint8_t*>(data),
|
||||||
|
dataLength);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
28
libs/discordGameSDK/cpp/image_manager.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class ImageManager final {
|
||||||
|
public:
|
||||||
|
~ImageManager() = default;
|
||||||
|
|
||||||
|
void Fetch(ImageHandle handle, bool refresh, std::function<void(Result, ImageHandle)> callback);
|
||||||
|
Result GetDimensions(ImageHandle handle, ImageDimensions* dimensions);
|
||||||
|
Result GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
ImageManager() = default;
|
||||||
|
ImageManager(ImageManager const& rhs) = delete;
|
||||||
|
ImageManager& operator=(ImageManager const& rhs) = delete;
|
||||||
|
ImageManager(ImageManager&& rhs) = delete;
|
||||||
|
ImageManager& operator=(ImageManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordImageManager* internal_;
|
||||||
|
static IDiscordImageEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
547
libs/discordGameSDK/cpp/lobby_manager.cpp
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "lobby_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class LobbyEvents final {
|
||||||
|
public:
|
||||||
|
static void OnLobbyUpdate(void* callbackData, int64_t lobbyId)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnLobbyUpdate(lobbyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnLobbyDelete(void* callbackData, int64_t lobbyId, uint32_t reason)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnLobbyDelete(lobbyId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnMemberConnect(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnMemberConnect(lobbyId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnMemberUpdate(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnMemberUpdate(lobbyId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnMemberDisconnect(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnMemberDisconnect(lobbyId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnLobbyMessage(void* callbackData,
|
||||||
|
int64_t lobbyId,
|
||||||
|
int64_t userId,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnLobbyMessage(lobbyId, userId, data, dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnSpeaking(void* callbackData, int64_t lobbyId, int64_t userId, bool speaking)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnSpeaking(lobbyId, userId, (speaking != 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnNetworkMessage(void* callbackData,
|
||||||
|
int64_t lobbyId,
|
||||||
|
int64_t userId,
|
||||||
|
uint8_t channelId,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->LobbyManager();
|
||||||
|
module.OnNetworkMessage(lobbyId, userId, channelId, data, dataLength);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordLobbyEvents LobbyManager::events_{
|
||||||
|
&LobbyEvents::OnLobbyUpdate,
|
||||||
|
&LobbyEvents::OnLobbyDelete,
|
||||||
|
&LobbyEvents::OnMemberConnect,
|
||||||
|
&LobbyEvents::OnMemberUpdate,
|
||||||
|
&LobbyEvents::OnMemberDisconnect,
|
||||||
|
&LobbyEvents::OnLobbyMessage,
|
||||||
|
&LobbyEvents::OnSpeaking,
|
||||||
|
&LobbyEvents::OnNetworkMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobbyCreateTransaction(LobbyTransaction* transaction)
|
||||||
|
{
|
||||||
|
if (!transaction) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_lobby_create_transaction(internal_, transaction->Receive());
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction)
|
||||||
|
{
|
||||||
|
if (!transaction) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_lobby_update_transaction(internal_, lobbyId, transaction->Receive());
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetMemberUpdateTransaction(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
LobbyMemberTransaction* transaction)
|
||||||
|
{
|
||||||
|
if (!transaction) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_member_update_transaction(internal_, lobbyId, userId, transaction->Receive());
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::CreateLobby(LobbyTransaction const& transaction,
|
||||||
|
std::function<void(Result, Lobby const&)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||||
|
internal_->create_lobby(
|
||||||
|
internal_, const_cast<LobbyTransaction&>(transaction).Internal(), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::UpdateLobby(LobbyId lobbyId,
|
||||||
|
LobbyTransaction const& transaction,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->update_lobby(internal_,
|
||||||
|
lobbyId,
|
||||||
|
const_cast<LobbyTransaction&>(transaction).Internal(),
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->delete_lobby(internal_, lobbyId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::ConnectLobby(LobbyId lobbyId,
|
||||||
|
LobbySecret secret,
|
||||||
|
std::function<void(Result, Lobby const&)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||||
|
internal_->connect_lobby(internal_, lobbyId, const_cast<char*>(secret), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::ConnectLobbyWithActivitySecret(
|
||||||
|
LobbySecret activitySecret,
|
||||||
|
std::function<void(Result, Lobby const&)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||||
|
internal_->connect_lobby_with_activity_secret(
|
||||||
|
internal_, const_cast<char*>(activitySecret), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->disconnect_lobby(internal_, lobbyId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobby(LobbyId lobbyId, Lobby* lobby)
|
||||||
|
{
|
||||||
|
if (!lobby) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_lobby(internal_, lobbyId, reinterpret_cast<DiscordLobby*>(lobby));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobbyActivitySecret(LobbyId lobbyId, char secret[128])
|
||||||
|
{
|
||||||
|
if (!secret) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_lobby_activity_secret(
|
||||||
|
internal_, lobbyId, reinterpret_cast<DiscordLobbySecret*>(secret));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096])
|
||||||
|
{
|
||||||
|
if (!value) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_lobby_metadata_value(
|
||||||
|
internal_, lobbyId, const_cast<char*>(key), reinterpret_cast<DiscordMetadataValue*>(value));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256])
|
||||||
|
{
|
||||||
|
if (!key) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_lobby_metadata_key(
|
||||||
|
internal_, lobbyId, index, reinterpret_cast<DiscordMetadataKey*>(key));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->lobby_metadata_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::MemberCount(LobbyId lobbyId, std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->member_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId)
|
||||||
|
{
|
||||||
|
if (!userId) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_member_user_id(internal_, lobbyId, index, reinterpret_cast<int64_t*>(userId));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetMemberUser(LobbyId lobbyId, UserId userId, User* user)
|
||||||
|
{
|
||||||
|
if (!user) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_member_user(internal_, lobbyId, userId, reinterpret_cast<DiscordUser*>(user));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetMemberMetadataValue(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
MetadataKey key,
|
||||||
|
char value[4096])
|
||||||
|
{
|
||||||
|
if (!value) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_member_metadata_value(internal_,
|
||||||
|
lobbyId,
|
||||||
|
userId,
|
||||||
|
const_cast<char*>(key),
|
||||||
|
reinterpret_cast<DiscordMetadataValue*>(value));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetMemberMetadataKey(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
std::int32_t index,
|
||||||
|
char key[256])
|
||||||
|
{
|
||||||
|
if (!key) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_member_metadata_key(
|
||||||
|
internal_, lobbyId, userId, index, reinterpret_cast<DiscordMetadataKey*>(key));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->member_metadata_count(
|
||||||
|
internal_, lobbyId, userId, reinterpret_cast<int32_t*>(count));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::UpdateMember(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
LobbyMemberTransaction const& transaction,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->update_member(internal_,
|
||||||
|
lobbyId,
|
||||||
|
userId,
|
||||||
|
const_cast<LobbyMemberTransaction&>(transaction).Internal(),
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::SendLobbyMessage(LobbyId lobbyId,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->send_lobby_message(
|
||||||
|
internal_, lobbyId, reinterpret_cast<uint8_t*>(data), dataLength, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetSearchQuery(LobbySearchQuery* query)
|
||||||
|
{
|
||||||
|
if (!query) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_search_query(internal_, query->Receive());
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::Search(LobbySearchQuery const& query, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->search(
|
||||||
|
internal_, const_cast<LobbySearchQuery&>(query).Internal(), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::LobbyCount(std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->lobby_count(internal_, reinterpret_cast<int32_t*>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::GetLobbyId(std::int32_t index, LobbyId* lobbyId)
|
||||||
|
{
|
||||||
|
if (!lobbyId) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_lobby_id(internal_, index, reinterpret_cast<int64_t*>(lobbyId));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->connect_voice(internal_, lobbyId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LobbyManager::DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->disconnect_voice(internal_, lobbyId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::ConnectNetwork(LobbyId lobbyId)
|
||||||
|
{
|
||||||
|
auto result = internal_->connect_network(internal_, lobbyId);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::DisconnectNetwork(LobbyId lobbyId)
|
||||||
|
{
|
||||||
|
auto result = internal_->disconnect_network(internal_, lobbyId);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::FlushNetwork()
|
||||||
|
{
|
||||||
|
auto result = internal_->flush_network(internal_);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable)
|
||||||
|
{
|
||||||
|
auto result =
|
||||||
|
internal_->open_network_channel(internal_, lobbyId, channelId, (reliable ? 1 : 0));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyManager::SendNetworkMessage(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
std::uint8_t channelId,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto result = internal_->send_network_message(
|
||||||
|
internal_, lobbyId, userId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
88
libs/discordGameSDK/cpp/lobby_manager.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class LobbyManager final {
|
||||||
|
public:
|
||||||
|
~LobbyManager() = default;
|
||||||
|
|
||||||
|
Result GetLobbyCreateTransaction(LobbyTransaction* transaction);
|
||||||
|
Result GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction);
|
||||||
|
Result GetMemberUpdateTransaction(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
LobbyMemberTransaction* transaction);
|
||||||
|
void CreateLobby(LobbyTransaction const& transaction,
|
||||||
|
std::function<void(Result, Lobby const&)> callback);
|
||||||
|
void UpdateLobby(LobbyId lobbyId,
|
||||||
|
LobbyTransaction const& transaction,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
void DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||||
|
void ConnectLobby(LobbyId lobbyId,
|
||||||
|
LobbySecret secret,
|
||||||
|
std::function<void(Result, Lobby const&)> callback);
|
||||||
|
void ConnectLobbyWithActivitySecret(LobbySecret activitySecret,
|
||||||
|
std::function<void(Result, Lobby const&)> callback);
|
||||||
|
void DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||||
|
Result GetLobby(LobbyId lobbyId, Lobby* lobby);
|
||||||
|
Result GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]);
|
||||||
|
Result GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]);
|
||||||
|
Result GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]);
|
||||||
|
Result LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count);
|
||||||
|
Result MemberCount(LobbyId lobbyId, std::int32_t* count);
|
||||||
|
Result GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId);
|
||||||
|
Result GetMemberUser(LobbyId lobbyId, UserId userId, User* user);
|
||||||
|
Result GetMemberMetadataValue(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
MetadataKey key,
|
||||||
|
char value[4096]);
|
||||||
|
Result GetMemberMetadataKey(LobbyId lobbyId, UserId userId, std::int32_t index, char key[256]);
|
||||||
|
Result MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count);
|
||||||
|
void UpdateMember(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
LobbyMemberTransaction const& transaction,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
void SendLobbyMessage(LobbyId lobbyId,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
Result GetSearchQuery(LobbySearchQuery* query);
|
||||||
|
void Search(LobbySearchQuery const& query, std::function<void(Result)> callback);
|
||||||
|
void LobbyCount(std::int32_t* count);
|
||||||
|
Result GetLobbyId(std::int32_t index, LobbyId* lobbyId);
|
||||||
|
void ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||||
|
void DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||||
|
Result ConnectNetwork(LobbyId lobbyId);
|
||||||
|
Result DisconnectNetwork(LobbyId lobbyId);
|
||||||
|
Result FlushNetwork();
|
||||||
|
Result OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable);
|
||||||
|
Result SendNetworkMessage(LobbyId lobbyId,
|
||||||
|
UserId userId,
|
||||||
|
std::uint8_t channelId,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength);
|
||||||
|
|
||||||
|
Event<std::int64_t> OnLobbyUpdate;
|
||||||
|
Event<std::int64_t, std::uint32_t> OnLobbyDelete;
|
||||||
|
Event<std::int64_t, std::int64_t> OnMemberConnect;
|
||||||
|
Event<std::int64_t, std::int64_t> OnMemberUpdate;
|
||||||
|
Event<std::int64_t, std::int64_t> OnMemberDisconnect;
|
||||||
|
Event<std::int64_t, std::int64_t, std::uint8_t*, std::uint32_t> OnLobbyMessage;
|
||||||
|
Event<std::int64_t, std::int64_t, bool> OnSpeaking;
|
||||||
|
Event<std::int64_t, std::int64_t, std::uint8_t, std::uint8_t*, std::uint32_t> OnNetworkMessage;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
LobbyManager() = default;
|
||||||
|
LobbyManager(LobbyManager const& rhs) = delete;
|
||||||
|
LobbyManager& operator=(LobbyManager const& rhs) = delete;
|
||||||
|
LobbyManager(LobbyManager&& rhs) = delete;
|
||||||
|
LobbyManager& operator=(LobbyManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordLobbyManager* internal_;
|
||||||
|
static IDiscordLobbyEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
103
libs/discordGameSDK/cpp/network_manager.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "network_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class NetworkEvents final {
|
||||||
|
public:
|
||||||
|
static void OnMessage(void* callbackData,
|
||||||
|
DiscordNetworkPeerId peerId,
|
||||||
|
DiscordNetworkChannelId channelId,
|
||||||
|
uint8_t* data,
|
||||||
|
uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->NetworkManager();
|
||||||
|
module.OnMessage(peerId, channelId, data, dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnRouteUpdate(void* callbackData, char const* routeData)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->NetworkManager();
|
||||||
|
module.OnRouteUpdate(static_cast<const char*>(routeData));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordNetworkEvents NetworkManager::events_{
|
||||||
|
&NetworkEvents::OnMessage,
|
||||||
|
&NetworkEvents::OnRouteUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
void NetworkManager::GetPeerId(NetworkPeerId* peerId)
|
||||||
|
{
|
||||||
|
if (!peerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->get_peer_id(internal_, reinterpret_cast<uint64_t*>(peerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::Flush()
|
||||||
|
{
|
||||||
|
auto result = internal_->flush(internal_);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::OpenPeer(NetworkPeerId peerId, char const* routeData)
|
||||||
|
{
|
||||||
|
auto result = internal_->open_peer(internal_, peerId, const_cast<char*>(routeData));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::UpdatePeer(NetworkPeerId peerId, char const* routeData)
|
||||||
|
{
|
||||||
|
auto result = internal_->update_peer(internal_, peerId, const_cast<char*>(routeData));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::ClosePeer(NetworkPeerId peerId)
|
||||||
|
{
|
||||||
|
auto result = internal_->close_peer(internal_, peerId);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable)
|
||||||
|
{
|
||||||
|
auto result = internal_->open_channel(internal_, peerId, channelId, (reliable ? 1 : 0));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId)
|
||||||
|
{
|
||||||
|
auto result = internal_->close_channel(internal_, peerId, channelId);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result NetworkManager::SendMessage(NetworkPeerId peerId,
|
||||||
|
NetworkChannelId channelId,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto result = internal_->send_message(
|
||||||
|
internal_, peerId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
63
libs/discordGameSDK/cpp/network_manager.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class NetworkManager final {
|
||||||
|
public:
|
||||||
|
~NetworkManager() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the local peer ID for this process.
|
||||||
|
*/
|
||||||
|
void GetPeerId(NetworkPeerId* peerId);
|
||||||
|
/**
|
||||||
|
* Send pending network messages.
|
||||||
|
*/
|
||||||
|
Result Flush();
|
||||||
|
/**
|
||||||
|
* Open a connection to a remote peer.
|
||||||
|
*/
|
||||||
|
Result OpenPeer(NetworkPeerId peerId, char const* routeData);
|
||||||
|
/**
|
||||||
|
* Update the route data for a connected peer.
|
||||||
|
*/
|
||||||
|
Result UpdatePeer(NetworkPeerId peerId, char const* routeData);
|
||||||
|
/**
|
||||||
|
* Close the connection to a remote peer.
|
||||||
|
*/
|
||||||
|
Result ClosePeer(NetworkPeerId peerId);
|
||||||
|
/**
|
||||||
|
* Open a message channel to a connected peer.
|
||||||
|
*/
|
||||||
|
Result OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable);
|
||||||
|
/**
|
||||||
|
* Close a message channel to a connected peer.
|
||||||
|
*/
|
||||||
|
Result CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId);
|
||||||
|
/**
|
||||||
|
* Send a message to a connected peer over an opened message channel.
|
||||||
|
*/
|
||||||
|
Result SendMessage(NetworkPeerId peerId,
|
||||||
|
NetworkChannelId channelId,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength);
|
||||||
|
|
||||||
|
Event<NetworkPeerId, NetworkChannelId, std::uint8_t*, std::uint32_t> OnMessage;
|
||||||
|
Event<char const*> OnRouteUpdate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
NetworkManager() = default;
|
||||||
|
NetworkManager(NetworkManager const& rhs) = delete;
|
||||||
|
NetworkManager& operator=(NetworkManager const& rhs) = delete;
|
||||||
|
NetworkManager(NetworkManager&& rhs) = delete;
|
||||||
|
NetworkManager& operator=(NetworkManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordNetworkManager* internal_;
|
||||||
|
static IDiscordNetworkEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
112
libs/discordGameSDK/cpp/overlay_manager.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "overlay_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class OverlayEvents final {
|
||||||
|
public:
|
||||||
|
static void OnToggle(void* callbackData, bool locked)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->OverlayManager();
|
||||||
|
module.OnToggle((locked != 0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordOverlayEvents OverlayManager::events_{
|
||||||
|
&OverlayEvents::OnToggle,
|
||||||
|
};
|
||||||
|
|
||||||
|
void OverlayManager::IsEnabled(bool* enabled)
|
||||||
|
{
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->is_enabled(internal_, reinterpret_cast<bool*>(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayManager::IsLocked(bool* locked)
|
||||||
|
{
|
||||||
|
if (!locked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->is_locked(internal_, reinterpret_cast<bool*>(locked));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayManager::SetLocked(bool locked, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->set_locked(internal_, (locked ? 1 : 0), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayManager::OpenActivityInvite(ActivityActionType type,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->open_activity_invite(
|
||||||
|
internal_, static_cast<EDiscordActivityActionType>(type), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayManager::OpenGuildInvite(char const* code, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->open_guild_invite(internal_, const_cast<char*>(code), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverlayManager::OpenVoiceSettings(std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->open_voice_settings(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
33
libs/discordGameSDK/cpp/overlay_manager.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class OverlayManager final {
|
||||||
|
public:
|
||||||
|
~OverlayManager() = default;
|
||||||
|
|
||||||
|
void IsEnabled(bool* enabled);
|
||||||
|
void IsLocked(bool* locked);
|
||||||
|
void SetLocked(bool locked, std::function<void(Result)> callback);
|
||||||
|
void OpenActivityInvite(ActivityActionType type, std::function<void(Result)> callback);
|
||||||
|
void OpenGuildInvite(char const* code, std::function<void(Result)> callback);
|
||||||
|
void OpenVoiceSettings(std::function<void(Result)> callback);
|
||||||
|
|
||||||
|
Event<bool> OnToggle;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
OverlayManager() = default;
|
||||||
|
OverlayManager(OverlayManager const& rhs) = delete;
|
||||||
|
OverlayManager& operator=(OverlayManager const& rhs) = delete;
|
||||||
|
OverlayManager(OverlayManager&& rhs) = delete;
|
||||||
|
OverlayManager& operator=(OverlayManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordOverlayManager* internal_;
|
||||||
|
static IDiscordOverlayEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
90
libs/discordGameSDK/cpp/relationship_manager.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "relationship_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class RelationshipEvents final {
|
||||||
|
public:
|
||||||
|
static void OnRefresh(void* callbackData)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->RelationshipManager();
|
||||||
|
module.OnRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnRelationshipUpdate(void* callbackData, DiscordRelationship* relationship)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->RelationshipManager();
|
||||||
|
module.OnRelationshipUpdate(*reinterpret_cast<Relationship const*>(relationship));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordRelationshipEvents RelationshipManager::events_{
|
||||||
|
&RelationshipEvents::OnRefresh,
|
||||||
|
&RelationshipEvents::OnRelationshipUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
void RelationshipManager::Filter(std::function<bool(Relationship const&)> filter)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, DiscordRelationship* relationship) -> bool {
|
||||||
|
auto cb(reinterpret_cast<std::function<bool(Relationship const&)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return (*cb)(*reinterpret_cast<Relationship const*>(relationship));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<bool(Relationship const&)>> cb{};
|
||||||
|
cb.reset(new std::function<bool(Relationship const&)>(std::move(filter)));
|
||||||
|
internal_->filter(internal_, cb.get(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result RelationshipManager::Count(std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->count(internal_, reinterpret_cast<int32_t*>(count));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result RelationshipManager::Get(UserId userId, Relationship* relationship)
|
||||||
|
{
|
||||||
|
if (!relationship) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get(internal_, userId, reinterpret_cast<DiscordRelationship*>(relationship));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result RelationshipManager::GetAt(std::uint32_t index, Relationship* relationship)
|
||||||
|
{
|
||||||
|
if (!relationship) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_at(internal_, index, reinterpret_cast<DiscordRelationship*>(relationship));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
32
libs/discordGameSDK/cpp/relationship_manager.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class RelationshipManager final {
|
||||||
|
public:
|
||||||
|
~RelationshipManager() = default;
|
||||||
|
|
||||||
|
void Filter(std::function<bool(Relationship const&)> filter);
|
||||||
|
Result Count(std::int32_t* count);
|
||||||
|
Result Get(UserId userId, Relationship* relationship);
|
||||||
|
Result GetAt(std::uint32_t index, Relationship* relationship);
|
||||||
|
|
||||||
|
Event<> OnRefresh;
|
||||||
|
Event<Relationship const&> OnRelationshipUpdate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
RelationshipManager() = default;
|
||||||
|
RelationshipManager(RelationshipManager const& rhs) = delete;
|
||||||
|
RelationshipManager& operator=(RelationshipManager const& rhs) = delete;
|
||||||
|
RelationshipManager(RelationshipManager&& rhs) = delete;
|
||||||
|
RelationshipManager& operator=(RelationshipManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordRelationshipManager* internal_;
|
||||||
|
static IDiscordRelationshipEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
158
libs/discordGameSDK/cpp/storage_manager.cpp
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "storage_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
Result StorageManager::Read(char const* name,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength,
|
||||||
|
std::uint32_t* read)
|
||||||
|
{
|
||||||
|
if (!read) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->read(internal_,
|
||||||
|
const_cast<char*>(name),
|
||||||
|
reinterpret_cast<uint8_t*>(data),
|
||||||
|
dataLength,
|
||||||
|
reinterpret_cast<uint32_t*>(read));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StorageManager::ReadAsync(char const* name,
|
||||||
|
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
|
||||||
|
callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), data, dataLength);
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
|
||||||
|
internal_->read_async(internal_, const_cast<char*>(name), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StorageManager::ReadAsyncPartial(
|
||||||
|
char const* name,
|
||||||
|
std::uint64_t offset,
|
||||||
|
std::uint64_t length,
|
||||||
|
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper =
|
||||||
|
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
|
||||||
|
callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), data, dataLength);
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
|
||||||
|
internal_->read_async_partial(
|
||||||
|
internal_, const_cast<char*>(name), offset, length, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageManager::Write(char const* name, std::uint8_t* data, std::uint32_t dataLength)
|
||||||
|
{
|
||||||
|
auto result = internal_->write(
|
||||||
|
internal_, const_cast<char*>(name), reinterpret_cast<uint8_t*>(data), dataLength);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StorageManager::WriteAsync(char const* name,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength,
|
||||||
|
std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->write_async(internal_,
|
||||||
|
const_cast<char*>(name),
|
||||||
|
reinterpret_cast<uint8_t*>(data),
|
||||||
|
dataLength,
|
||||||
|
cb.release(),
|
||||||
|
wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageManager::Delete(char const* name)
|
||||||
|
{
|
||||||
|
auto result = internal_->delete_(internal_, const_cast<char*>(name));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageManager::Exists(char const* name, bool* exists)
|
||||||
|
{
|
||||||
|
if (!exists) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->exists(internal_, const_cast<char*>(name), reinterpret_cast<bool*>(exists));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StorageManager::Count(std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->count(internal_, reinterpret_cast<int32_t*>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageManager::Stat(char const* name, FileStat* stat)
|
||||||
|
{
|
||||||
|
if (!stat) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->stat(internal_, const_cast<char*>(name), reinterpret_cast<DiscordFileStat*>(stat));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageManager::StatAt(std::int32_t index, FileStat* stat)
|
||||||
|
{
|
||||||
|
if (!stat) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->stat_at(internal_, index, reinterpret_cast<DiscordFileStat*>(stat));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageManager::GetPath(char path[4096])
|
||||||
|
{
|
||||||
|
if (!path) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_path(internal_, reinterpret_cast<DiscordPath*>(path));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
46
libs/discordGameSDK/cpp/storage_manager.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class StorageManager final {
|
||||||
|
public:
|
||||||
|
~StorageManager() = default;
|
||||||
|
|
||||||
|
Result Read(char const* name,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength,
|
||||||
|
std::uint32_t* read);
|
||||||
|
void ReadAsync(char const* name,
|
||||||
|
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
|
||||||
|
void ReadAsyncPartial(char const* name,
|
||||||
|
std::uint64_t offset,
|
||||||
|
std::uint64_t length,
|
||||||
|
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
|
||||||
|
Result Write(char const* name, std::uint8_t* data, std::uint32_t dataLength);
|
||||||
|
void WriteAsync(char const* name,
|
||||||
|
std::uint8_t* data,
|
||||||
|
std::uint32_t dataLength,
|
||||||
|
std::function<void(Result)> callback);
|
||||||
|
Result Delete(char const* name);
|
||||||
|
Result Exists(char const* name, bool* exists);
|
||||||
|
void Count(std::int32_t* count);
|
||||||
|
Result Stat(char const* name, FileStat* stat);
|
||||||
|
Result StatAt(std::int32_t index, FileStat* stat);
|
||||||
|
Result GetPath(char path[4096]);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
StorageManager() = default;
|
||||||
|
StorageManager(StorageManager const& rhs) = delete;
|
||||||
|
StorageManager& operator=(StorageManager const& rhs) = delete;
|
||||||
|
StorageManager(StorageManager&& rhs) = delete;
|
||||||
|
StorageManager& operator=(StorageManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordStorageManager* internal_;
|
||||||
|
static IDiscordStorageEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
160
libs/discordGameSDK/cpp/store_manager.cpp
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "store_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class StoreEvents final {
|
||||||
|
public:
|
||||||
|
static void OnEntitlementCreate(void* callbackData, DiscordEntitlement* entitlement)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->StoreManager();
|
||||||
|
module.OnEntitlementCreate(*reinterpret_cast<Entitlement const*>(entitlement));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnEntitlementDelete(void* callbackData, DiscordEntitlement* entitlement)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->StoreManager();
|
||||||
|
module.OnEntitlementDelete(*reinterpret_cast<Entitlement const*>(entitlement));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordStoreEvents StoreManager::events_{
|
||||||
|
&StoreEvents::OnEntitlementCreate,
|
||||||
|
&StoreEvents::OnEntitlementDelete,
|
||||||
|
};
|
||||||
|
|
||||||
|
void StoreManager::FetchSkus(std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->fetch_skus(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreManager::CountSkus(std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->count_skus(internal_, reinterpret_cast<int32_t*>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StoreManager::GetSku(Snowflake skuId, Sku* sku)
|
||||||
|
{
|
||||||
|
if (!sku) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_sku(internal_, skuId, reinterpret_cast<DiscordSku*>(sku));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StoreManager::GetSkuAt(std::int32_t index, Sku* sku)
|
||||||
|
{
|
||||||
|
if (!sku) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_sku_at(internal_, index, reinterpret_cast<DiscordSku*>(sku));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreManager::FetchEntitlements(std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->fetch_entitlements(internal_, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreManager::CountEntitlements(std::int32_t* count)
|
||||||
|
{
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal_->count_entitlements(internal_, reinterpret_cast<int32_t*>(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StoreManager::GetEntitlement(Snowflake entitlementId, Entitlement* entitlement)
|
||||||
|
{
|
||||||
|
if (!entitlement) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_entitlement(
|
||||||
|
internal_, entitlementId, reinterpret_cast<DiscordEntitlement*>(entitlement));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StoreManager::GetEntitlementAt(std::int32_t index, Entitlement* entitlement)
|
||||||
|
{
|
||||||
|
if (!entitlement) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_entitlement_at(
|
||||||
|
internal_, index, reinterpret_cast<DiscordEntitlement*>(entitlement));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StoreManager::HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement)
|
||||||
|
{
|
||||||
|
if (!hasEntitlement) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->has_sku_entitlement(internal_, skuId, reinterpret_cast<bool*>(hasEntitlement));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreManager::StartPurchase(Snowflake skuId, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->start_purchase(internal_, skuId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
38
libs/discordGameSDK/cpp/store_manager.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class StoreManager final {
|
||||||
|
public:
|
||||||
|
~StoreManager() = default;
|
||||||
|
|
||||||
|
void FetchSkus(std::function<void(Result)> callback);
|
||||||
|
void CountSkus(std::int32_t* count);
|
||||||
|
Result GetSku(Snowflake skuId, Sku* sku);
|
||||||
|
Result GetSkuAt(std::int32_t index, Sku* sku);
|
||||||
|
void FetchEntitlements(std::function<void(Result)> callback);
|
||||||
|
void CountEntitlements(std::int32_t* count);
|
||||||
|
Result GetEntitlement(Snowflake entitlementId, Entitlement* entitlement);
|
||||||
|
Result GetEntitlementAt(std::int32_t index, Entitlement* entitlement);
|
||||||
|
Result HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement);
|
||||||
|
void StartPurchase(Snowflake skuId, std::function<void(Result)> callback);
|
||||||
|
|
||||||
|
Event<Entitlement const&> OnEntitlementCreate;
|
||||||
|
Event<Entitlement const&> OnEntitlementDelete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
StoreManager() = default;
|
||||||
|
StoreManager(StoreManager const& rhs) = delete;
|
||||||
|
StoreManager& operator=(StoreManager const& rhs) = delete;
|
||||||
|
StoreManager(StoreManager&& rhs) = delete;
|
||||||
|
StoreManager& operator=(StoreManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordStoreManager* internal_;
|
||||||
|
static IDiscordStoreEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
769
libs/discordGameSDK/cpp/types.cpp
Normal file
@@ -0,0 +1,769 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
void User::SetId(UserId id)
|
||||||
|
{
|
||||||
|
internal_.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserId User::GetId() const
|
||||||
|
{
|
||||||
|
return internal_.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::SetUsername(char const* username)
|
||||||
|
{
|
||||||
|
strncpy(internal_.username, username, 256);
|
||||||
|
internal_.username[256 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* User::GetUsername() const
|
||||||
|
{
|
||||||
|
return internal_.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::SetDiscriminator(char const* discriminator)
|
||||||
|
{
|
||||||
|
strncpy(internal_.discriminator, discriminator, 8);
|
||||||
|
internal_.discriminator[8 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* User::GetDiscriminator() const
|
||||||
|
{
|
||||||
|
return internal_.discriminator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::SetAvatar(char const* avatar)
|
||||||
|
{
|
||||||
|
strncpy(internal_.avatar, avatar, 128);
|
||||||
|
internal_.avatar[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* User::GetAvatar() const
|
||||||
|
{
|
||||||
|
return internal_.avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void User::SetBot(bool bot)
|
||||||
|
{
|
||||||
|
internal_.bot = bot;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool User::GetBot() const
|
||||||
|
{
|
||||||
|
return internal_.bot != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OAuth2Token::SetAccessToken(char const* accessToken)
|
||||||
|
{
|
||||||
|
strncpy(internal_.access_token, accessToken, 128);
|
||||||
|
internal_.access_token[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* OAuth2Token::GetAccessToken() const
|
||||||
|
{
|
||||||
|
return internal_.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OAuth2Token::SetScopes(char const* scopes)
|
||||||
|
{
|
||||||
|
strncpy(internal_.scopes, scopes, 1024);
|
||||||
|
internal_.scopes[1024 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* OAuth2Token::GetScopes() const
|
||||||
|
{
|
||||||
|
return internal_.scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OAuth2Token::SetExpires(Timestamp expires)
|
||||||
|
{
|
||||||
|
internal_.expires = expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timestamp OAuth2Token::GetExpires() const
|
||||||
|
{
|
||||||
|
return internal_.expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageHandle::SetType(ImageType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordImageType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageType ImageHandle::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<ImageType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageHandle::SetId(std::int64_t id)
|
||||||
|
{
|
||||||
|
internal_.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t ImageHandle::GetId() const
|
||||||
|
{
|
||||||
|
return internal_.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageHandle::SetSize(std::uint32_t size)
|
||||||
|
{
|
||||||
|
internal_.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t ImageHandle::GetSize() const
|
||||||
|
{
|
||||||
|
return internal_.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageDimensions::SetWidth(std::uint32_t width)
|
||||||
|
{
|
||||||
|
internal_.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t ImageDimensions::GetWidth() const
|
||||||
|
{
|
||||||
|
return internal_.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImageDimensions::SetHeight(std::uint32_t height)
|
||||||
|
{
|
||||||
|
internal_.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t ImageDimensions::GetHeight() const
|
||||||
|
{
|
||||||
|
return internal_.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityTimestamps::SetStart(Timestamp start)
|
||||||
|
{
|
||||||
|
internal_.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timestamp ActivityTimestamps::GetStart() const
|
||||||
|
{
|
||||||
|
return internal_.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityTimestamps::SetEnd(Timestamp end)
|
||||||
|
{
|
||||||
|
internal_.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timestamp ActivityTimestamps::GetEnd() const
|
||||||
|
{
|
||||||
|
return internal_.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityAssets::SetLargeImage(char const* largeImage)
|
||||||
|
{
|
||||||
|
strncpy(internal_.large_image, largeImage, 128);
|
||||||
|
internal_.large_image[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivityAssets::GetLargeImage() const
|
||||||
|
{
|
||||||
|
return internal_.large_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityAssets::SetLargeText(char const* largeText)
|
||||||
|
{
|
||||||
|
strncpy(internal_.large_text, largeText, 128);
|
||||||
|
internal_.large_text[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivityAssets::GetLargeText() const
|
||||||
|
{
|
||||||
|
return internal_.large_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityAssets::SetSmallImage(char const* smallImage)
|
||||||
|
{
|
||||||
|
strncpy(internal_.small_image, smallImage, 128);
|
||||||
|
internal_.small_image[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivityAssets::GetSmallImage() const
|
||||||
|
{
|
||||||
|
return internal_.small_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityAssets::SetSmallText(char const* smallText)
|
||||||
|
{
|
||||||
|
strncpy(internal_.small_text, smallText, 128);
|
||||||
|
internal_.small_text[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivityAssets::GetSmallText() const
|
||||||
|
{
|
||||||
|
return internal_.small_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PartySize::SetCurrentSize(std::int32_t currentSize)
|
||||||
|
{
|
||||||
|
internal_.current_size = currentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t PartySize::GetCurrentSize() const
|
||||||
|
{
|
||||||
|
return internal_.current_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PartySize::SetMaxSize(std::int32_t maxSize)
|
||||||
|
{
|
||||||
|
internal_.max_size = maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int32_t PartySize::GetMaxSize() const
|
||||||
|
{
|
||||||
|
return internal_.max_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityParty::SetId(char const* id)
|
||||||
|
{
|
||||||
|
strncpy(internal_.id, id, 128);
|
||||||
|
internal_.id[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivityParty::GetId() const
|
||||||
|
{
|
||||||
|
return internal_.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
PartySize& ActivityParty::GetSize()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<PartySize&>(internal_.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
PartySize const& ActivityParty::GetSize() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<PartySize const&>(internal_.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySecrets::SetMatch(char const* match)
|
||||||
|
{
|
||||||
|
strncpy(internal_.match, match, 128);
|
||||||
|
internal_.match[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivitySecrets::GetMatch() const
|
||||||
|
{
|
||||||
|
return internal_.match;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySecrets::SetJoin(char const* join)
|
||||||
|
{
|
||||||
|
strncpy(internal_.join, join, 128);
|
||||||
|
internal_.join[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivitySecrets::GetJoin() const
|
||||||
|
{
|
||||||
|
return internal_.join;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySecrets::SetSpectate(char const* spectate)
|
||||||
|
{
|
||||||
|
strncpy(internal_.spectate, spectate, 128);
|
||||||
|
internal_.spectate[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* ActivitySecrets::GetSpectate() const
|
||||||
|
{
|
||||||
|
return internal_.spectate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Activity::SetType(ActivityType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordActivityType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityType Activity::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<ActivityType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Activity::SetApplicationId(std::int64_t applicationId)
|
||||||
|
{
|
||||||
|
internal_.application_id = applicationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::int64_t Activity::GetApplicationId() const
|
||||||
|
{
|
||||||
|
return internal_.application_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Activity::SetName(char const* name)
|
||||||
|
{
|
||||||
|
strncpy(internal_.name, name, 128);
|
||||||
|
internal_.name[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* Activity::GetName() const
|
||||||
|
{
|
||||||
|
return internal_.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Activity::SetState(char const* state)
|
||||||
|
{
|
||||||
|
strncpy(internal_.state, state, 128);
|
||||||
|
internal_.state[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* Activity::GetState() const
|
||||||
|
{
|
||||||
|
return internal_.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Activity::SetDetails(char const* details)
|
||||||
|
{
|
||||||
|
strncpy(internal_.details, details, 128);
|
||||||
|
internal_.details[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* Activity::GetDetails() const
|
||||||
|
{
|
||||||
|
return internal_.details;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityTimestamps& Activity::GetTimestamps()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivityTimestamps&>(internal_.timestamps);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityTimestamps const& Activity::GetTimestamps() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivityTimestamps const&>(internal_.timestamps);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityAssets& Activity::GetAssets()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivityAssets&>(internal_.assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityAssets const& Activity::GetAssets() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivityAssets const&>(internal_.assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityParty& Activity::GetParty()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivityParty&>(internal_.party);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityParty const& Activity::GetParty() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivityParty const&>(internal_.party);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivitySecrets& Activity::GetSecrets()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivitySecrets&>(internal_.secrets);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivitySecrets const& Activity::GetSecrets() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<ActivitySecrets const&>(internal_.secrets);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Activity::SetInstance(bool instance)
|
||||||
|
{
|
||||||
|
internal_.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Activity::GetInstance() const
|
||||||
|
{
|
||||||
|
return internal_.instance != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Presence::SetStatus(Status status)
|
||||||
|
{
|
||||||
|
internal_.status = static_cast<EDiscordStatus>(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Presence::GetStatus() const
|
||||||
|
{
|
||||||
|
return static_cast<Status>(internal_.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity& Presence::GetActivity()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<Activity&>(internal_.activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity const& Presence::GetActivity() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<Activity const&>(internal_.activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Relationship::SetType(RelationshipType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordRelationshipType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationshipType Relationship::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<RelationshipType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
User& Relationship::GetUser()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<User&>(internal_.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
User const& Relationship::GetUser() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<User const&>(internal_.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
Presence& Relationship::GetPresence()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<Presence&>(internal_.presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
Presence const& Relationship::GetPresence() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<Presence const&>(internal_.presence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lobby::SetId(LobbyId id)
|
||||||
|
{
|
||||||
|
internal_.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
LobbyId Lobby::GetId() const
|
||||||
|
{
|
||||||
|
return internal_.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lobby::SetType(LobbyType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordLobbyType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
LobbyType Lobby::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<LobbyType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lobby::SetOwnerId(UserId ownerId)
|
||||||
|
{
|
||||||
|
internal_.owner_id = ownerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserId Lobby::GetOwnerId() const
|
||||||
|
{
|
||||||
|
return internal_.owner_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lobby::SetSecret(LobbySecret secret)
|
||||||
|
{
|
||||||
|
strncpy(internal_.secret, secret, 128);
|
||||||
|
internal_.secret[128 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
LobbySecret Lobby::GetSecret() const
|
||||||
|
{
|
||||||
|
return internal_.secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lobby::SetCapacity(std::uint32_t capacity)
|
||||||
|
{
|
||||||
|
internal_.capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t Lobby::GetCapacity() const
|
||||||
|
{
|
||||||
|
return internal_.capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lobby::SetLocked(bool locked)
|
||||||
|
{
|
||||||
|
internal_.locked = locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lobby::GetLocked() const
|
||||||
|
{
|
||||||
|
return internal_.locked != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileStat::SetFilename(char const* filename)
|
||||||
|
{
|
||||||
|
strncpy(internal_.filename, filename, 260);
|
||||||
|
internal_.filename[260 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* FileStat::GetFilename() const
|
||||||
|
{
|
||||||
|
return internal_.filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileStat::SetSize(std::uint64_t size)
|
||||||
|
{
|
||||||
|
internal_.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t FileStat::GetSize() const
|
||||||
|
{
|
||||||
|
return internal_.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileStat::SetLastModified(std::uint64_t lastModified)
|
||||||
|
{
|
||||||
|
internal_.last_modified = lastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t FileStat::GetLastModified() const
|
||||||
|
{
|
||||||
|
return internal_.last_modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entitlement::SetId(Snowflake id)
|
||||||
|
{
|
||||||
|
internal_.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Snowflake Entitlement::GetId() const
|
||||||
|
{
|
||||||
|
return internal_.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entitlement::SetType(EntitlementType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordEntitlementType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
EntitlementType Entitlement::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<EntitlementType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Entitlement::SetSkuId(Snowflake skuId)
|
||||||
|
{
|
||||||
|
internal_.sku_id = skuId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Snowflake Entitlement::GetSkuId() const
|
||||||
|
{
|
||||||
|
return internal_.sku_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkuPrice::SetAmount(std::uint32_t amount)
|
||||||
|
{
|
||||||
|
internal_.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t SkuPrice::GetAmount() const
|
||||||
|
{
|
||||||
|
return internal_.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkuPrice::SetCurrency(char const* currency)
|
||||||
|
{
|
||||||
|
strncpy(internal_.currency, currency, 16);
|
||||||
|
internal_.currency[16 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* SkuPrice::GetCurrency() const
|
||||||
|
{
|
||||||
|
return internal_.currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sku::SetId(Snowflake id)
|
||||||
|
{
|
||||||
|
internal_.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Snowflake Sku::GetId() const
|
||||||
|
{
|
||||||
|
return internal_.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sku::SetType(SkuType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordSkuType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
SkuType Sku::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<SkuType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sku::SetName(char const* name)
|
||||||
|
{
|
||||||
|
strncpy(internal_.name, name, 256);
|
||||||
|
internal_.name[256 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* Sku::GetName() const
|
||||||
|
{
|
||||||
|
return internal_.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkuPrice& Sku::GetPrice()
|
||||||
|
{
|
||||||
|
return reinterpret_cast<SkuPrice&>(internal_.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
SkuPrice const& Sku::GetPrice() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<SkuPrice const&>(internal_.price);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputMode::SetType(InputModeType type)
|
||||||
|
{
|
||||||
|
internal_.type = static_cast<EDiscordInputModeType>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputModeType InputMode::GetType() const
|
||||||
|
{
|
||||||
|
return static_cast<InputModeType>(internal_.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputMode::SetShortcut(char const* shortcut)
|
||||||
|
{
|
||||||
|
strncpy(internal_.shortcut, shortcut, 256);
|
||||||
|
internal_.shortcut[256 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char const* InputMode::GetShortcut() const
|
||||||
|
{
|
||||||
|
return internal_.shortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserAchievement::SetUserId(Snowflake userId)
|
||||||
|
{
|
||||||
|
internal_.user_id = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Snowflake UserAchievement::GetUserId() const
|
||||||
|
{
|
||||||
|
return internal_.user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserAchievement::SetAchievementId(Snowflake achievementId)
|
||||||
|
{
|
||||||
|
internal_.achievement_id = achievementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Snowflake UserAchievement::GetAchievementId() const
|
||||||
|
{
|
||||||
|
return internal_.achievement_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserAchievement::SetPercentComplete(std::uint8_t percentComplete)
|
||||||
|
{
|
||||||
|
internal_.percent_complete = percentComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint8_t UserAchievement::GetPercentComplete() const
|
||||||
|
{
|
||||||
|
return internal_.percent_complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserAchievement::SetUnlockedAt(DateTime unlockedAt)
|
||||||
|
{
|
||||||
|
strncpy(internal_.unlocked_at, unlockedAt, 64);
|
||||||
|
internal_.unlocked_at[64 - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime UserAchievement::GetUnlockedAt() const
|
||||||
|
{
|
||||||
|
return internal_.unlocked_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyTransaction::SetType(LobbyType type)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_type(internal_, static_cast<EDiscordLobbyType>(type));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyTransaction::SetOwner(UserId ownerId)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_owner(internal_, ownerId);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyTransaction::SetCapacity(std::uint32_t capacity)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_capacity(internal_, capacity);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyTransaction::SetMetadata(MetadataKey key, MetadataValue value)
|
||||||
|
{
|
||||||
|
auto result =
|
||||||
|
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyTransaction::DeleteMetadata(MetadataKey key)
|
||||||
|
{
|
||||||
|
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyTransaction::SetLocked(bool locked)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_locked(internal_, (locked ? 1 : 0));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyMemberTransaction::SetMetadata(MetadataKey key, MetadataValue value)
|
||||||
|
{
|
||||||
|
auto result =
|
||||||
|
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbyMemberTransaction::DeleteMetadata(MetadataKey key)
|
||||||
|
{
|
||||||
|
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbySearchQuery::Filter(MetadataKey key,
|
||||||
|
LobbySearchComparison comparison,
|
||||||
|
LobbySearchCast cast,
|
||||||
|
MetadataValue value)
|
||||||
|
{
|
||||||
|
auto result = internal_->filter(internal_,
|
||||||
|
const_cast<char*>(key),
|
||||||
|
static_cast<EDiscordLobbySearchComparison>(comparison),
|
||||||
|
static_cast<EDiscordLobbySearchCast>(cast),
|
||||||
|
const_cast<char*>(value));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbySearchQuery::Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value)
|
||||||
|
{
|
||||||
|
auto result = internal_->sort(internal_,
|
||||||
|
const_cast<char*>(key),
|
||||||
|
static_cast<EDiscordLobbySearchCast>(cast),
|
||||||
|
const_cast<char*>(value));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbySearchQuery::Limit(std::uint32_t limit)
|
||||||
|
{
|
||||||
|
auto result = internal_->limit(internal_, limit);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result LobbySearchQuery::Distance(LobbySearchDistance distance)
|
||||||
|
{
|
||||||
|
auto result =
|
||||||
|
internal_->distance(internal_, static_cast<EDiscordLobbySearchDistance>(distance));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
491
libs/discordGameSDK/cpp/types.h
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ffi.h"
|
||||||
|
#include "event.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
enum class Result {
|
||||||
|
Ok = 0,
|
||||||
|
ServiceUnavailable = 1,
|
||||||
|
InvalidVersion = 2,
|
||||||
|
LockFailed = 3,
|
||||||
|
InternalError = 4,
|
||||||
|
InvalidPayload = 5,
|
||||||
|
InvalidCommand = 6,
|
||||||
|
InvalidPermissions = 7,
|
||||||
|
NotFetched = 8,
|
||||||
|
NotFound = 9,
|
||||||
|
Conflict = 10,
|
||||||
|
InvalidSecret = 11,
|
||||||
|
InvalidJoinSecret = 12,
|
||||||
|
NoEligibleActivity = 13,
|
||||||
|
InvalidInvite = 14,
|
||||||
|
NotAuthenticated = 15,
|
||||||
|
InvalidAccessToken = 16,
|
||||||
|
ApplicationMismatch = 17,
|
||||||
|
InvalidDataUrl = 18,
|
||||||
|
InvalidBase64 = 19,
|
||||||
|
NotFiltered = 20,
|
||||||
|
LobbyFull = 21,
|
||||||
|
InvalidLobbySecret = 22,
|
||||||
|
InvalidFilename = 23,
|
||||||
|
InvalidFileSize = 24,
|
||||||
|
InvalidEntitlement = 25,
|
||||||
|
NotInstalled = 26,
|
||||||
|
NotRunning = 27,
|
||||||
|
InsufficientBuffer = 28,
|
||||||
|
PurchaseCanceled = 29,
|
||||||
|
InvalidGuild = 30,
|
||||||
|
InvalidEvent = 31,
|
||||||
|
InvalidChannel = 32,
|
||||||
|
InvalidOrigin = 33,
|
||||||
|
RateLimited = 34,
|
||||||
|
OAuth2Error = 35,
|
||||||
|
SelectChannelTimeout = 36,
|
||||||
|
GetGuildTimeout = 37,
|
||||||
|
SelectVoiceForceRequired = 38,
|
||||||
|
CaptureShortcutAlreadyListening = 39,
|
||||||
|
UnauthorizedForAchievement = 40,
|
||||||
|
InvalidGiftCode = 41,
|
||||||
|
PurchaseError = 42,
|
||||||
|
TransactionAborted = 43,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CreateFlags {
|
||||||
|
Default = 0,
|
||||||
|
NoRequireDiscord = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LogLevel {
|
||||||
|
Error = 1,
|
||||||
|
Warn,
|
||||||
|
Info,
|
||||||
|
Debug,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class UserFlag {
|
||||||
|
Partner = 2,
|
||||||
|
HypeSquadEvents = 4,
|
||||||
|
HypeSquadHouse1 = 64,
|
||||||
|
HypeSquadHouse2 = 128,
|
||||||
|
HypeSquadHouse3 = 256,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PremiumType {
|
||||||
|
None = 0,
|
||||||
|
Tier1 = 1,
|
||||||
|
Tier2 = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ImageType {
|
||||||
|
User,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ActivityType {
|
||||||
|
Playing,
|
||||||
|
Streaming,
|
||||||
|
Listening,
|
||||||
|
Watching,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ActivityActionType {
|
||||||
|
Join = 1,
|
||||||
|
Spectate,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ActivityJoinRequestReply {
|
||||||
|
No,
|
||||||
|
Yes,
|
||||||
|
Ignore,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
Offline = 0,
|
||||||
|
Online = 1,
|
||||||
|
Idle = 2,
|
||||||
|
DoNotDisturb = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RelationshipType {
|
||||||
|
None,
|
||||||
|
Friend,
|
||||||
|
Blocked,
|
||||||
|
PendingIncoming,
|
||||||
|
PendingOutgoing,
|
||||||
|
Implicit,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LobbyType {
|
||||||
|
Private = 1,
|
||||||
|
Public,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LobbySearchComparison {
|
||||||
|
LessThanOrEqual = -2,
|
||||||
|
LessThan,
|
||||||
|
Equal,
|
||||||
|
GreaterThan,
|
||||||
|
GreaterThanOrEqual,
|
||||||
|
NotEqual,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LobbySearchCast {
|
||||||
|
String = 1,
|
||||||
|
Number,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LobbySearchDistance {
|
||||||
|
Local,
|
||||||
|
Default,
|
||||||
|
Extended,
|
||||||
|
Global,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EntitlementType {
|
||||||
|
Purchase = 1,
|
||||||
|
PremiumSubscription,
|
||||||
|
DeveloperGift,
|
||||||
|
TestModePurchase,
|
||||||
|
FreePurchase,
|
||||||
|
UserGift,
|
||||||
|
PremiumPurchase,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SkuType {
|
||||||
|
Application = 1,
|
||||||
|
DLC,
|
||||||
|
Consumable,
|
||||||
|
Bundle,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class InputModeType {
|
||||||
|
VoiceActivity = 0,
|
||||||
|
PushToTalk,
|
||||||
|
};
|
||||||
|
|
||||||
|
using ClientId = std::int64_t;
|
||||||
|
using Version = std::int32_t;
|
||||||
|
using Snowflake = std::int64_t;
|
||||||
|
using Timestamp = std::int64_t;
|
||||||
|
using UserId = Snowflake;
|
||||||
|
using Locale = char const*;
|
||||||
|
using Branch = char const*;
|
||||||
|
using LobbyId = Snowflake;
|
||||||
|
using LobbySecret = char const*;
|
||||||
|
using MetadataKey = char const*;
|
||||||
|
using MetadataValue = char const*;
|
||||||
|
using NetworkPeerId = std::uint64_t;
|
||||||
|
using NetworkChannelId = std::uint8_t;
|
||||||
|
using Path = char const*;
|
||||||
|
using DateTime = char const*;
|
||||||
|
|
||||||
|
class User final {
|
||||||
|
public:
|
||||||
|
void SetId(UserId id);
|
||||||
|
UserId GetId() const;
|
||||||
|
void SetUsername(char const* username);
|
||||||
|
char const* GetUsername() const;
|
||||||
|
void SetDiscriminator(char const* discriminator);
|
||||||
|
char const* GetDiscriminator() const;
|
||||||
|
void SetAvatar(char const* avatar);
|
||||||
|
char const* GetAvatar() const;
|
||||||
|
void SetBot(bool bot);
|
||||||
|
bool GetBot() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordUser internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OAuth2Token final {
|
||||||
|
public:
|
||||||
|
void SetAccessToken(char const* accessToken);
|
||||||
|
char const* GetAccessToken() const;
|
||||||
|
void SetScopes(char const* scopes);
|
||||||
|
char const* GetScopes() const;
|
||||||
|
void SetExpires(Timestamp expires);
|
||||||
|
Timestamp GetExpires() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordOAuth2Token internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageHandle final {
|
||||||
|
public:
|
||||||
|
void SetType(ImageType type);
|
||||||
|
ImageType GetType() const;
|
||||||
|
void SetId(std::int64_t id);
|
||||||
|
std::int64_t GetId() const;
|
||||||
|
void SetSize(std::uint32_t size);
|
||||||
|
std::uint32_t GetSize() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordImageHandle internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageDimensions final {
|
||||||
|
public:
|
||||||
|
void SetWidth(std::uint32_t width);
|
||||||
|
std::uint32_t GetWidth() const;
|
||||||
|
void SetHeight(std::uint32_t height);
|
||||||
|
std::uint32_t GetHeight() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordImageDimensions internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActivityTimestamps final {
|
||||||
|
public:
|
||||||
|
void SetStart(Timestamp start);
|
||||||
|
Timestamp GetStart() const;
|
||||||
|
void SetEnd(Timestamp end);
|
||||||
|
Timestamp GetEnd() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordActivityTimestamps internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActivityAssets final {
|
||||||
|
public:
|
||||||
|
void SetLargeImage(char const* largeImage);
|
||||||
|
char const* GetLargeImage() const;
|
||||||
|
void SetLargeText(char const* largeText);
|
||||||
|
char const* GetLargeText() const;
|
||||||
|
void SetSmallImage(char const* smallImage);
|
||||||
|
char const* GetSmallImage() const;
|
||||||
|
void SetSmallText(char const* smallText);
|
||||||
|
char const* GetSmallText() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordActivityAssets internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PartySize final {
|
||||||
|
public:
|
||||||
|
void SetCurrentSize(std::int32_t currentSize);
|
||||||
|
std::int32_t GetCurrentSize() const;
|
||||||
|
void SetMaxSize(std::int32_t maxSize);
|
||||||
|
std::int32_t GetMaxSize() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordPartySize internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActivityParty final {
|
||||||
|
public:
|
||||||
|
void SetId(char const* id);
|
||||||
|
char const* GetId() const;
|
||||||
|
PartySize& GetSize();
|
||||||
|
PartySize const& GetSize() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordActivityParty internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ActivitySecrets final {
|
||||||
|
public:
|
||||||
|
void SetMatch(char const* match);
|
||||||
|
char const* GetMatch() const;
|
||||||
|
void SetJoin(char const* join);
|
||||||
|
char const* GetJoin() const;
|
||||||
|
void SetSpectate(char const* spectate);
|
||||||
|
char const* GetSpectate() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordActivitySecrets internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Activity final {
|
||||||
|
public:
|
||||||
|
void SetType(ActivityType type);
|
||||||
|
ActivityType GetType() const;
|
||||||
|
void SetApplicationId(std::int64_t applicationId);
|
||||||
|
std::int64_t GetApplicationId() const;
|
||||||
|
void SetName(char const* name);
|
||||||
|
char const* GetName() const;
|
||||||
|
void SetState(char const* state);
|
||||||
|
char const* GetState() const;
|
||||||
|
void SetDetails(char const* details);
|
||||||
|
char const* GetDetails() const;
|
||||||
|
ActivityTimestamps& GetTimestamps();
|
||||||
|
ActivityTimestamps const& GetTimestamps() const;
|
||||||
|
ActivityAssets& GetAssets();
|
||||||
|
ActivityAssets const& GetAssets() const;
|
||||||
|
ActivityParty& GetParty();
|
||||||
|
ActivityParty const& GetParty() const;
|
||||||
|
ActivitySecrets& GetSecrets();
|
||||||
|
ActivitySecrets const& GetSecrets() const;
|
||||||
|
void SetInstance(bool instance);
|
||||||
|
bool GetInstance() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordActivity internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Presence final {
|
||||||
|
public:
|
||||||
|
void SetStatus(Status status);
|
||||||
|
Status GetStatus() const;
|
||||||
|
Activity& GetActivity();
|
||||||
|
Activity const& GetActivity() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordPresence internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Relationship final {
|
||||||
|
public:
|
||||||
|
void SetType(RelationshipType type);
|
||||||
|
RelationshipType GetType() const;
|
||||||
|
User& GetUser();
|
||||||
|
User const& GetUser() const;
|
||||||
|
Presence& GetPresence();
|
||||||
|
Presence const& GetPresence() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordRelationship internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lobby final {
|
||||||
|
public:
|
||||||
|
void SetId(LobbyId id);
|
||||||
|
LobbyId GetId() const;
|
||||||
|
void SetType(LobbyType type);
|
||||||
|
LobbyType GetType() const;
|
||||||
|
void SetOwnerId(UserId ownerId);
|
||||||
|
UserId GetOwnerId() const;
|
||||||
|
void SetSecret(LobbySecret secret);
|
||||||
|
LobbySecret GetSecret() const;
|
||||||
|
void SetCapacity(std::uint32_t capacity);
|
||||||
|
std::uint32_t GetCapacity() const;
|
||||||
|
void SetLocked(bool locked);
|
||||||
|
bool GetLocked() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordLobby internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileStat final {
|
||||||
|
public:
|
||||||
|
void SetFilename(char const* filename);
|
||||||
|
char const* GetFilename() const;
|
||||||
|
void SetSize(std::uint64_t size);
|
||||||
|
std::uint64_t GetSize() const;
|
||||||
|
void SetLastModified(std::uint64_t lastModified);
|
||||||
|
std::uint64_t GetLastModified() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordFileStat internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Entitlement final {
|
||||||
|
public:
|
||||||
|
void SetId(Snowflake id);
|
||||||
|
Snowflake GetId() const;
|
||||||
|
void SetType(EntitlementType type);
|
||||||
|
EntitlementType GetType() const;
|
||||||
|
void SetSkuId(Snowflake skuId);
|
||||||
|
Snowflake GetSkuId() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordEntitlement internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SkuPrice final {
|
||||||
|
public:
|
||||||
|
void SetAmount(std::uint32_t amount);
|
||||||
|
std::uint32_t GetAmount() const;
|
||||||
|
void SetCurrency(char const* currency);
|
||||||
|
char const* GetCurrency() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordSkuPrice internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Sku final {
|
||||||
|
public:
|
||||||
|
void SetId(Snowflake id);
|
||||||
|
Snowflake GetId() const;
|
||||||
|
void SetType(SkuType type);
|
||||||
|
SkuType GetType() const;
|
||||||
|
void SetName(char const* name);
|
||||||
|
char const* GetName() const;
|
||||||
|
SkuPrice& GetPrice();
|
||||||
|
SkuPrice const& GetPrice() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordSku internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputMode final {
|
||||||
|
public:
|
||||||
|
void SetType(InputModeType type);
|
||||||
|
InputModeType GetType() const;
|
||||||
|
void SetShortcut(char const* shortcut);
|
||||||
|
char const* GetShortcut() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordInputMode internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UserAchievement final {
|
||||||
|
public:
|
||||||
|
void SetUserId(Snowflake userId);
|
||||||
|
Snowflake GetUserId() const;
|
||||||
|
void SetAchievementId(Snowflake achievementId);
|
||||||
|
Snowflake GetAchievementId() const;
|
||||||
|
void SetPercentComplete(std::uint8_t percentComplete);
|
||||||
|
std::uint8_t GetPercentComplete() const;
|
||||||
|
void SetUnlockedAt(DateTime unlockedAt);
|
||||||
|
DateTime GetUnlockedAt() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DiscordUserAchievement internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LobbyTransaction final {
|
||||||
|
public:
|
||||||
|
Result SetType(LobbyType type);
|
||||||
|
Result SetOwner(UserId ownerId);
|
||||||
|
Result SetCapacity(std::uint32_t capacity);
|
||||||
|
Result SetMetadata(MetadataKey key, MetadataValue value);
|
||||||
|
Result DeleteMetadata(MetadataKey key);
|
||||||
|
Result SetLocked(bool locked);
|
||||||
|
|
||||||
|
IDiscordLobbyTransaction** Receive() { return &internal_; }
|
||||||
|
IDiscordLobbyTransaction* Internal() { return internal_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IDiscordLobbyTransaction* internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LobbyMemberTransaction final {
|
||||||
|
public:
|
||||||
|
Result SetMetadata(MetadataKey key, MetadataValue value);
|
||||||
|
Result DeleteMetadata(MetadataKey key);
|
||||||
|
|
||||||
|
IDiscordLobbyMemberTransaction** Receive() { return &internal_; }
|
||||||
|
IDiscordLobbyMemberTransaction* Internal() { return internal_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IDiscordLobbyMemberTransaction* internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LobbySearchQuery final {
|
||||||
|
public:
|
||||||
|
Result Filter(MetadataKey key,
|
||||||
|
LobbySearchComparison comparison,
|
||||||
|
LobbySearchCast cast,
|
||||||
|
MetadataValue value);
|
||||||
|
Result Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value);
|
||||||
|
Result Limit(std::uint32_t limit);
|
||||||
|
Result Distance(LobbySearchDistance distance);
|
||||||
|
|
||||||
|
IDiscordLobbySearchQuery** Receive() { return &internal_; }
|
||||||
|
IDiscordLobbySearchQuery* Internal() { return internal_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IDiscordLobbySearchQuery* internal_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
80
libs/discordGameSDK/cpp/user_manager.cpp
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "user_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class UserEvents final {
|
||||||
|
public:
|
||||||
|
static void OnCurrentUserUpdate(void* callbackData)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->UserManager();
|
||||||
|
module.OnCurrentUserUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordUserEvents UserManager::events_{
|
||||||
|
&UserEvents::OnCurrentUserUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
Result UserManager::GetCurrentUser(User* currentUser)
|
||||||
|
{
|
||||||
|
if (!currentUser) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_current_user(internal_, reinterpret_cast<DiscordUser*>(currentUser));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserManager::GetUser(UserId userId, std::function<void(Result, User const&)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result, DiscordUser* user) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result, User const&)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result, User const&)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result), *reinterpret_cast<User const*>(user));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result, User const&)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result, User const&)>(std::move(callback)));
|
||||||
|
internal_->get_user(internal_, userId, cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UserManager::GetCurrentUserPremiumType(PremiumType* premiumType)
|
||||||
|
{
|
||||||
|
if (!premiumType) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->get_current_user_premium_type(
|
||||||
|
internal_, reinterpret_cast<EDiscordPremiumType*>(premiumType));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result UserManager::CurrentUserHasFlag(UserFlag flag, bool* hasFlag)
|
||||||
|
{
|
||||||
|
if (!hasFlag) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->current_user_has_flag(
|
||||||
|
internal_, static_cast<EDiscordUserFlag>(flag), reinterpret_cast<bool*>(hasFlag));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
31
libs/discordGameSDK/cpp/user_manager.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class UserManager final {
|
||||||
|
public:
|
||||||
|
~UserManager() = default;
|
||||||
|
|
||||||
|
Result GetCurrentUser(User* currentUser);
|
||||||
|
void GetUser(UserId userId, std::function<void(Result, User const&)> callback);
|
||||||
|
Result GetCurrentUserPremiumType(PremiumType* premiumType);
|
||||||
|
Result CurrentUserHasFlag(UserFlag flag, bool* hasFlag);
|
||||||
|
|
||||||
|
Event<> OnCurrentUserUpdate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
UserManager() = default;
|
||||||
|
UserManager(UserManager const& rhs) = delete;
|
||||||
|
UserManager& operator=(UserManager const& rhs) = delete;
|
||||||
|
UserManager(UserManager&& rhs) = delete;
|
||||||
|
UserManager& operator=(UserManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordUserManager* internal_;
|
||||||
|
static IDiscordUserEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
124
libs/discordGameSDK/cpp/voice_manager.cpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "voice_manager.h"
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class VoiceEvents final {
|
||||||
|
public:
|
||||||
|
static void OnSettingsUpdate(void* callbackData)
|
||||||
|
{
|
||||||
|
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||||
|
if (!core) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& module = core->VoiceManager();
|
||||||
|
module.OnSettingsUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IDiscordVoiceEvents VoiceManager::events_{
|
||||||
|
&VoiceEvents::OnSettingsUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
|
Result VoiceManager::GetInputMode(InputMode* inputMode)
|
||||||
|
{
|
||||||
|
if (!inputMode) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_input_mode(internal_, reinterpret_cast<DiscordInputMode*>(inputMode));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceManager::SetInputMode(InputMode inputMode, std::function<void(Result)> callback)
|
||||||
|
{
|
||||||
|
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb(
|
||||||
|
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||||
|
if (!cb || !(*cb)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(*cb)(static_cast<Result>(result));
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||||
|
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||||
|
internal_->set_input_mode(
|
||||||
|
internal_, *reinterpret_cast<DiscordInputMode const*>(&inputMode), cb.release(), wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::IsSelfMute(bool* mute)
|
||||||
|
{
|
||||||
|
if (!mute) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->is_self_mute(internal_, reinterpret_cast<bool*>(mute));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::SetSelfMute(bool mute)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_self_mute(internal_, (mute ? 1 : 0));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::IsSelfDeaf(bool* deaf)
|
||||||
|
{
|
||||||
|
if (!deaf) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->is_self_deaf(internal_, reinterpret_cast<bool*>(deaf));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::SetSelfDeaf(bool deaf)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_self_deaf(internal_, (deaf ? 1 : 0));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::IsLocalMute(Snowflake userId, bool* mute)
|
||||||
|
{
|
||||||
|
if (!mute) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = internal_->is_local_mute(internal_, userId, reinterpret_cast<bool*>(mute));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::SetLocalMute(Snowflake userId, bool mute)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_local_mute(internal_, userId, (mute ? 1 : 0));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::GetLocalVolume(Snowflake userId, std::uint8_t* volume)
|
||||||
|
{
|
||||||
|
if (!volume) {
|
||||||
|
return Result::InternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result =
|
||||||
|
internal_->get_local_volume(internal_, userId, reinterpret_cast<uint8_t*>(volume));
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VoiceManager::SetLocalVolume(Snowflake userId, std::uint8_t volume)
|
||||||
|
{
|
||||||
|
auto result = internal_->set_local_volume(internal_, userId, volume);
|
||||||
|
return static_cast<Result>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
37
libs/discordGameSDK/cpp/voice_manager.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace discord {
|
||||||
|
|
||||||
|
class VoiceManager final {
|
||||||
|
public:
|
||||||
|
~VoiceManager() = default;
|
||||||
|
|
||||||
|
Result GetInputMode(InputMode* inputMode);
|
||||||
|
void SetInputMode(InputMode inputMode, std::function<void(Result)> callback);
|
||||||
|
Result IsSelfMute(bool* mute);
|
||||||
|
Result SetSelfMute(bool mute);
|
||||||
|
Result IsSelfDeaf(bool* deaf);
|
||||||
|
Result SetSelfDeaf(bool deaf);
|
||||||
|
Result IsLocalMute(Snowflake userId, bool* mute);
|
||||||
|
Result SetLocalMute(Snowflake userId, bool mute);
|
||||||
|
Result GetLocalVolume(Snowflake userId, std::uint8_t* volume);
|
||||||
|
Result SetLocalVolume(Snowflake userId, std::uint8_t volume);
|
||||||
|
|
||||||
|
Event<> OnSettingsUpdate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Core;
|
||||||
|
|
||||||
|
VoiceManager() = default;
|
||||||
|
VoiceManager(VoiceManager const& rhs) = delete;
|
||||||
|
VoiceManager& operator=(VoiceManager const& rhs) = delete;
|
||||||
|
VoiceManager(VoiceManager&& rhs) = delete;
|
||||||
|
VoiceManager& operator=(VoiceManager&& rhs) = delete;
|
||||||
|
|
||||||
|
IDiscordVoiceManager* internal_;
|
||||||
|
static IDiscordVoiceEvents events_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord
|
||||||
12
libs/discordGameSDK/csharp/ActivityManager.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
public partial class ActivityManager
|
||||||
|
{
|
||||||
|
public void RegisterCommand()
|
||||||
|
{
|
||||||
|
RegisterCommand(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
libs/discordGameSDK/csharp/Constants.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
static class Constants
|
||||||
|
{
|
||||||
|
public const string DllName = "discord_game_sdk";
|
||||||
|
}
|
||||||
|
}
|
||||||
4199
libs/discordGameSDK/csharp/Core.cs
Normal file
53
libs/discordGameSDK/csharp/ImageManager.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||||
|
using UnityEngine;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
public partial struct ImageHandle
|
||||||
|
{
|
||||||
|
static public ImageHandle User(Int64 id)
|
||||||
|
{
|
||||||
|
return User(id, 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public ImageHandle User(Int64 id, UInt32 size)
|
||||||
|
{
|
||||||
|
return new ImageHandle
|
||||||
|
{
|
||||||
|
Type = ImageType.User,
|
||||||
|
Id = id,
|
||||||
|
Size = size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class ImageManager
|
||||||
|
{
|
||||||
|
public void Fetch(ImageHandle handle, FetchHandler callback)
|
||||||
|
{
|
||||||
|
Fetch(handle, false, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetData(ImageHandle handle)
|
||||||
|
{
|
||||||
|
var dimensions = GetDimensions(handle);
|
||||||
|
var data = new byte[dimensions.Width * dimensions.Height * 4];
|
||||||
|
GetData(handle, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||||
|
public Texture2D GetTexture(ImageHandle handle)
|
||||||
|
{
|
||||||
|
var dimensions = GetDimensions(handle);
|
||||||
|
var texture = new Texture2D((int)dimensions.Width, (int)dimensions.Height, TextureFormat.RGBA32, false, true);
|
||||||
|
texture.LoadRawTextureData(GetData(handle));
|
||||||
|
texture.Apply();
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
26
libs/discordGameSDK/csharp/LobbyManager.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
public partial class LobbyManager
|
||||||
|
{
|
||||||
|
public IEnumerable<User> GetMemberUsers(Int64 lobbyID)
|
||||||
|
{
|
||||||
|
var memberCount = MemberCount(lobbyID);
|
||||||
|
var members = new List<User>();
|
||||||
|
for (var i = 0; i < memberCount; i++)
|
||||||
|
{
|
||||||
|
members.Add(GetMemberUser(lobbyID, GetMemberUserId(lobbyID, i)));
|
||||||
|
}
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendLobbyMessage(Int64 lobbyID, string data, SendLobbyMessageHandler handler)
|
||||||
|
{
|
||||||
|
SendLobbyMessage(lobbyID, Encoding.UTF8.GetBytes(data), handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
libs/discordGameSDK/csharp/StorageManager.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
public partial class StorageManager
|
||||||
|
{
|
||||||
|
public IEnumerable<FileStat> Files()
|
||||||
|
{
|
||||||
|
var fileCount = Count();
|
||||||
|
var files = new List<FileStat>();
|
||||||
|
for (var i = 0; i < fileCount; i++)
|
||||||
|
{
|
||||||
|
files.Add(StatAt(i));
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
libs/discordGameSDK/csharp/StoreManager.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Discord
|
||||||
|
{
|
||||||
|
public partial class StoreManager
|
||||||
|
{
|
||||||
|
public IEnumerable<Entitlement> GetEntitlements()
|
||||||
|
{
|
||||||
|
var count = CountEntitlements();
|
||||||
|
var entitlements = new List<Entitlement>();
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
entitlements.Add(GetEntitlementAt(i));
|
||||||
|
}
|
||||||
|
return entitlements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Sku> GetSkus()
|
||||||
|
{
|
||||||
|
var count = CountSkus();
|
||||||
|
var skus = new List<Sku>();
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
skus.Add(GetSkuAt(i));
|
||||||
|
}
|
||||||
|
return skus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
155
libs/discordGameSDK/examples/c/main.c
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "discord_game_sdk.h"
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DISCORD_REQUIRE(x) assert(x == DiscordResult_Ok)
|
||||||
|
|
||||||
|
struct Application {
|
||||||
|
struct IDiscordCore* core;
|
||||||
|
struct IDiscordUserManager* users;
|
||||||
|
struct IDiscordAchievementManager* achievements;
|
||||||
|
struct IDiscordActivityManager* activities;
|
||||||
|
struct IDiscordRelationshipManager* relationships;
|
||||||
|
struct IDiscordApplicationManager* application;
|
||||||
|
struct IDiscordLobbyManager* lobbies;
|
||||||
|
DiscordUserId user_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
void UpdateActivityCallback(void* data, enum EDiscordResult result)
|
||||||
|
{
|
||||||
|
DISCORD_REQUIRE(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RelationshipPassFilter(void* data, struct DiscordRelationship* relationship)
|
||||||
|
{
|
||||||
|
return (relationship->type == DiscordRelationshipType_Friend);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RelationshipSnowflakeFilter(void* data, struct DiscordRelationship* relationship)
|
||||||
|
{
|
||||||
|
struct Application* app = (struct Application*)data;
|
||||||
|
|
||||||
|
return (relationship->type == DiscordRelationshipType_Friend &&
|
||||||
|
relationship->user.id < app->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnRelationshipsRefresh(void* data)
|
||||||
|
{
|
||||||
|
struct Application* app = (struct Application*)data;
|
||||||
|
struct IDiscordRelationshipManager* module = app->relationships;
|
||||||
|
|
||||||
|
module->filter(module, app, RelationshipPassFilter);
|
||||||
|
|
||||||
|
int32_t unfiltered_count = 0;
|
||||||
|
DISCORD_REQUIRE(module->count(module, &unfiltered_count));
|
||||||
|
|
||||||
|
module->filter(module, app, RelationshipSnowflakeFilter);
|
||||||
|
|
||||||
|
int32_t filtered_count = 0;
|
||||||
|
DISCORD_REQUIRE(module->count(module, &filtered_count));
|
||||||
|
|
||||||
|
printf("=== Cool Friends ===\n");
|
||||||
|
for (int32_t i = 0; i < filtered_count; i += 1) {
|
||||||
|
struct DiscordRelationship relationship;
|
||||||
|
DISCORD_REQUIRE(module->get_at(module, i, &relationship));
|
||||||
|
|
||||||
|
printf("%lld %s#%s\n",
|
||||||
|
relationship.user.id,
|
||||||
|
relationship.user.username,
|
||||||
|
relationship.user.discriminator);
|
||||||
|
}
|
||||||
|
printf("(%d friends less cool than you omitted)\n", unfiltered_count - filtered_count);
|
||||||
|
|
||||||
|
struct DiscordActivity activity;
|
||||||
|
memset(&activity, 0, sizeof(activity));
|
||||||
|
sprintf(activity.details, "Cooler than %d friends", unfiltered_count - filtered_count);
|
||||||
|
sprintf(activity.state, "%d friends total", unfiltered_count);
|
||||||
|
|
||||||
|
app->activities->update_activity(app->activities, &activity, app, UpdateActivityCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUserUpdated(void* data)
|
||||||
|
{
|
||||||
|
struct Application* app = (struct Application*)data;
|
||||||
|
struct DiscordUser user;
|
||||||
|
app->users->get_current_user(app->users, &user);
|
||||||
|
app->user_id = user.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnOAuth2Token(void* data, enum EDiscordResult result, struct DiscordOAuth2Token* token)
|
||||||
|
{
|
||||||
|
if (result == DiscordResult_Ok) {
|
||||||
|
printf("OAuth2 token: %s\n", token->access_token);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("GetOAuth2Token failed with %d\n", (int)result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnLobbyConnect(void* data, enum EDiscordResult result, struct DiscordLobby* lobby)
|
||||||
|
{
|
||||||
|
printf("LobbyConnect returned %d\n", (int)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
struct Application app;
|
||||||
|
memset(&app, 0, sizeof(app));
|
||||||
|
|
||||||
|
struct IDiscordUserEvents users_events;
|
||||||
|
memset(&users_events, 0, sizeof(users_events));
|
||||||
|
users_events.on_current_user_update = OnUserUpdated;
|
||||||
|
|
||||||
|
struct IDiscordActivityEvents activities_events;
|
||||||
|
memset(&activities_events, 0, sizeof(activities_events));
|
||||||
|
|
||||||
|
struct IDiscordRelationshipEvents relationships_events;
|
||||||
|
memset(&relationships_events, 0, sizeof(relationships_events));
|
||||||
|
relationships_events.on_refresh = OnRelationshipsRefresh;
|
||||||
|
|
||||||
|
struct DiscordCreateParams params;
|
||||||
|
DiscordCreateParamsSetDefault(¶ms);
|
||||||
|
params.client_id = 418559331265675294;
|
||||||
|
params.flags = DiscordCreateFlags_Default;
|
||||||
|
params.event_data = &app;
|
||||||
|
params.activity_events = &activities_events;
|
||||||
|
params.relationship_events = &relationships_events;
|
||||||
|
params.user_events = &users_events;
|
||||||
|
DISCORD_REQUIRE(DiscordCreate(DISCORD_VERSION, ¶ms, &app.core));
|
||||||
|
|
||||||
|
app.users = app.core->get_user_manager(app.core);
|
||||||
|
app.achievements = app.core->get_achievement_manager(app.core);
|
||||||
|
app.activities = app.core->get_activity_manager(app.core);
|
||||||
|
app.application = app.core->get_application_manager(app.core);
|
||||||
|
app.lobbies = app.core->get_lobby_manager(app.core);
|
||||||
|
|
||||||
|
app.lobbies->connect_lobby_with_activity_secret(
|
||||||
|
app.lobbies, "invalid_secret", &app, OnLobbyConnect);
|
||||||
|
|
||||||
|
app.application->get_oauth2_token(app.application, &app, OnOAuth2Token);
|
||||||
|
|
||||||
|
DiscordBranch branch;
|
||||||
|
app.application->get_current_branch(app.application, &branch);
|
||||||
|
printf("Current branch %s\n", branch);
|
||||||
|
|
||||||
|
app.relationships = app.core->get_relationship_manager(app.core);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
DISCORD_REQUIRE(app.core->run_callbacks(app.core));
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
Sleep(16);
|
||||||
|
#else
|
||||||
|
usleep(16 * 1000);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
297
libs/discordGameSDK/examples/cpp/main.cpp
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "discord.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct BitmapImageHeader {
|
||||||
|
uint32_t const structSize{sizeof(BitmapImageHeader)};
|
||||||
|
int32_t width{0};
|
||||||
|
int32_t height{0};
|
||||||
|
uint16_t const planes{1};
|
||||||
|
uint16_t const bpp{32};
|
||||||
|
uint32_t const pad0{0};
|
||||||
|
uint32_t const pad1{0};
|
||||||
|
uint32_t const hres{2835};
|
||||||
|
uint32_t const vres{2835};
|
||||||
|
uint32_t const pad4{0};
|
||||||
|
uint32_t const pad5{0};
|
||||||
|
|
||||||
|
BitmapImageHeader& operator=(BitmapImageHeader const&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BitmapFileHeader {
|
||||||
|
uint8_t const magic0{'B'};
|
||||||
|
uint8_t const magic1{'M'};
|
||||||
|
uint32_t size{0};
|
||||||
|
uint32_t const pad{0};
|
||||||
|
uint32_t const offset{sizeof(BitmapFileHeader) + sizeof(BitmapImageHeader)};
|
||||||
|
|
||||||
|
BitmapFileHeader& operator=(BitmapFileHeader const&) = delete;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct DiscordState {
|
||||||
|
discord::User currentUser;
|
||||||
|
|
||||||
|
std::unique_ptr<discord::Core> core;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
volatile bool interrupted{false};
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int, char**)
|
||||||
|
{
|
||||||
|
DiscordState state{};
|
||||||
|
|
||||||
|
discord::Core* core{};
|
||||||
|
auto result = discord::Core::Create(310270644849737729, DiscordCreateFlags_Default, &core);
|
||||||
|
state.core.reset(core);
|
||||||
|
if (!state.core) {
|
||||||
|
std::cout << "Failed to instantiate discord core! (err " << static_cast<int>(result)
|
||||||
|
<< ")\n";
|
||||||
|
std::exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.core->SetLogHook(
|
||||||
|
discord::LogLevel::Debug, [](discord::LogLevel level, const char* message) {
|
||||||
|
std::cerr << "Log(" << static_cast<uint32_t>(level) << "): " << message << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
core->UserManager().OnCurrentUserUpdate.Connect([&state]() {
|
||||||
|
state.core->UserManager().GetCurrentUser(&state.currentUser);
|
||||||
|
|
||||||
|
std::cout << "Current user updated: " << state.currentUser.GetUsername() << "#"
|
||||||
|
<< state.currentUser.GetDiscriminator() << "\n";
|
||||||
|
|
||||||
|
state.core->UserManager().GetUser(130050050968518656,
|
||||||
|
[](discord::Result result, discord::User const& user) {
|
||||||
|
if (result == discord::Result::Ok) {
|
||||||
|
std::cout << "Get " << user.GetUsername() << "\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Failed to get David!\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
discord::ImageHandle handle{};
|
||||||
|
handle.SetId(state.currentUser.GetId());
|
||||||
|
handle.SetType(discord::ImageType::User);
|
||||||
|
handle.SetSize(256);
|
||||||
|
|
||||||
|
state.core->ImageManager().Fetch(
|
||||||
|
handle, true, [&state](discord::Result res, discord::ImageHandle handle) {
|
||||||
|
if (res == discord::Result::Ok) {
|
||||||
|
discord::ImageDimensions dims{};
|
||||||
|
state.core->ImageManager().GetDimensions(handle, &dims);
|
||||||
|
std::cout << "Fetched " << dims.GetWidth() << "x" << dims.GetHeight()
|
||||||
|
<< " avatar!\n";
|
||||||
|
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.reserve(dims.GetWidth() * dims.GetHeight() * 4);
|
||||||
|
uint8_t* d = data.data();
|
||||||
|
state.core->ImageManager().GetData(handle, d, data.size());
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
auto fileSize =
|
||||||
|
data.size() + sizeof(BitmapImageHeader) + sizeof(BitmapFileHeader);
|
||||||
|
|
||||||
|
BitmapImageHeader imageHeader;
|
||||||
|
imageHeader.width = static_cast<int32_t>(dims.GetWidth());
|
||||||
|
imageHeader.height = static_cast<int32_t>(dims.GetHeight());
|
||||||
|
|
||||||
|
BitmapFileHeader fileHeader;
|
||||||
|
fileHeader.size = static_cast<uint32_t>(fileSize);
|
||||||
|
|
||||||
|
FILE* fp = fopen("avatar.bmp", "wb");
|
||||||
|
fwrite(&fileHeader, sizeof(BitmapFileHeader), 1, fp);
|
||||||
|
fwrite(&imageHeader, sizeof(BitmapImageHeader), 1, fp);
|
||||||
|
|
||||||
|
for (auto y = 0u; y < dims.GetHeight(); ++y) {
|
||||||
|
auto pixels = reinterpret_cast<uint32_t const*>(data.data());
|
||||||
|
auto invY = dims.GetHeight() - y - 1;
|
||||||
|
fwrite(
|
||||||
|
&pixels[invY * dims.GetWidth()], sizeof(uint32_t) * dims.GetWidth(), 1, fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
fflush(fp);
|
||||||
|
fclose(fp);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Failed fetching avatar. (err " << static_cast<int>(res) << ")\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->ActivityManager().RegisterCommand("run/command/foo/bar/baz/here.exe");
|
||||||
|
state.core->ActivityManager().RegisterSteam(123123321);
|
||||||
|
|
||||||
|
state.core->ActivityManager().OnActivityJoin.Connect(
|
||||||
|
[](const char* secret) { std::cout << "Join " << secret << "\n"; });
|
||||||
|
state.core->ActivityManager().OnActivitySpectate.Connect(
|
||||||
|
[](const char* secret) { std::cout << "Spectate " << secret << "\n"; });
|
||||||
|
state.core->ActivityManager().OnActivityJoinRequest.Connect([](discord::User const& user) {
|
||||||
|
std::cout << "Join Request " << user.GetUsername() << "\n";
|
||||||
|
});
|
||||||
|
state.core->ActivityManager().OnActivityInvite.Connect(
|
||||||
|
[](discord::ActivityActionType, discord::User const& user, discord::Activity const&) {
|
||||||
|
std::cout << "Invite " << user.GetUsername() << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnLobbyUpdate.Connect(
|
||||||
|
[](std::int64_t lobbyId) { std::cout << "Lobby update " << lobbyId << "\n"; });
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnLobbyDelete.Connect(
|
||||||
|
[](std::int64_t lobbyId, std::uint32_t reason) {
|
||||||
|
std::cout << "Lobby delete " << lobbyId << " (reason: " << reason << ")\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnMemberConnect.Connect(
|
||||||
|
[](std::int64_t lobbyId, std::int64_t userId) {
|
||||||
|
std::cout << "Lobby member connect " << lobbyId << " userId " << userId << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnMemberUpdate.Connect(
|
||||||
|
[](std::int64_t lobbyId, std::int64_t userId) {
|
||||||
|
std::cout << "Lobby member update " << lobbyId << " userId " << userId << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnMemberDisconnect.Connect(
|
||||||
|
[](std::int64_t lobbyId, std::int64_t userId) {
|
||||||
|
std::cout << "Lobby member disconnect " << lobbyId << " userId " << userId << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnLobbyMessage.Connect([&](std::int64_t lobbyId,
|
||||||
|
std::int64_t userId,
|
||||||
|
std::uint8_t* payload,
|
||||||
|
std::uint32_t payloadLength) {
|
||||||
|
std::vector<uint8_t> buffer{};
|
||||||
|
buffer.resize(payloadLength);
|
||||||
|
memcpy(buffer.data(), payload, payloadLength);
|
||||||
|
std::cout << "Lobby message " << lobbyId << " from " << userId << " of length "
|
||||||
|
<< payloadLength << " bytes.\n";
|
||||||
|
|
||||||
|
char fourtyNinetySix[4096];
|
||||||
|
state.core->LobbyManager().GetLobbyMetadataValue(lobbyId, "foo", fourtyNinetySix);
|
||||||
|
|
||||||
|
std::cout << "Metadata for key foo is " << fourtyNinetySix << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->LobbyManager().OnSpeaking.Connect(
|
||||||
|
[&](std::int64_t, std::int64_t userId, bool speaking) {
|
||||||
|
std::cout << "User " << userId << " is " << (speaking ? "" : "NOT ") << "speaking.\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
discord::Activity activity{};
|
||||||
|
activity.SetDetails("Fruit Tarts");
|
||||||
|
activity.SetState("Pop Snacks");
|
||||||
|
activity.GetAssets().SetSmallImage("the");
|
||||||
|
activity.GetAssets().SetSmallText("i mage");
|
||||||
|
activity.GetAssets().SetLargeImage("the");
|
||||||
|
activity.GetAssets().SetLargeText("u mage");
|
||||||
|
activity.SetType(discord::ActivityType::Playing);
|
||||||
|
state.core->ActivityManager().UpdateActivity(activity, [](discord::Result result) {
|
||||||
|
std::cout << ((result == discord::Result::Ok) ? "Succeeded" : "Failed")
|
||||||
|
<< " updating activity!\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
discord::LobbyTransaction lobby{};
|
||||||
|
state.core->LobbyManager().GetLobbyCreateTransaction(&lobby);
|
||||||
|
lobby.SetCapacity(2);
|
||||||
|
lobby.SetMetadata("foo", "bar");
|
||||||
|
lobby.SetMetadata("baz", "bat");
|
||||||
|
lobby.SetType(discord::LobbyType::Public);
|
||||||
|
state.core->LobbyManager().CreateLobby(
|
||||||
|
lobby, [&state](discord::Result result, discord::Lobby const& lobby) {
|
||||||
|
if (result == discord::Result::Ok) {
|
||||||
|
std::cout << "Created lobby with secret " << lobby.GetSecret() << "\n";
|
||||||
|
std::array<uint8_t, 234> data{};
|
||||||
|
state.core->LobbyManager().SendLobbyMessage(
|
||||||
|
lobby.GetId(),
|
||||||
|
reinterpret_cast<uint8_t*>(data.data()),
|
||||||
|
data.size(),
|
||||||
|
[](discord::Result result) {
|
||||||
|
std::cout << "Sent message. Result: " << static_cast<int>(result) << "\n";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Failed creating lobby. (err " << static_cast<int>(result) << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
discord::LobbySearchQuery query{};
|
||||||
|
state.core->LobbyManager().GetSearchQuery(&query);
|
||||||
|
query.Limit(1);
|
||||||
|
state.core->LobbyManager().Search(query, [&state](discord::Result result) {
|
||||||
|
if (result == discord::Result::Ok) {
|
||||||
|
std::int32_t lobbyCount{};
|
||||||
|
state.core->LobbyManager().LobbyCount(&lobbyCount);
|
||||||
|
std::cout << "Lobby search succeeded with " << lobbyCount << " lobbies.\n";
|
||||||
|
for (auto i = 0; i < lobbyCount; ++i) {
|
||||||
|
discord::LobbyId lobbyId{};
|
||||||
|
state.core->LobbyManager().GetLobbyId(i, &lobbyId);
|
||||||
|
std::cout << " " << lobbyId << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Lobby search failed. (err " << static_cast<int>(result) << ")\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->RelationshipManager().OnRefresh.Connect([&]() {
|
||||||
|
std::cout << "Relationships refreshed!\n";
|
||||||
|
|
||||||
|
state.core->RelationshipManager().Filter(
|
||||||
|
[](discord::Relationship const& relationship) -> bool {
|
||||||
|
return relationship.GetType() == discord::RelationshipType::Friend;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::int32_t friendCount{0};
|
||||||
|
state.core->RelationshipManager().Count(&friendCount);
|
||||||
|
|
||||||
|
state.core->RelationshipManager().Filter(
|
||||||
|
[&](discord::Relationship const& relationship) -> bool {
|
||||||
|
return relationship.GetType() == discord::RelationshipType::Friend &&
|
||||||
|
relationship.GetUser().GetId() < state.currentUser.GetId();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::int32_t filteredCount{0};
|
||||||
|
state.core->RelationshipManager().Count(&filteredCount);
|
||||||
|
|
||||||
|
discord::Relationship relationship{};
|
||||||
|
for (auto i = 0; i < filteredCount; ++i) {
|
||||||
|
state.core->RelationshipManager().GetAt(i, &relationship);
|
||||||
|
std::cout << relationship.GetUser().GetId() << " "
|
||||||
|
<< relationship.GetUser().GetUsername() << "#"
|
||||||
|
<< relationship.GetUser().GetDiscriminator() << "\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state.core->RelationshipManager().OnRelationshipUpdate.Connect(
|
||||||
|
[](discord::Relationship const& relationship) {
|
||||||
|
std::cout << "Relationship with " << relationship.GetUser().GetUsername()
|
||||||
|
<< " updated!\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
std::signal(SIGINT, [](int) { interrupted = true; });
|
||||||
|
|
||||||
|
do {
|
||||||
|
state.core->RunCallbacks();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||||
|
} while (!interrupted);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
412
libs/discordGameSDK/examples/csharp/Program.cs
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
// Request user's avatar data. Sizes can be powers of 2 between 16 and 2048
|
||||||
|
static void FetchAvatar(Discord.ImageManager imageManager, Int64 userID)
|
||||||
|
{
|
||||||
|
imageManager.Fetch(Discord.ImageHandle.User(userID), (result, handle) =>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
if (result == Discord.Result.Ok)
|
||||||
|
{
|
||||||
|
// You can also use GetTexture2D within Unity.
|
||||||
|
// These return raw RGBA.
|
||||||
|
var data = imageManager.GetData(handle);
|
||||||
|
Console.WriteLine("image updated {0} {1}", handle.Id, data.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("image error {0}", handle.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user's activity for your game.
|
||||||
|
// Party and secrets are vital.
|
||||||
|
// Read https://discordapp.com/developers/docs/rich-presence/how-to for more details.
|
||||||
|
static void UpdateActivity(Discord.Discord discord, Discord.Lobby lobby)
|
||||||
|
{
|
||||||
|
var activityManager = discord.GetActivityManager();
|
||||||
|
var lobbyManager = discord.GetLobbyManager();
|
||||||
|
|
||||||
|
var activity = new Discord.Activity
|
||||||
|
{
|
||||||
|
State = "olleh",
|
||||||
|
Details = "foo details",
|
||||||
|
Timestamps =
|
||||||
|
{
|
||||||
|
Start = 5,
|
||||||
|
End = 6,
|
||||||
|
},
|
||||||
|
Assets =
|
||||||
|
{
|
||||||
|
LargeImage = "foo largeImageKey",
|
||||||
|
LargeText = "foo largeImageText",
|
||||||
|
SmallImage = "foo smallImageKey",
|
||||||
|
SmallText = "foo smallImageText",
|
||||||
|
},
|
||||||
|
Party = {
|
||||||
|
Id = lobby.Id.ToString(),
|
||||||
|
Size = {
|
||||||
|
CurrentSize = lobbyManager.MemberCount(lobby.Id),
|
||||||
|
MaxSize = (int)lobby.Capacity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Secrets = {
|
||||||
|
Join = lobbyManager.GetLobbyActivitySecret(lobby.Id),
|
||||||
|
},
|
||||||
|
Instance = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
activityManager.UpdateActivity(activity, result =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Update Activity {0}", result);
|
||||||
|
|
||||||
|
// Send an invite to another user for this activity.
|
||||||
|
// Receiver should see an invite in their DM.
|
||||||
|
// Use a relationship user's ID for this.
|
||||||
|
// activityManager
|
||||||
|
// .SendInvite(
|
||||||
|
// 364843917537050624,
|
||||||
|
// Discord.ActivityActionType.Join,
|
||||||
|
// "",
|
||||||
|
// inviteResult =>
|
||||||
|
// {
|
||||||
|
// Console.WriteLine("Invite {0}", inviteResult);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// Use your client ID from Discord's developer site.
|
||||||
|
var clientID = Environment.GetEnvironmentVariable("DISCORD_CLIENT_ID");
|
||||||
|
if (clientID == null)
|
||||||
|
{
|
||||||
|
clientID = "418559331265675294";
|
||||||
|
}
|
||||||
|
var discord = new Discord.Discord(Int64.Parse(clientID), (UInt64)Discord.CreateFlags.Default);
|
||||||
|
discord.SetLogHook(Discord.LogLevel.Debug, (level, message) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Log[{0}] {1}", level, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
var applicationManager = discord.GetApplicationManager();
|
||||||
|
// Get the current locale. This can be used to determine what text or audio the user wants.
|
||||||
|
Console.WriteLine("Current Locale: {0}", applicationManager.GetCurrentLocale());
|
||||||
|
// Get the current branch. For example alpha or beta.
|
||||||
|
Console.WriteLine("Current Branch: {0}", applicationManager.GetCurrentBranch());
|
||||||
|
// If you want to verify information from your game's server then you can
|
||||||
|
// grab the access token and send it to your server.
|
||||||
|
//
|
||||||
|
// This automatically looks for an environment variable passed by the Discord client,
|
||||||
|
// if it does not exist the Discord client will focus itself for manual authorization.
|
||||||
|
//
|
||||||
|
// By-default the SDK grants the identify and rpc scopes.
|
||||||
|
// Read more at https://discordapp.com/developers/docs/topics/oauth2
|
||||||
|
// applicationManager.GetOAuth2Token((Discord.Result result, ref Discord.OAuth2Token oauth2Token) =>
|
||||||
|
// {
|
||||||
|
// Console.WriteLine("Access Token {0}", oauth2Token.AccessToken);
|
||||||
|
// });
|
||||||
|
|
||||||
|
var activityManager = discord.GetActivityManager();
|
||||||
|
var lobbyManager = discord.GetLobbyManager();
|
||||||
|
// Received when someone accepts a request to join or invite.
|
||||||
|
// Use secrets to receive back the information needed to add the user to the group/party/match
|
||||||
|
activityManager.OnActivityJoin += secret =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("OnJoin {0}", secret);
|
||||||
|
lobbyManager.ConnectLobbyWithActivitySecret(secret, (Discord.Result result, ref Discord.Lobby lobby) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Connected to lobby: {0}", lobby.Id);
|
||||||
|
lobbyManager.ConnectNetwork(lobby.Id);
|
||||||
|
lobbyManager.OpenNetworkChannel(lobby.Id, 0, true);
|
||||||
|
foreach (var user in lobbyManager.GetMemberUsers(lobby.Id))
|
||||||
|
{
|
||||||
|
lobbyManager.SendNetworkMessage(lobby.Id, user.Id, 0,
|
||||||
|
Encoding.UTF8.GetBytes(String.Format("Hello, {0}!", user.Username)));
|
||||||
|
}
|
||||||
|
UpdateActivity(discord, lobby);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Received when someone accepts a request to spectate
|
||||||
|
activityManager.OnActivitySpectate += secret =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("OnSpectate {0}", secret);
|
||||||
|
};
|
||||||
|
// A join request has been received. Render the request on the UI.
|
||||||
|
activityManager.OnActivityJoinRequest += (ref Discord.User user) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("OnJoinRequest {0} {1}", user.Id, user.Username);
|
||||||
|
};
|
||||||
|
// An invite has been received. Consider rendering the user / activity on the UI.
|
||||||
|
activityManager.OnActivityInvite += (Discord.ActivityActionType Type, ref Discord.User user, ref Discord.Activity activity2) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("OnInvite {0} {1} {2}", Type, user.Username, activity2.Name);
|
||||||
|
// activityManager.AcceptInvite(user.Id, result =>
|
||||||
|
// {
|
||||||
|
// Console.WriteLine("AcceptInvite {0}", result);
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
// This is used to register the game in the registry such that Discord can find it.
|
||||||
|
// This is only needed by games acquired from other platforms, like Steam.
|
||||||
|
// activityManager.RegisterCommand();
|
||||||
|
|
||||||
|
var imageManager = discord.GetImageManager();
|
||||||
|
|
||||||
|
var userManager = discord.GetUserManager();
|
||||||
|
// The auth manager fires events as information about the current user changes.
|
||||||
|
// This event will fire once on init.
|
||||||
|
//
|
||||||
|
// GetCurrentUser will error until this fires once.
|
||||||
|
userManager.OnCurrentUserUpdate += () =>
|
||||||
|
{
|
||||||
|
var currentUser = userManager.GetCurrentUser();
|
||||||
|
Console.WriteLine(currentUser.Username);
|
||||||
|
Console.WriteLine(currentUser.Id);
|
||||||
|
};
|
||||||
|
// If you store Discord user ids in a central place like a leaderboard and want to render them.
|
||||||
|
// The users manager can be used to fetch arbitrary Discord users. This only provides basic
|
||||||
|
// information and does not automatically update like relationships.
|
||||||
|
userManager.GetUser(450795363658366976, (Discord.Result result, ref Discord.User user) =>
|
||||||
|
{
|
||||||
|
if (result == Discord.Result.Ok)
|
||||||
|
{
|
||||||
|
Console.WriteLine("user fetched: {0}", user.Username);
|
||||||
|
|
||||||
|
// Request users's avatar data.
|
||||||
|
// This can only be done after a user is successfully fetched.
|
||||||
|
FetchAvatar(imageManager, user.Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("user fetch error: {0}", result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var relationshipManager = discord.GetRelationshipManager();
|
||||||
|
// It is important to assign this handle right away to get the initial relationships refresh.
|
||||||
|
// This callback will only be fired when the whole list is initially loaded or was reset
|
||||||
|
relationshipManager.OnRefresh += () =>
|
||||||
|
{
|
||||||
|
// Filter a user's relationship list to be just friends
|
||||||
|
relationshipManager.Filter((ref Discord.Relationship relationship) => { return relationship.Type == Discord.RelationshipType.Friend; });
|
||||||
|
// Loop over all friends a user has.
|
||||||
|
Console.WriteLine("relationships updated: {0}", relationshipManager.Count());
|
||||||
|
for (var i = 0; i < Math.Min(relationshipManager.Count(), 10); i++)
|
||||||
|
{
|
||||||
|
// Get an individual relationship from the list
|
||||||
|
var r = relationshipManager.GetAt((uint)i);
|
||||||
|
Console.WriteLine("relationships: {0} {1} {2} {3}", r.Type, r.User.Username, r.Presence.Status, r.Presence.Activity.Name);
|
||||||
|
|
||||||
|
// Request relationship's avatar data.
|
||||||
|
FetchAvatar(imageManager, r.User.Id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// All following relationship updates are delivered individually.
|
||||||
|
// These are fired when a user gets a new friend, removes a friend, or a relationship's presence changes.
|
||||||
|
relationshipManager.OnRelationshipUpdate += (ref Discord.Relationship r) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("relationship updated: {0} {1} {2} {3}", r.Type, r.User.Username, r.Presence.Status, r.Presence.Activity.Name);
|
||||||
|
};
|
||||||
|
|
||||||
|
lobbyManager.OnLobbyMessage += (lobbyID, userID, data) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("lobby message: {0} {1}", lobbyID, Encoding.UTF8.GetString(data));
|
||||||
|
};
|
||||||
|
lobbyManager.OnNetworkMessage += (lobbyId, userId, channelId, data) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("network message: {0} {1} {2} {3}", lobbyId, userId, channelId, Encoding.UTF8.GetString(data));
|
||||||
|
};
|
||||||
|
lobbyManager.OnSpeaking += (lobbyID, userID, speaking) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("lobby speaking: {0} {1} {2}", lobbyID, userID, speaking);
|
||||||
|
};
|
||||||
|
// Create a lobby.
|
||||||
|
var transaction = lobbyManager.GetLobbyCreateTransaction();
|
||||||
|
transaction.SetCapacity(6);
|
||||||
|
transaction.SetType(Discord.LobbyType.Public);
|
||||||
|
transaction.SetMetadata("a", "123");
|
||||||
|
transaction.SetMetadata("a", "456");
|
||||||
|
transaction.SetMetadata("b", "111");
|
||||||
|
transaction.SetMetadata("c", "222");
|
||||||
|
|
||||||
|
lobbyManager.CreateLobby(transaction, (Discord.Result result, ref Discord.Lobby lobby) =>
|
||||||
|
{
|
||||||
|
if (result != Discord.Result.Ok)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the lobby's configuration.
|
||||||
|
Console.WriteLine("lobby {0} with capacity {1} and secret {2}", lobby.Id, lobby.Capacity, lobby.Secret);
|
||||||
|
|
||||||
|
// Check lobby metadata.
|
||||||
|
foreach (var key in new string[] { "a", "b", "c" })
|
||||||
|
{
|
||||||
|
Console.WriteLine("{0} = {1}", key, lobbyManager.GetLobbyMetadataValue(lobby.Id, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print all the members of the lobby.
|
||||||
|
foreach (var user in lobbyManager.GetMemberUsers(lobby.Id))
|
||||||
|
{
|
||||||
|
Console.WriteLine("lobby member: {0}", user.Username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send everyone a message.
|
||||||
|
lobbyManager.SendLobbyMessage(lobby.Id, "Hello from C#!", (_) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("sent message");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update lobby.
|
||||||
|
var lobbyTransaction = lobbyManager.GetLobbyUpdateTransaction(lobby.Id);
|
||||||
|
lobbyTransaction.SetMetadata("d", "e");
|
||||||
|
lobbyTransaction.SetCapacity(16);
|
||||||
|
lobbyManager.UpdateLobby(lobby.Id, lobbyTransaction, (_) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("lobby has been updated");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update a member.
|
||||||
|
var lobbyID = lobby.Id;
|
||||||
|
var userID = lobby.OwnerId;
|
||||||
|
var memberTransaction = lobbyManager.GetMemberUpdateTransaction(lobbyID, userID);
|
||||||
|
memberTransaction.SetMetadata("hello", "there");
|
||||||
|
lobbyManager.UpdateMember(lobbyID, userID, memberTransaction, (_) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("lobby member has been updated: {0}", lobbyManager.GetMemberMetadataValue(lobbyID, userID, "hello"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Search lobbies.
|
||||||
|
var query = lobbyManager.GetSearchQuery();
|
||||||
|
// Filter by a metadata value.
|
||||||
|
query.Filter("metadata.a", Discord.LobbySearchComparison.GreaterThan, Discord.LobbySearchCast.Number, "455");
|
||||||
|
query.Sort("metadata.a", Discord.LobbySearchCast.Number, "0");
|
||||||
|
// Only return 1 result max.
|
||||||
|
query.Limit(1);
|
||||||
|
lobbyManager.Search(query, (_) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("search returned {0} lobbies", lobbyManager.LobbyCount());
|
||||||
|
if (lobbyManager.LobbyCount() == 1)
|
||||||
|
{
|
||||||
|
Console.WriteLine("first lobby secret: {0}", lobbyManager.GetLobby(lobbyManager.GetLobbyId(0)).Secret);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to voice chat.
|
||||||
|
lobbyManager.ConnectVoice(lobby.Id, (_) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Connected to voice chat!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup networking.
|
||||||
|
lobbyManager.ConnectNetwork(lobby.Id);
|
||||||
|
lobbyManager.OpenNetworkChannel(lobby.Id, 0, true);
|
||||||
|
|
||||||
|
// Update activity.
|
||||||
|
UpdateActivity(discord, lobby);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
var overlayManager = discord.GetOverlayManager();
|
||||||
|
overlayManager.OnOverlayLocked += locked =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Overlay Locked: {0}", locked);
|
||||||
|
};
|
||||||
|
overlayManager.SetLocked(false);
|
||||||
|
*/
|
||||||
|
|
||||||
|
var storageManager = discord.GetStorageManager();
|
||||||
|
var contents = new byte[20000];
|
||||||
|
var random = new Random();
|
||||||
|
random.NextBytes(contents);
|
||||||
|
Console.WriteLine("storage path: {0}", storageManager.GetPath());
|
||||||
|
storageManager.WriteAsync("foo", contents, res =>
|
||||||
|
{
|
||||||
|
var files = storageManager.Files();
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
Console.WriteLine("file: {0} size: {1} last_modified: {2}", file.Filename, file.Size, file.LastModified);
|
||||||
|
}
|
||||||
|
storageManager.ReadAsyncPartial("foo", 400, 50, (result, data) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("partial contents of foo match {0}", Enumerable.SequenceEqual(data, new ArraySegment<byte>(contents, 400, 50)));
|
||||||
|
});
|
||||||
|
storageManager.ReadAsync("foo", (result, data) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("length of contents {0} data {1}", contents.Length, data.Length);
|
||||||
|
Console.WriteLine("contents of foo match {0}", Enumerable.SequenceEqual(data, contents));
|
||||||
|
Console.WriteLine("foo exists? {0}", storageManager.Exists("foo"));
|
||||||
|
storageManager.Delete("foo");
|
||||||
|
Console.WriteLine("post-delete foo exists? {0}", storageManager.Exists("foo"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var storeManager = discord.GetStoreManager();
|
||||||
|
storeManager.OnEntitlementCreate += (ref Discord.Entitlement entitlement) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Entitlement Create1: {0}", entitlement.Id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start a purchase flow.
|
||||||
|
// storeManager.StartPurchase(487507201519255552, result =>
|
||||||
|
// {
|
||||||
|
// if (result == Discord.Result.Ok)
|
||||||
|
// {
|
||||||
|
// Console.WriteLine("Purchase Complete");
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// Console.WriteLine("Purchase Canceled");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Get all entitlements.
|
||||||
|
storeManager.FetchEntitlements(result =>
|
||||||
|
{
|
||||||
|
if (result == Discord.Result.Ok)
|
||||||
|
{
|
||||||
|
foreach (var entitlement in storeManager.GetEntitlements())
|
||||||
|
{
|
||||||
|
Console.WriteLine("entitlement: {0} - {1} {2}", entitlement.Id, entitlement.Type, entitlement.SkuId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all SKUs.
|
||||||
|
storeManager.FetchSkus(result =>
|
||||||
|
{
|
||||||
|
if (result == Discord.Result.Ok)
|
||||||
|
{
|
||||||
|
foreach (var sku in storeManager.GetSkus())
|
||||||
|
{
|
||||||
|
Console.WriteLine("sku: {0} - {1} {2}", sku.Name, sku.Price.Amount, sku.Price.Currency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pump the event look to ensure all callbacks continue to get fired.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
discord.RunCallbacks();
|
||||||
|
lobbyManager.FlushNetwork();
|
||||||
|
Thread.Sleep(1000 / 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
discord.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
libs/discordGameSDK/lib/x86/discord_game_sdk.dll
Normal file
BIN
libs/discordGameSDK/lib/x86/discord_game_sdk.dll.lib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.bundle
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll.lib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dylib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.so
Normal file
@@ -24,6 +24,59 @@ if osname == "Linux" then
|
|||||||
elseif osname == "OS X" then
|
elseif osname == "OS X" then
|
||||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
|
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
|
||||||
elseif osname == "Windows" then
|
elseif osname == "Windows" then
|
||||||
|
-- I would strongly advise never touching this. It was not trivial to get correct. -nightmareci
|
||||||
|
|
||||||
|
ffi.cdef[[
|
||||||
|
typedef uint32_t DWORD;
|
||||||
|
typedef char CHAR;
|
||||||
|
typedef CHAR *LPSTR;
|
||||||
|
typedef const CHAR *LPCSTR;
|
||||||
|
typedef wchar_t WCHAR;
|
||||||
|
typedef WCHAR *LPWSTR;
|
||||||
|
typedef LPWSTR PWSTR;
|
||||||
|
typedef const WCHAR *LPCWSTR;
|
||||||
|
|
||||||
|
static const DWORD CP_UTF8 = 65001;
|
||||||
|
int32_t MultiByteToWideChar(
|
||||||
|
DWORD CodePage,
|
||||||
|
DWORD dwFlags,
|
||||||
|
LPCSTR lpMultiByteStr,
|
||||||
|
int32_t cbMultiByte,
|
||||||
|
LPWSTR lpWideCharStr,
|
||||||
|
int32_t cchWideChar
|
||||||
|
);
|
||||||
|
|
||||||
|
int32_t WideCharToMultiByte(
|
||||||
|
DWORD CodePage,
|
||||||
|
DWORD dwFlags,
|
||||||
|
LPCWSTR lpWideCharStr,
|
||||||
|
int32_t cchWideChar,
|
||||||
|
LPSTR lpMultiByteStr,
|
||||||
|
int32_t cbMultiByte,
|
||||||
|
void* lpDefaultChar,
|
||||||
|
void* lpUsedDefaultChar
|
||||||
|
);
|
||||||
|
|
||||||
|
DWORD GetShortPathNameW(
|
||||||
|
LPCWSTR lpszLongPath,
|
||||||
|
LPWSTR lpszShortPath,
|
||||||
|
DWORD cchBuffer
|
||||||
|
);
|
||||||
|
]]
|
||||||
|
|
||||||
|
local originalWideSize = ffi.C.MultiByteToWideChar(ffi.C.CP_UTF8, 0, source, -1, nil, 0)
|
||||||
|
local originalWide = ffi.new('WCHAR[?]', originalWideSize)
|
||||||
|
ffi.C.MultiByteToWideChar(ffi.C.CP_UTF8, 0, source, -1, originalWide, originalWideSize)
|
||||||
|
|
||||||
|
local sourceSize = ffi.C.GetShortPathNameW(originalWide, nil, 0)
|
||||||
|
local sourceWide = ffi.new('WCHAR[?]', sourceSize)
|
||||||
|
ffi.C.GetShortPathNameW(originalWide, sourceWide, sourceSize)
|
||||||
|
|
||||||
|
local sourceChar = ffi.new('char[?]', sourceSize)
|
||||||
|
ffi.C.WideCharToMultiByte(ffi.C.CP_UTF8, 0, sourceWide, sourceSize, sourceChar, sourceSize, nil, nil)
|
||||||
|
|
||||||
|
source = ffi.string(sourceChar)
|
||||||
|
|
||||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dll")
|
discordRPClib = ffi.load(source.."/libs/discord-rpc.dll")
|
||||||
else
|
else
|
||||||
-- Else it crashes later on
|
-- Else it crashes later on
|
||||||
|
|||||||
43
load/bgm.lua
@@ -6,34 +6,45 @@ bgm = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local current_bgm = nil
|
local current_bgm = nil
|
||||||
|
local pitch = 1
|
||||||
local bgm_locked = false
|
local bgm_locked = false
|
||||||
|
|
||||||
function switchBGM(sound, subsound)
|
function switchBGM(sound, subsound)
|
||||||
if bgm_locked then return end
|
if bgm_locked then
|
||||||
|
return
|
||||||
|
end
|
||||||
if current_bgm ~= nil then
|
if current_bgm ~= nil then
|
||||||
current_bgm:stop()
|
current_bgm:stop()
|
||||||
end
|
end
|
||||||
if subsound ~= nil then
|
if config.bgm_volume <= 0 then
|
||||||
current_bgm = bgm[sound][subsound]
|
current_bgm = nil
|
||||||
resetBGMFadeout()
|
|
||||||
elseif sound ~= nil then
|
elseif sound ~= nil then
|
||||||
current_bgm = bgm[sound]
|
if subsound ~= nil then
|
||||||
resetBGMFadeout()
|
current_bgm = bgm[sound][subsound]
|
||||||
|
else
|
||||||
|
current_bgm = bgm[sound]
|
||||||
|
end
|
||||||
else
|
else
|
||||||
current_bgm = nil
|
current_bgm = nil
|
||||||
end
|
end
|
||||||
|
if current_bgm ~= nil then
|
||||||
|
resetBGMFadeout()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function switchBGMLoop(sound, subsound)
|
function switchBGMLoop(sound, subsound)
|
||||||
if bgm_locked then return end
|
|
||||||
switchBGM(sound, subsound)
|
switchBGM(sound, subsound)
|
||||||
current_bgm:setLooping(true)
|
if current_bgm then current_bgm:setLooping(true) end
|
||||||
end
|
end
|
||||||
|
|
||||||
function lockBGM()
|
function lockBGM()
|
||||||
bgm_locked = true
|
bgm_locked = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function unlockBGM()
|
||||||
|
bgm_locked = false
|
||||||
|
end
|
||||||
|
|
||||||
local fading_bgm = false
|
local fading_bgm = false
|
||||||
local fadeout_time = 0
|
local fadeout_time = 0
|
||||||
local total_fadeout_time = 0
|
local total_fadeout_time = 0
|
||||||
@@ -49,17 +60,19 @@ end
|
|||||||
function resetBGMFadeout(time)
|
function resetBGMFadeout(time)
|
||||||
current_bgm:setVolume(config.bgm_volume)
|
current_bgm:setVolume(config.bgm_volume)
|
||||||
fading_bgm = false
|
fading_bgm = false
|
||||||
current_bgm:play()
|
resumeBGM()
|
||||||
end
|
end
|
||||||
|
|
||||||
function processBGMFadeout(dt)
|
function processBGMFadeout(dt)
|
||||||
if fading_bgm then
|
if current_bgm and fading_bgm then
|
||||||
fadeout_time = fadeout_time - dt
|
fadeout_time = fadeout_time - dt
|
||||||
if fadeout_time < 0 then
|
if fadeout_time < 0 then
|
||||||
fadeout_time = 0
|
fadeout_time = 0
|
||||||
fading_bgm = false
|
fading_bgm = false
|
||||||
end
|
end
|
||||||
current_bgm:setVolume(fadeout_time * config.bgm_volume / total_fadeout_time)
|
current_bgm:setVolume(
|
||||||
|
fadeout_time * config.bgm_volume / total_fadeout_time
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -72,5 +85,13 @@ end
|
|||||||
function resumeBGM()
|
function resumeBGM()
|
||||||
if current_bgm ~= nil then
|
if current_bgm ~= nil then
|
||||||
current_bgm:play()
|
current_bgm:play()
|
||||||
|
current_bgm:setPitch(pitch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function pitchBGM(new_pitch)
|
||||||
|
pitch = new_pitch
|
||||||
|
if current_bgm ~= nil then
|
||||||
|
current_bgm:setPitch(pitch)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
29
load/cpml.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- adapted from the init.lua CPML comes with
|
||||||
|
|
||||||
|
cpml = {
|
||||||
|
_LICENSE = "CPML is distributed under the terms of the MIT license. See libs/cpml/LICENSE.md.",
|
||||||
|
_URL = "https://github.com/excessive/cpml",
|
||||||
|
_VERSION = "1.2.9",
|
||||||
|
_DESCRIPTION = "Cirno's Perfect Math Library: Just about everything you need for 3D games. Hopefully."
|
||||||
|
}
|
||||||
|
|
||||||
|
local files = {
|
||||||
|
"bvh",
|
||||||
|
"color",
|
||||||
|
"constants",
|
||||||
|
"intersect",
|
||||||
|
"mat4",
|
||||||
|
"mesh",
|
||||||
|
"octree",
|
||||||
|
"quat",
|
||||||
|
"simplex",
|
||||||
|
"utils",
|
||||||
|
"vec2",
|
||||||
|
"vec3",
|
||||||
|
"bound2",
|
||||||
|
"bound3"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file in ipairs(files) do
|
||||||
|
cpml[file] = require('libs.cpml.' .. file)
|
||||||
|
end
|
||||||
@@ -26,8 +26,18 @@ font_3x5_4 = love.graphics.newImageFont(
|
|||||||
-4
|
-4
|
||||||
)
|
)
|
||||||
|
|
||||||
font_8x11 = love.graphics.newImageFont(
|
-- this would be font_8x11 with the other one as 8x11_2
|
||||||
"res/fonts/8x11_medium.png",
|
-- but that would break compatibility :(
|
||||||
"0123456789:.",
|
font_8x11_small = love.graphics.newImageFont(
|
||||||
|
"res/fonts/8x11.png",
|
||||||
|
" 0123456789:;.,ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ..
|
||||||
|
"?!/\\^@$%<=>()*-+[]_&",
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
font_8x11 = love.graphics.newImageFont(
|
||||||
|
"res/fonts/8x11_medium.png",
|
||||||
|
" 0123456789:;.,ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ..
|
||||||
|
"?!/\\^@$%<=>()*-+[]_&",
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|||||||
13
load/gamesdk.lua
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
print("Loading Discord GameSDK...")
|
||||||
|
DiscordGameSDK = {
|
||||||
|
loaded = false
|
||||||
|
}
|
||||||
|
local success, libDiscordGameSDK = pcall(require, "libs.discordGameSDK")
|
||||||
|
if success then
|
||||||
|
DiscordGameSDK.loaded = true
|
||||||
|
|
||||||
|
print("Discord GameSDK successfully loaded")
|
||||||
|
else
|
||||||
|
print("Discord GameSDK failed to load!")
|
||||||
|
end
|
||||||
@@ -1,30 +1,116 @@
|
|||||||
backgrounds = {
|
named_backgrounds = {
|
||||||
[0] = love.graphics.newImage("res/backgrounds/0.png"),
|
"title", "title_no_icon", "title_night",
|
||||||
love.graphics.newImage("res/backgrounds/100.png"),
|
"snow", "options_input", "options_game"
|
||||||
love.graphics.newImage("res/backgrounds/200.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/300.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/400.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/500.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/600.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/700.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/800.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/900.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1000.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1100.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1200.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1300.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1400.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1500.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1600.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1700.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1800.png"),
|
|
||||||
love.graphics.newImage("res/backgrounds/1900.png"),
|
|
||||||
title = love.graphics.newImage("res/backgrounds/title.png"),
|
|
||||||
snow = love.graphics.newImage("res/backgrounds/snow.png"),
|
|
||||||
input_config = love.graphics.newImage("res/backgrounds/options-input.png"),
|
|
||||||
game_config = love.graphics.newImage("res/backgrounds/options-game.png"),
|
|
||||||
}
|
}
|
||||||
|
current_playing_bgs = {}
|
||||||
|
extended_bgs = {}
|
||||||
|
image_formats = {".jpg", ".png"}
|
||||||
|
bgpath = "res/backgrounds/"
|
||||||
|
dir = love.filesystem.getDirectoryItems(bgpath)
|
||||||
|
|
||||||
|
backgrounds = {}
|
||||||
|
|
||||||
|
local function loadExtendedBgs()
|
||||||
|
extended_bgs = require("res.backgrounds.extend_section_bg")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- error handling for if there is no extend_section_bg
|
||||||
|
if pcall(loadExtendedBgs) then end
|
||||||
|
|
||||||
|
-- helper method to populate backgrounds
|
||||||
|
local function createBackgroundIfExists(name, file_name)
|
||||||
|
local format_index = 1
|
||||||
|
|
||||||
|
-- see if background is an extension of another background
|
||||||
|
if extended_bgs[file_name] ~= nil then
|
||||||
|
copy_bg = extended_bgs[file_name]
|
||||||
|
copy_bg = copy_bg / 100
|
||||||
|
backgrounds[name] = backgrounds[copy_bg]
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- try creating image backgrounds
|
||||||
|
while format_index <= #image_formats do
|
||||||
|
for num, existing_file in pairs(dir) do
|
||||||
|
if existing_file == (file_name..image_formats[format_index]) then
|
||||||
|
local tempBgPath = bgpath .. file_name .. image_formats[format_index]
|
||||||
|
backgrounds[name] = love.graphics.newImage(tempBgPath)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
format_index = format_index + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- try creating video background
|
||||||
|
if love.filesystem.getInfo(bgpath .. file_name .. ".ogv") then
|
||||||
|
for num, existing_file in pairs(dir) do
|
||||||
|
if existing_file == (file_name..".ogv") then
|
||||||
|
local tempBgPath = bgpath .. file_name .. ".ogv"
|
||||||
|
backgrounds[name] = love.graphics.newVideo(
|
||||||
|
tempBgPath, {["audio"] = false}
|
||||||
|
)
|
||||||
|
-- you can set audio to true, but the video will not loop
|
||||||
|
-- properly if audio extends beyond video frames
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stopOtherBgs(bg)
|
||||||
|
if #current_playing_bgs == 0 and bg:typeOf("Video") then
|
||||||
|
current_playing_bgs[#current_playing_bgs+1] = bg
|
||||||
|
end
|
||||||
|
|
||||||
|
if #current_playing_bgs >= 1 then
|
||||||
|
while current_playing_bgs[1] ~= bg and #current_playing_bgs >= 1 do
|
||||||
|
current_playing_bgs[1]:pause()
|
||||||
|
current_playing_bgs[1]:rewind()
|
||||||
|
table.remove(current_playing_bgs, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function fetchBackgroundAndLoop(id)
|
||||||
|
bg = backgrounds[id]
|
||||||
|
|
||||||
|
if bg:typeOf("Video") and not bg:isPlaying() then
|
||||||
|
bg:rewind()
|
||||||
|
bg:play()
|
||||||
|
end
|
||||||
|
|
||||||
|
stopOtherBgs(bg)
|
||||||
|
|
||||||
|
return bg
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create section backgrounds
|
||||||
|
local section = 0
|
||||||
|
while (createBackgroundIfExists(section, section*100)) do
|
||||||
|
section = section + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create named backgrounds
|
||||||
|
local nbgIndex = 1
|
||||||
|
while nbgIndex <= #named_backgrounds do
|
||||||
|
createBackgroundIfExists(
|
||||||
|
named_backgrounds[nbgIndex],
|
||||||
|
string.gsub(named_backgrounds[nbgIndex], "_", "-")
|
||||||
|
)
|
||||||
|
nbgIndex = nbgIndex + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- in order, the colors are:
|
||||||
|
-- red, orange, yellow, green, cyan, blue
|
||||||
|
-- magenta (or purple), white, black
|
||||||
|
-- the next three don't have colors tied to them
|
||||||
|
-- F is used for lock flash
|
||||||
|
-- A is a garbage block
|
||||||
|
-- X is an invisible "block"
|
||||||
|
-- don't use these for piece colors when making a ruleset
|
||||||
|
-- all the others are fine to use
|
||||||
blocks = {
|
blocks = {
|
||||||
["2tie"] = {
|
["2tie"] = {
|
||||||
R = love.graphics.newImage("res/img/s1.png"),
|
R = love.graphics.newImage("res/img/s1.png"),
|
||||||
@@ -34,6 +120,8 @@ blocks = {
|
|||||||
C = love.graphics.newImage("res/img/s2.png"),
|
C = love.graphics.newImage("res/img/s2.png"),
|
||||||
B = love.graphics.newImage("res/img/s4.png"),
|
B = love.graphics.newImage("res/img/s4.png"),
|
||||||
M = love.graphics.newImage("res/img/s5.png"),
|
M = love.graphics.newImage("res/img/s5.png"),
|
||||||
|
W = love.graphics.newImage("res/img/s9.png"),
|
||||||
|
D = love.graphics.newImage("res/img/s8.png"),
|
||||||
F = love.graphics.newImage("res/img/s9.png"),
|
F = love.graphics.newImage("res/img/s9.png"),
|
||||||
A = love.graphics.newImage("res/img/s8.png"),
|
A = love.graphics.newImage("res/img/s8.png"),
|
||||||
X = love.graphics.newImage("res/img/s9.png"),
|
X = love.graphics.newImage("res/img/s9.png"),
|
||||||
@@ -46,6 +134,8 @@ blocks = {
|
|||||||
C = love.graphics.newImage("res/img/bone.png"),
|
C = love.graphics.newImage("res/img/bone.png"),
|
||||||
B = love.graphics.newImage("res/img/bone.png"),
|
B = love.graphics.newImage("res/img/bone.png"),
|
||||||
M = love.graphics.newImage("res/img/bone.png"),
|
M = love.graphics.newImage("res/img/bone.png"),
|
||||||
|
W = love.graphics.newImage("res/img/bone.png"),
|
||||||
|
D = love.graphics.newImage("res/img/bone.png"),
|
||||||
F = love.graphics.newImage("res/img/bone.png"),
|
F = love.graphics.newImage("res/img/bone.png"),
|
||||||
A = love.graphics.newImage("res/img/bone.png"),
|
A = love.graphics.newImage("res/img/bone.png"),
|
||||||
X = love.graphics.newImage("res/img/bone.png"),
|
X = love.graphics.newImage("res/img/bone.png"),
|
||||||
@@ -58,13 +148,16 @@ blocks = {
|
|||||||
C = love.graphics.newImage("res/img/gem2.png"),
|
C = love.graphics.newImage("res/img/gem2.png"),
|
||||||
B = love.graphics.newImage("res/img/gem4.png"),
|
B = love.graphics.newImage("res/img/gem4.png"),
|
||||||
M = love.graphics.newImage("res/img/gem5.png"),
|
M = love.graphics.newImage("res/img/gem5.png"),
|
||||||
|
W = love.graphics.newImage("res/img/gem9.png"),
|
||||||
|
D = love.graphics.newImage("res/img/gem9.png"),
|
||||||
F = love.graphics.newImage("res/img/gem9.png"),
|
F = love.graphics.newImage("res/img/gem9.png"),
|
||||||
A = love.graphics.newImage("res/img/gem9.png"),
|
A = love.graphics.newImage("res/img/gem9.png"),
|
||||||
X = love.graphics.newImage("res/img/gem9.png"),
|
X = love.graphics.newImage("res/img/gem9.png"),
|
||||||
},
|
},
|
||||||
["square"] = {
|
["square"] = {
|
||||||
F = love.graphics.newImage("res/img/squares.png"),
|
W = love.graphics.newImage("res/img/squares.png"),
|
||||||
Y = love.graphics.newImage("res/img/squareg.png"),
|
Y = love.graphics.newImage("res/img/squareg.png"),
|
||||||
|
F = love.graphics.newImage("res/img/squares.png"),
|
||||||
X = love.graphics.newImage("res/img/squares.png"),
|
X = love.graphics.newImage("res/img/squares.png"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +180,7 @@ ColourSchemes = {
|
|||||||
Z = "R",
|
Z = "R",
|
||||||
O = "Y",
|
O = "Y",
|
||||||
T = "M",
|
T = "M",
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, blockset in pairs(blocks) do
|
for name, blockset in pairs(blocks) do
|
||||||
@@ -102,5 +195,19 @@ misc_graphics = {
|
|||||||
go = love.graphics.newImage("res/img/go.png"),
|
go = love.graphics.newImage("res/img/go.png"),
|
||||||
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
select_mode = love.graphics.newImage("res/img/select_mode.png"),
|
||||||
strike = love.graphics.newImage("res/img/strike.png"),
|
strike = love.graphics.newImage("res/img/strike.png"),
|
||||||
santa = love.graphics.newImage("res/img/santa.png")
|
santa = love.graphics.newImage("res/img/santa.png"),
|
||||||
|
icon = love.graphics.newImage("res/img/cambridge_transparent.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- utility function to allow any size background to be used
|
||||||
|
-- this will stretch the background to 4:3 aspect ratio
|
||||||
|
function drawBackground(id)
|
||||||
|
local bg_object = fetchBackgroundAndLoop(id)
|
||||||
|
local width = bg_object:getWidth()
|
||||||
|
local height = bg_object:getHeight()
|
||||||
|
love.graphics.draw(
|
||||||
|
bg_object,
|
||||||
|
0, 0, 0,
|
||||||
|
640 / width, 480 / height
|
||||||
|
)
|
||||||
|
end
|
||||||
@@ -6,7 +6,11 @@ function loadSave()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function loadFromFile(filename)
|
function loadFromFile(filename)
|
||||||
local save_data, len = binser.readFile(filename)
|
local file_data = love.filesystem.read(filename)
|
||||||
|
if file_data == nil then
|
||||||
|
return {} -- new object
|
||||||
|
end
|
||||||
|
local save_data = binser.deserialize(file_data)
|
||||||
if save_data == nil then
|
if save_data == nil then
|
||||||
return {} -- new object
|
return {} -- new object
|
||||||
end
|
end
|
||||||
@@ -40,9 +44,13 @@ function initConfig()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function saveConfig()
|
function saveConfig()
|
||||||
binser.writeFile('config.sav', config)
|
love.filesystem.write(
|
||||||
|
'config.sav', binser.serialize(config)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function saveHighscores()
|
function saveHighscores()
|
||||||
binser.writeFile('highscores.sav', highscores)
|
love.filesystem.write(
|
||||||
|
'highscores.sav', binser.serialize(highscores)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
127
load/sounds.lua
@@ -1,59 +1,98 @@
|
|||||||
sounds = {
|
sound_paths = {
|
||||||
blocks = {
|
blocks = {
|
||||||
I = love.audio.newSource("res/se/piece_i.wav", "static"),
|
I = "res/se/piece_i.wav",
|
||||||
J = love.audio.newSource("res/se/piece_j.wav", "static"),
|
J = "res/se/piece_j.wav",
|
||||||
L = love.audio.newSource("res/se/piece_l.wav", "static"),
|
L = "res/se/piece_l.wav",
|
||||||
O = love.audio.newSource("res/se/piece_o.wav", "static"),
|
O = "res/se/piece_o.wav",
|
||||||
S = love.audio.newSource("res/se/piece_s.wav", "static"),
|
S = "res/se/piece_s.wav",
|
||||||
T = love.audio.newSource("res/se/piece_t.wav", "static"),
|
T = "res/se/piece_t.wav",
|
||||||
Z = love.audio.newSource("res/se/piece_z.wav", "static")
|
Z = "res/se/piece_z.wav"
|
||||||
},
|
},
|
||||||
move = love.audio.newSource("res/se/move.wav", "static"),
|
move = "res/se/move.wav",
|
||||||
bottom = love.audio.newSource("res/se/bottom.wav", "static"),
|
rotate = "res/se/rotate.wav",
|
||||||
cursor = love.audio.newSource("res/se/cursor.wav", "static"),
|
kick = "res/se/kick.wav",
|
||||||
cursor_lr = love.audio.newSource("res/se/cursor_lr.wav", "static"),
|
bottom = "res/se/bottom.wav",
|
||||||
main_decide = love.audio.newSource("res/se/main_decide.wav", "static"),
|
cursor = "res/se/cursor.wav",
|
||||||
mode_decide = love.audio.newSource("res/se/mode_decide.wav", "static"),
|
cursor_lr = "res/se/cursor_lr.wav",
|
||||||
lock = love.audio.newSource("res/se/lock.wav", "static"),
|
main_decide = "res/se/main_decide.wav",
|
||||||
hold = love.audio.newSource("res/se/hold.wav", "static"),
|
mode_decide = "res/se/mode_decide.wav",
|
||||||
erase = love.audio.newSource("res/se/erase.wav", "static"),
|
lock = "res/se/lock.wav",
|
||||||
fall = love.audio.newSource("res/se/fall.wav", "static"),
|
hold = "res/se/hold.wav",
|
||||||
ready = love.audio.newSource("res/se/ready.wav", "static"),
|
erase = {
|
||||||
go = love.audio.newSource("res/se/go.wav", "static"),
|
single = "res/se/single.wav",
|
||||||
irs = love.audio.newSource("res/se/irs.wav", "static"),
|
double = "res/se/double.wav",
|
||||||
ihs = love.audio.newSource("res/se/ihs.wav", "static"),
|
triple = "res/se/triple.wav",
|
||||||
|
quad = "res/se/quad.wav"
|
||||||
|
},
|
||||||
|
fall = "res/se/fall.wav",
|
||||||
|
ready = "res/se/ready.wav",
|
||||||
|
go = "res/se/go.wav",
|
||||||
|
irs = "res/se/irs.wav",
|
||||||
|
ihs = "res/se/ihs.wav",
|
||||||
-- a secret sound!
|
-- a secret sound!
|
||||||
welcome = love.audio.newSource("res/se/welcomeToCambridge.wav", "static"),
|
welcome = "res/se/welcomeToCambridge.wav",
|
||||||
}
|
}
|
||||||
|
|
||||||
function playSE(sound, subsound)
|
sounds = {}
|
||||||
if subsound == nil then
|
-- Replace each sound effect string with its love audiosource counterpart, but only if it exists. This lets the game handle missing SFX.
|
||||||
sounds[sound]:setVolume(config.sfx_volume)
|
for k,v in pairs(sound_paths) do
|
||||||
if sounds[sound]:isPlaying() then
|
if(type(v) == "table") then
|
||||||
sounds[sound]:stop()
|
-- list of subsounds
|
||||||
|
for k2,v2 in pairs(v) do
|
||||||
|
if(love.filesystem.getInfo(sound_paths[k][k2])) then
|
||||||
|
-- this file exists
|
||||||
|
sounds[k] = sounds[k] or {}
|
||||||
|
sounds[k][k2] = love.audio.newSource(sound_paths[k][k2], "static")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
sounds[sound]:play()
|
|
||||||
else
|
else
|
||||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
if(love.filesystem.getInfo(sound_paths[k])) then
|
||||||
if sounds[sound][subsound]:isPlaying() then
|
-- this file exists
|
||||||
sounds[sound][subsound]:stop()
|
sounds[k] = love.audio.newSource(sound_paths[k], "static")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function playSE(sound, subsound)
|
||||||
|
if sound ~= nil then
|
||||||
|
if sounds[sound] then
|
||||||
|
if subsound ~= nil then
|
||||||
|
if sounds[sound][subsound] then
|
||||||
|
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||||
|
if sounds[sound][subsound]:isPlaying() then
|
||||||
|
sounds[sound][subsound]:stop()
|
||||||
|
end
|
||||||
|
sounds[sound][subsound]:play()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
sounds[sound]:setVolume(config.sfx_volume)
|
||||||
|
if sounds[sound]:isPlaying() then
|
||||||
|
sounds[sound]:stop()
|
||||||
|
end
|
||||||
|
sounds[sound]:play()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
sounds[sound][subsound]:play()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function playSEOnce(sound, subsound)
|
function playSEOnce(sound, subsound)
|
||||||
if subsound == nil then
|
if sound ~= nil then
|
||||||
sounds[sound]:setVolume(config.sfx_volume)
|
if sounds[sound] then
|
||||||
if sounds[sound]:isPlaying() then
|
if subsound ~= nil then
|
||||||
return
|
if sounds[sound][subsound] then
|
||||||
|
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||||
|
if sounds[sound][subsound]:isPlaying() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
sounds[sound][subsound]:play()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
sounds[sound]:setVolume(config.sfx_volume)
|
||||||
|
if sounds[sound]:isPlaying() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
sounds[sound]:play()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
sounds[sound]:play()
|
|
||||||
else
|
|
||||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
|
||||||
if sounds[sound][subsound]:isPlaying() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
sounds[sound][subsound]:play()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1 +1 @@
|
|||||||
version = "v0.3-beta5.1"
|
version = "v0.3.4"
|
||||||
184
main.lua
@@ -1,6 +1,6 @@
|
|||||||
function love.load()
|
function love.load()
|
||||||
math.randomseed(os.time())
|
|
||||||
highscores = {}
|
highscores = {}
|
||||||
|
love.graphics.setDefaultFilter("linear", "nearest")
|
||||||
require "load.rpc"
|
require "load.rpc"
|
||||||
require "load.graphics"
|
require "load.graphics"
|
||||||
require "load.fonts"
|
require "load.fonts"
|
||||||
@@ -8,8 +8,10 @@ function love.load()
|
|||||||
require "load.bgm"
|
require "load.bgm"
|
||||||
require "load.save"
|
require "load.save"
|
||||||
require "load.bigint"
|
require "load.bigint"
|
||||||
|
require 'load.cpml'
|
||||||
require "load.version"
|
require "load.version"
|
||||||
loadSave()
|
loadSave()
|
||||||
|
require "funcs"
|
||||||
require "scene"
|
require "scene"
|
||||||
|
|
||||||
--config["side_next"] = false
|
--config["side_next"] = false
|
||||||
@@ -19,11 +21,17 @@ function love.load()
|
|||||||
|
|
||||||
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
||||||
|
|
||||||
-- used for screenshots
|
-- used for screenshots
|
||||||
GLOBAL_CANVAS = love.graphics.newCanvas()
|
GLOBAL_CANVAS = love.graphics.newCanvas()
|
||||||
|
|
||||||
|
-- aliasing to prevent people using math.random by accident
|
||||||
|
math.random = love.math.random
|
||||||
|
math.randomseed = love.math.setRandomSeed
|
||||||
|
math.randomseed(os.time())
|
||||||
|
|
||||||
-- init config
|
-- init config
|
||||||
initConfig()
|
initConfig()
|
||||||
|
config.depth_3d = 100 -- TODO add a setting for this to the menu
|
||||||
|
|
||||||
love.window.setFullscreen(config["fullscreen"])
|
love.window.setFullscreen(config["fullscreen"])
|
||||||
if config.secret then playSE("welcome") end
|
if config.secret then playSE("welcome") end
|
||||||
@@ -33,17 +41,19 @@ function love.load()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function initModules()
|
function initModules()
|
||||||
|
-- replays are not loaded here, but they are cleared
|
||||||
|
replays = {}
|
||||||
game_modes = {}
|
game_modes = {}
|
||||||
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
|
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
|
||||||
for i=1,#mode_list do
|
for i=1,#mode_list do
|
||||||
if(mode_list[i] ~= "gamemode.lua" and mode_list[i] ~= "unrefactored_modes") then
|
if(mode_list[i] ~= "gamemode.lua" and string.sub(mode_list[i], -4) == ".lua") then
|
||||||
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
|
game_modes[#game_modes+1] = require ("tetris.modes."..string.sub(mode_list[i],1,-5))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rulesets = {}
|
rulesets = {}
|
||||||
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
|
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
|
||||||
for i=1,#rule_list do
|
for i=1,#rule_list do
|
||||||
if(rule_list[i] ~= "ruleset.lua" and rule_list[i] ~= "unrefactored_rulesets") then
|
if(rule_list[i] ~= "ruleset.lua" and string.sub(rule_list[i], -4) == ".lua") then
|
||||||
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
|
rulesets[#rulesets+1] = require ("tetris.rulesets."..string.sub(rule_list[i],1,-5))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -55,52 +65,13 @@ function initModules()
|
|||||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local TARGET_FPS = 60
|
|
||||||
local SAMPLE_SIZE = 60
|
|
||||||
|
|
||||||
local rolling_samples = {}
|
|
||||||
local rolling_total = 0
|
|
||||||
local average_n = 0
|
|
||||||
local frame = 0
|
|
||||||
|
|
||||||
function getSmoothedDt(dt)
|
|
||||||
rolling_total = rolling_total + dt
|
|
||||||
frame = frame + 1
|
|
||||||
if frame > SAMPLE_SIZE then frame = frame - SAMPLE_SIZE end
|
|
||||||
if average_n == SAMPLE_SIZE then
|
|
||||||
rolling_total = rolling_total - rolling_samples[frame]
|
|
||||||
else
|
|
||||||
average_n = average_n + 1
|
|
||||||
end
|
|
||||||
rolling_samples[frame] = dt
|
|
||||||
return rolling_total / average_n
|
|
||||||
end
|
|
||||||
|
|
||||||
local update_time = 0.52
|
|
||||||
|
|
||||||
function love.update(dt)
|
|
||||||
processBGMFadeout(dt)
|
|
||||||
local old_update_time = update_time
|
|
||||||
update_time = update_time + getSmoothedDt(dt) * TARGET_FPS
|
|
||||||
updates = 0
|
|
||||||
while (update_time >= 1.02) do
|
|
||||||
scene:update()
|
|
||||||
updates = updates + 1
|
|
||||||
update_time = update_time - 1
|
|
||||||
end
|
|
||||||
if math.abs(update_time - old_update_time) < 0.02 then
|
|
||||||
update_time = old_update_time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.draw()
|
function love.draw()
|
||||||
love.graphics.setCanvas(GLOBAL_CANVAS)
|
love.graphics.setCanvas{GLOBAL_CANVAS, depth = config.depth_3d > 0}
|
||||||
love.graphics.clear()
|
love.graphics.clear(0, 0, 0, 1, false, config.depth_3d > 0)
|
||||||
|
|
||||||
love.graphics.push()
|
love.graphics.push()
|
||||||
|
|
||||||
-- get offset matrix
|
-- get offset matrix
|
||||||
love.graphics.setDefaultFilter("linear", "nearest")
|
|
||||||
local width = love.graphics.getWidth()
|
local width = love.graphics.getWidth()
|
||||||
local height = love.graphics.getHeight()
|
local height = love.graphics.getHeight()
|
||||||
local scale_factor = math.min(width / 640, height / 480)
|
local scale_factor = math.min(width / 640, height / 480)
|
||||||
@@ -111,39 +82,50 @@ function love.draw()
|
|||||||
love.graphics.scale(scale_factor)
|
love.graphics.scale(scale_factor)
|
||||||
|
|
||||||
scene:render()
|
scene:render()
|
||||||
|
|
||||||
|
if config.gamesettings.display_gamemode == 1 or scene.title == "Title" then
|
||||||
|
love.graphics.setFont(font_3x5_2)
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
love.graphics.printf(
|
||||||
|
string.format("%.2f", 1.0 / love.timer.getAverageDelta()) ..
|
||||||
|
"fps - " .. version, 0, 460, 635, "right"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
|
|
||||||
love.graphics.setCanvas()
|
love.graphics.setCanvas()
|
||||||
love.graphics.setColor(1,1,1,1)
|
love.graphics.setColor(1,1,1,1)
|
||||||
love.graphics.draw(GLOBAL_CANVAS)
|
love.graphics.draw(GLOBAL_CANVAS)
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.keypressed(key, scancode)
|
function love.keypressed(key, scancode)
|
||||||
-- global hotkeys
|
-- global hotkeys
|
||||||
if scancode == "f4" then
|
if scancode == "f11" then
|
||||||
config["fullscreen"] = not config["fullscreen"]
|
config["fullscreen"] = not config["fullscreen"]
|
||||||
saveConfig()
|
saveConfig()
|
||||||
love.window.setFullscreen(config["fullscreen"])
|
love.window.setFullscreen(config["fullscreen"])
|
||||||
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
||||||
scene = InputConfigScene()
|
scene = InputConfigScene()
|
||||||
switchBGM(nil)
|
switchBGM(nil)
|
||||||
|
loadSave()
|
||||||
-- secret sound playing :eyes:
|
-- secret sound playing :eyes:
|
||||||
elseif scancode == "f8" and scene.title == "Title" then
|
elseif scancode == "f8" and scene.title == "Title" then
|
||||||
config.secret = not config.secret
|
config.secret = not config.secret
|
||||||
saveConfig()
|
saveConfig()
|
||||||
scene.restart_message = true
|
scene.restart_message = true
|
||||||
if config.secret then playSE("mode_decide")
|
if config.secret then playSE("mode_decide")
|
||||||
else playSE("erase") end
|
else playSE("erase", "single") end
|
||||||
-- f12 is reserved for saving screenshots
|
-- f12 is reserved for saving screenshots
|
||||||
elseif scancode == "f12" then
|
elseif scancode == "f12" then
|
||||||
local ss_name = os.date("ss/%Y-%m-%d_%H-%M-%S.png")
|
local ss_name = os.date("ss/%Y-%m-%d_%H-%M-%S.png")
|
||||||
local info = love.filesystem.getInfo("ss")
|
local info = love.filesystem.getInfo("ss", "directory")
|
||||||
if not info or info.type ~= "directory" then
|
if not info then
|
||||||
love.filesystem.remove("ss")
|
love.filesystem.remove("ss")
|
||||||
love.filesystem.createDirectory("ss")
|
love.filesystem.createDirectory("ss")
|
||||||
end
|
end
|
||||||
print("Saving screenshot as "..ss_name)
|
print("Saving screenshot as "..love.filesystem.getSaveDirectory().."/"..ss_name)
|
||||||
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
|
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
|
||||||
-- function keys are reserved
|
-- function keys are reserved
|
||||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||||
return
|
return
|
||||||
@@ -296,15 +278,83 @@ function love.joystickhat(joystick, hat, direction)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.focus(f)
|
function love.wheelmoved(x, y)
|
||||||
if f and (scene.title ~= "Game" or not scene.paused) then
|
scene:onInputPress({input=nil, type="wheel", x=x, y=y})
|
||||||
resumeBGM()
|
|
||||||
else
|
|
||||||
pauseBGM()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function love.resize(w, h)
|
function love.resize(w, h)
|
||||||
GLOBAL_CANVAS:release()
|
GLOBAL_CANVAS:release()
|
||||||
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
|
GLOBAL_CANVAS = love.graphics.newCanvas(w, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- higher values of TARGET_FPS will make the game run "faster"
|
||||||
|
-- since the game is mostly designed for 60 FPS
|
||||||
|
local TARGET_FPS = 60
|
||||||
|
local FRAME_DURATION = 1.0 / TARGET_FPS
|
||||||
|
|
||||||
|
-- custom run function; optimizes game by syncing draw/update calls
|
||||||
|
function love.run()
|
||||||
|
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
|
||||||
|
|
||||||
|
if love.timer then love.timer.step() end
|
||||||
|
|
||||||
|
local dt = 0
|
||||||
|
|
||||||
|
local last_time = love.timer.getTime()
|
||||||
|
local time_accumulator = 0.0
|
||||||
|
return function()
|
||||||
|
if love.event then
|
||||||
|
love.event.pump()
|
||||||
|
for name, a,b,c,d,e,f in love.event.poll() do
|
||||||
|
if name == "quit" then
|
||||||
|
if not love.quit or not love.quit() then
|
||||||
|
return a or 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
love.handlers[name](a,b,c,d,e,f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if love.timer then
|
||||||
|
processBGMFadeout(love.timer.step())
|
||||||
|
end
|
||||||
|
|
||||||
|
if scene and scene.update and love.timer then
|
||||||
|
scene:update()
|
||||||
|
|
||||||
|
if time_accumulator < FRAME_DURATION then
|
||||||
|
if love.graphics and love.graphics.isActive() and love.draw then
|
||||||
|
love.graphics.origin()
|
||||||
|
love.graphics.clear(love.graphics.getBackgroundColor())
|
||||||
|
love.draw()
|
||||||
|
love.graphics.present()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- request 1ms delays first but stop short of overshooting, then do "0ms" delays without overshooting (0ms requests generally do a delay of some nonzero amount of time, but maybe less than 1ms)
|
||||||
|
for milliseconds=0.001,0.000,-0.001 do
|
||||||
|
local max_delay = 0.0
|
||||||
|
while max_delay < FRAME_DURATION do
|
||||||
|
local delay_start_time = love.timer.getTime()
|
||||||
|
if delay_start_time - last_time < FRAME_DURATION - max_delay then
|
||||||
|
love.timer.sleep(milliseconds)
|
||||||
|
local last_delay = love.timer.getTime() - delay_start_time
|
||||||
|
if last_delay > max_delay then
|
||||||
|
max_delay = last_delay
|
||||||
|
end
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
while love.timer.getTime() - last_time < FRAME_DURATION do
|
||||||
|
-- busy loop, do nothing here until delay is finished; delays above stop short of finishing, so this part can finish it off precisely
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local finish_delay_time = love.timer.getTime()
|
||||||
|
local real_frame_duration = finish_delay_time - last_time
|
||||||
|
time_accumulator = time_accumulator + real_frame_duration - FRAME_DURATION
|
||||||
|
last_time = finish_delay_time
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
zip -r cambridge.love libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
zip -r cambridge.love libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
./package
|
#!/bin/sh
|
||||||
|
|
||||||
|
./package-love.sh
|
||||||
mkdir dist
|
mkdir dist
|
||||||
mkdir dist/windows
|
mkdir dist/windows
|
||||||
mkdir dist/win32
|
mkdir dist/win32
|
||||||
cp cambridge.love dist/
|
mkdir dist/other
|
||||||
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
|
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
|
||||||
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
|
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
|
||||||
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
|
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
|
||||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
||||||
|
cp cambridge.love dist/other/
|
||||||
|
zip dist/cambridge-other.zip cambridge.love libs/discord-rpc.* SOURCES.md LICENSE.md
|
||||||
10
release.bat
@@ -5,17 +5,23 @@ mkdir dist\windows
|
|||||||
mkdir dist\windows\libs
|
mkdir dist\windows\libs
|
||||||
mkdir dist\win32
|
mkdir dist\win32
|
||||||
mkdir dist\win32\libs
|
mkdir dist\win32\libs
|
||||||
|
mkdir dist\other
|
||||||
|
mkdir dist\other\libs
|
||||||
|
|
||||||
copy /b dist\windows\love.exe+cambridge.love dist\windows\cambridge.exe
|
copy /b dist\windows\love.exe+cambridge.love dist\windows\cambridge.exe
|
||||||
copy /b dist\win32\love.exe+cambridge.love dist\win32\cambridge.exe
|
copy /b dist\win32\love.exe+cambridge.love dist\win32\cambridge.exe
|
||||||
|
copy /b cambridge.love dist\other\cambridge.love
|
||||||
|
|
||||||
copy libs\discord-rpc.dll dist\windows\libs
|
copy libs\discord-rpc.dll dist\windows\libs
|
||||||
copy libs\discord-rpc.dll dist\win32\libs
|
copy libs\discord-rpc.dll dist\win32\libs
|
||||||
|
copy libs\discord-rpc.* dist\other\libs
|
||||||
|
|
||||||
copy SOURCES.md dist\windows
|
copy SOURCES.md dist\windows
|
||||||
copy LICENSE.md dist\windows
|
copy LICENSE.md dist\windows
|
||||||
copy SOURCES.md dist\win32
|
copy SOURCES.md dist\win32
|
||||||
copy LICENSE.md dist\win32
|
copy LICENSE.md dist\win32
|
||||||
|
copy SOURCES.md dist\other
|
||||||
|
copy LICENSE.md dist\other
|
||||||
|
|
||||||
cd dist\windows
|
cd dist\windows
|
||||||
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
|
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
|
||||||
@@ -24,3 +30,7 @@ cd ..\..
|
|||||||
cd dist\win32
|
cd dist\win32
|
||||||
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
|
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
|
||||||
cd ..\..
|
cd ..\..
|
||||||
|
|
||||||
|
cd dist\other
|
||||||
|
tar -a -c -f ..\cambridge-other.zip cambridge.love libs *.md
|
||||||
|
cd ..\..
|
||||||
14
res/backgrounds/extend_section_bg.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- ex: extend_section_bg[100] = 0
|
||||||
|
-- extend_section_bg[200] = 0
|
||||||
|
-- the video background associated with section 0 will continue playing into 100 and 200 without restarting.
|
||||||
|
-- will also cause any existing level 100, 200 backgrounds specified to NOT render.
|
||||||
|
|
||||||
|
-- please also note that you cannot currently extend any "named" backgrounds, such as "title" and "options-input"
|
||||||
|
|
||||||
|
extend_section_bg = {}
|
||||||
|
|
||||||
|
-- extend_section_bg[100] = 0
|
||||||
|
-- extend_section_bg[200] = 0
|
||||||
|
-- remove the dashes
|
||||||
|
|
||||||
|
return extend_section_bg
|
||||||
BIN
res/backgrounds/title-night.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
res/backgrounds/title-no-icon.jpg
Normal file
|
After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 734 B After Width: | Height: | Size: 4.9 KiB |
BIN
res/img/bone.png
|
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 151 B |
|
Before Width: | Height: | Size: 153 B After Width: | Height: | Size: 151 B |
BIN
res/img/cambridge_transparent.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |