mirror of
https://github.com/SashLilac/cambridge.git
synced 2025-05-13 20:21:25 -05:00
Compare commits
423 Commits
| 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 | ||
|
|
9f8e9a9778 | ||
|
|
62f9475fa9 | ||
|
|
99d3732d00 | ||
|
|
f5121b62e5 | ||
|
|
cbdbfa6633 | ||
|
|
1b1abc9792 | ||
|
|
894e99e677 | ||
|
|
d4b619da89 | ||
|
|
3766149cb7 | ||
|
|
449ca16bc4 | ||
|
|
71d76e8a6b | ||
|
|
3bdc6e1b2d | ||
|
|
d9b6c85704 | ||
|
|
5ce0686e1a | ||
|
|
3cf496ba98 | ||
|
|
5ddc6ec561 | ||
|
|
b91ffc913b | ||
|
|
ab445ff699 | ||
|
|
7b7a255bf8 | ||
|
|
57721ed35d | ||
|
|
8383d3f445 | ||
|
|
116284f31c | ||
|
|
2189e3a7b8 | ||
|
|
b1d325b714 | ||
|
|
4ab5e3747a | ||
|
|
9761ead48f | ||
|
|
8dedc8a70e | ||
|
|
21769f21c8 | ||
|
|
5cf26b4500 | ||
|
|
4b4a968632 | ||
|
|
e0d98de50d | ||
|
|
4992ea733c | ||
|
|
30ca434027 | ||
|
|
502a50d004 | ||
|
|
36f5287a39 | ||
|
|
a9bbe4a08d | ||
|
|
ee431f5fd8 | ||
|
|
36f2672e06 | ||
|
|
6ecea7edb1 | ||
|
|
dc764b9177 | ||
|
|
5a1494cb5a | ||
|
|
684c4f5b78 | ||
|
|
b568c0fe69 | ||
|
|
2ea75cdfaf | ||
|
|
1f0b43f1b7 | ||
|
|
40bdc5ed99 | ||
|
|
33f2a96ae8 | ||
|
|
846013ce7a | ||
|
|
37c85adc75 | ||
|
|
e7bb44deb4 | ||
|
|
57518dc299 | ||
|
|
0453a3db97 | ||
|
|
b85de17e51 | ||
|
|
163b8f6cc5 | ||
|
|
7250bee619 | ||
|
|
83de216408 | ||
|
|
ba235c8a41 | ||
|
|
ca18d090c9 | ||
|
|
a3a27d2566 | ||
|
|
b15cd9802f | ||
|
|
4c4a818c5c | ||
|
|
716de2814b | ||
|
|
bf19f49323 | ||
|
|
1234e78354 | ||
|
|
9129503d54 | ||
|
|
eae58f11e9 | ||
|
|
3cf5daeb2e | ||
|
|
1dfe68ccff | ||
|
|
8a459b68ba | ||
|
|
cb2b693bcb | ||
|
|
ef6d156d38 | ||
|
|
83e498534c | ||
|
|
8f19c73e2a | ||
|
|
e36b855ff7 | ||
|
|
23b58951cb | ||
|
|
1d73916b7c | ||
|
|
3947e9f02f | ||
|
|
99b15803ee | ||
|
|
d350b25726 | ||
|
|
44e4d00172 | ||
|
|
31e2529265 | ||
|
|
ea7c75f0b3 | ||
|
|
714c6b5e99 | ||
|
|
6a5d5a9c88 | ||
|
|
03491ba151 | ||
|
|
6e22e3d15b | ||
|
|
66ab5992ad | ||
|
|
2c07c2a58c | ||
|
|
a4d3f3bffc | ||
|
|
9ac60cbb5e | ||
|
|
cdd846c3e6 | ||
|
|
33d260b753 | ||
|
|
1644fcdf8e | ||
|
|
f3c1cf6e1f | ||
|
|
06a8a2ebf7 | ||
|
|
15354ce004 | ||
|
|
af02cd3467 | ||
|
|
acb05918c1 | ||
|
|
b644c8e457 | ||
|
|
288961e12a | ||
|
|
a047e51681 | ||
|
|
77f24f5ee5 | ||
|
|
32c2274bef | ||
|
|
4920e5de1c | ||
|
|
8418fc8ab7 | ||
|
|
711a5120f1 | ||
|
|
e7c3c9446a | ||
|
|
3ac39acd7a | ||
|
|
d0505251b3 | ||
|
|
bb0fe2ac20 | ||
|
|
986ebac47f | ||
|
|
9799147f96 | ||
|
|
1dda12e4be | ||
|
|
38947e00c0 | ||
|
|
035f6dd7b4 | ||
|
|
aa3eadc93d | ||
|
|
cb6962825f | ||
|
|
b5e7ce5be6 | ||
|
|
1ccd6a09d3 | ||
|
|
5a074f77cf | ||
|
|
81677221f1 | ||
|
|
a998be6f7b | ||
|
|
9c1c8eea21 | ||
|
|
f022c6c4b7 | ||
|
|
38f3d23b95 | ||
|
|
816d27db39 | ||
|
|
ce08ffd3da | ||
|
|
f0e84a8874 | ||
|
|
5e02471fb4 | ||
|
|
fa2fe77081 | ||
|
|
682c4a485a | ||
|
|
68760105cc | ||
|
|
e19da98ea1 | ||
|
|
e8904b92ed | ||
|
|
4f574e7716 | ||
|
|
f1528e8d71 | ||
|
|
79a25c3954 | ||
|
|
0f3883e18d | ||
|
|
1acd0ec65a | ||
|
|
b22f671409 | ||
|
|
0b6f62d50e | ||
|
|
086f327371 | ||
|
|
3c83ae0bf4 | ||
|
|
450833b246 | ||
|
|
8e7a5418dc | ||
|
|
6609b642dc | ||
|
|
452879ebab | ||
|
|
70a827b477 | ||
|
|
d281a732db | ||
|
|
01e91fbd93 | ||
|
|
ece853c9d3 | ||
|
|
ea8d008370 | ||
|
|
e20eb048c8 | ||
|
|
a33ca1af24 | ||
|
|
664bca2282 | ||
|
|
fc8fb8b66f | ||
|
|
fc58e6e908 | ||
|
|
061f6f5164 | ||
|
|
4e9cea7dda | ||
|
|
fa97216167 | ||
|
|
3f8d68cc9d | ||
|
|
6639d73c1c | ||
|
|
668f061077 | ||
|
|
cb70967b82 | ||
|
|
0c2ba5f0cc | ||
|
|
6d07a3b820 | ||
|
|
2de13a97f0 | ||
|
|
512c2149f0 | ||
|
|
6fb19220b7 | ||
|
|
08da67c434 | ||
|
|
2d63ca8ee1 | ||
|
|
0f09d47e60 | ||
|
|
9d44d1e771 | ||
|
|
5d022f9037 | ||
|
|
818743fe77 |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
*.sav
|
||||
*.love
|
||||
*.zip
|
||||
dist/*.zip
|
||||
dist/**/cambridge.exe
|
||||
dist/**/libs
|
||||
|
||||
@@ -57,13 +57,13 @@ Coding conventions
|
||||
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.
|
||||
* 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
|
||||
---- 4 spaces
|
||||
if self.level < 900 then return 12
|
||||
elseif self.level < 1200 then return 8
|
||||
else return 6 end
|
||||
if self.level < 900 then return 12
|
||||
elseif self.level < 1200 then return 8
|
||||
else return 6
|
||||
end
|
||||
```
|
||||
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
88
README.md
88
README.md
@@ -1,54 +1,38 @@
|
||||

|
||||

|
||||
|
||||
Cambridge
|
||||
=========
|
||||
|
||||
Welcome to Cambridge, the next open-source falling-block game engine!
|
||||
|
||||
This fork 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)!
|
||||
|
||||
Join our Discord server for help and a welcoming community! https://discord.gg/mteMJw4
|
||||
The Discord server has been reopened! https://discord.gg/AADZUmgsph
|
||||
|
||||
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!
|
||||
|
||||
The following people in no particular order also helped with the project:
|
||||
- [Hailey](https://github.com/haileylgbt)
|
||||
- CylinderKnot
|
||||
- MarkGamed7794
|
||||
- [Mizu](https://github.com/rexxt)
|
||||
- MattMayuga
|
||||
- Kitaru
|
||||
- switchpalacecorner
|
||||
- [sinefuse](https://github.com/sinefuse)
|
||||
- [2Tie](https://github.com/2Tie)
|
||||
- [nightmareci](https://github.com/nightmareci)
|
||||
- [MyPasswordIsWeak](https://github.com/MyPasswordIsWeak)
|
||||
- [Dr Ocelot](https://github.com/Dr-Ocelot)
|
||||
|
||||

|
||||
The game also has a website now with more detail than seen on this README: https://t-sp.in/cambridge
|
||||
|
||||
Playing the game
|
||||
----------------
|
||||
|
||||
### Windows
|
||||
|
||||
You do not need LÖVE on Windows, as it comes bundled with the program.
|
||||
You do not need LÖVE on Windows, as it comes bundled with the program.
|
||||
|
||||
To get the stable release, simply download the ZIP in the latest release. All assets needed are bundled with the executable.
|
||||
#### Stable release
|
||||
|
||||
If you want the bleeding edge version, download [this](https://github.com/SashLilac/cambridge/archive/master.zip).
|
||||
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).
|
||||
|
||||
Extract the ZIP, open a Command Prompt at the folder you extracted Cambridge to, then run this command:
|
||||
All assets needed are bundled with the executable.
|
||||
|
||||
#### Bleeding edge
|
||||
|
||||
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.
|
||||
|
||||
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 .
|
||||
|
||||
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 .
|
||||
|
||||
@@ -58,7 +42,7 @@ Then, check the mod pack section at the bottom of this page.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -70,7 +54,7 @@ You can download the .love file in the latest release, and run it with:
|
||||
|
||||
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.
|
||||
|
||||
@@ -82,13 +66,7 @@ It should run automatically!
|
||||
|
||||
## Installing modpacks
|
||||
|
||||
Simply drag your mode, ruleset, and randomizer Lua files into their respective [directory](https://love2d.org/wiki/love.filesystem), and they should appear automatically.
|
||||
|
||||
You can also load custom assets through this way, assuming you preserve the directory structure.
|
||||
|
||||
**WARNING:** The .exe / .love files and the bleeding edge releases have different save directories. Read the above link carefully!
|
||||
|
||||
For more detailed instructions, install [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
|
||||
-------
|
||||
@@ -100,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
|
||||
licensed. Their original sources, and copyright notices if applicable, are
|
||||
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
|
||||
|
||||

|
||||
|
||||
80
SOURCES.md
80
SOURCES.md
@@ -139,4 +139,82 @@ 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.
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
bigint.lua (https://github.com/empyreuma/bigint.lua)
|
||||
--------------------
|
||||
|
||||
3-Clause BSD License
|
||||
|
||||
Copyright (c) Emily "empyreuma" 2016
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
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 <COPYRIGHT HOLDER> 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.
|
||||
|
||||
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\LICENSE.md
|
||||
@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-win32.zip
|
||||
@del dist\cambridge-win32.zip
|
||||
@del dist\cambridge-other.zip
|
||||
1
conf.lua
1
conf.lua
@@ -6,5 +6,6 @@ function love.conf(t)
|
||||
t.window.title = "Cambridge"
|
||||
t.window.width = 640
|
||||
t.window.height = 480
|
||||
t.window.icon = "res/img/cambridge_icon.png"
|
||||
t.window.vsync = false
|
||||
end
|
||||
|
||||
@@ -3,19 +3,11 @@ Game modes
|
||||
|
||||
There are several classes of game modes. The modes that originate from other games are organized by suffix:
|
||||
|
||||
* The "C" series stand for "Classic" games, games that were produced before around 1992-1993 and generally have no wallkicks or lock delay.
|
||||
* C84 - The original version from the Electronika 60.
|
||||
* C88 - Sega Tetris.
|
||||
* C89 - Nintendo / NES Tetris.
|
||||
* The "A" series stand for "Arika" games, or games in the Tetris the Grand Master series.
|
||||
* A1 - Tetris The Grand Master (the original from 1998).
|
||||
* A2 - Tetris The Absolute The Grand Master 2 PLUS.
|
||||
* A3 - Tetris The Grand Master 3 Terror-Instinct.
|
||||
* AX - Tetris The Grand Master ACE (X for Xbox).
|
||||
* The "G" series stand for "Guideline" games, or games that follow the Tetris Guideline.
|
||||
* GF - Tetris Friends (2007-2019)
|
||||
* GJ - Tetris Online Japan (2005-2011)
|
||||
* N stands for Nullpomino, only used for Phantom Mania N.
|
||||
|
||||
MARATHON
|
||||
--------
|
||||
@@ -28,8 +20,6 @@ From other games:
|
||||
* **MARATHON A1**: Tetris the Grand Master 1.
|
||||
* **MARATHON A2**: Tetris the Grand Master 2 (TAP Master).
|
||||
* **MARATHON A3**: Tetris the Grand Master 3 (no exams).
|
||||
* **MARATHON AX4**: Another mode from TGM Ace.
|
||||
* **MARATHON C89**: Nintendo NES Tetris. Can you transition and make it to the killscreen?
|
||||
|
||||
|
||||
SURVIVAL
|
||||
@@ -43,14 +33,7 @@ From other games:
|
||||
* **SURVIVAL A1**: 20G mode from Tetris the Grand Master.
|
||||
* **SURVIVAL A2**: T.A. Death.
|
||||
* **SURVIVAL A3**: Ti Shirase.
|
||||
|
||||
|
||||
RACE
|
||||
----
|
||||
|
||||
Modes with no levels, just a single timed goal.
|
||||
|
||||
* **Race 40**: How fast can you clear 40 lines? No limits, no holds barred.
|
||||
* **SURVIVAL AX**: Another mode from TGM Ace.
|
||||
|
||||
|
||||
PHANTOM MANIA
|
||||
@@ -69,8 +52,4 @@ OTHER MODES
|
||||
|
||||
* **Strategy**: How well can you plan ahead your movements? Can you handle only having a short time to place each piece?
|
||||
|
||||
* **TetrisGram™ Pacer Test**: is a multi-stage piece-placing ability test that progressively gets more difficult as it continues.
|
||||
|
||||
* **Interval Training**: 30 seconds per section. 20G. 15 frames of lock delay. How long can you last?
|
||||
|
||||
* **Demon Mode**: An original mode from Oshisaure! Can you push through the ever faster levels and not get denied?
|
||||
* **Big A2**: Marathon A2 but all the pieces are BIG!
|
||||
96
funcs.lua
96
funcs.lua
@@ -1,10 +1,20 @@
|
||||
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
|
||||
local meta = getmetatable(t)
|
||||
local target = {}
|
||||
for k, v in pairs(t) do target[k] = v end
|
||||
setmetatable(target, meta)
|
||||
for k, v in next, t do target[k] = v end
|
||||
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
|
||||
end
|
||||
|
||||
@@ -50,15 +60,28 @@ function formatTime(frames)
|
||||
min = math.floor(frames/3600)
|
||||
sec = math.floor(frames/60) % 60
|
||||
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)
|
||||
return str
|
||||
end
|
||||
|
||||
function formatBigNum(number)
|
||||
-- returns a string representing a number with commas as thousands separator (e.g. 12,345,678)
|
||||
local s = string.format("%d", number)
|
||||
local pos = string.len(s) % 3
|
||||
if pos == 0 then pos = 3 end
|
||||
local s
|
||||
if type(number) == "number" then
|
||||
s = string.format("%d", number)
|
||||
elseif type(number) == "string" then
|
||||
if not tonumber(number) then
|
||||
return
|
||||
else
|
||||
s = number
|
||||
end
|
||||
else
|
||||
return
|
||||
end
|
||||
local pos = Mod1(string.len(s), 3)
|
||||
return string.sub(s, 1, pos)
|
||||
.. string.gsub(string.sub(s, pos+1), "(...)", ",%1")
|
||||
end
|
||||
@@ -66,4 +89,61 @@ end
|
||||
function Mod1(n, m)
|
||||
-- returns a number congruent to n modulo m in the range [1;m] (as opposed to [0;m-1])
|
||||
return ((n-1) % m) + 1
|
||||
end
|
||||
end
|
||||
|
||||
function table.contains(table, element)
|
||||
for _, value in pairs(table) do
|
||||
if value == element then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
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)
|
||||
if max < min then
|
||||
min, max = max, min
|
||||
end
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
566
libs/bigint/bigint.lua
Normal file
566
libs/bigint/bigint.lua
Normal file
@@ -0,0 +1,566 @@
|
||||
#!/usr/bin/env lua
|
||||
-- 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
|
||||
-- errors and bugs earlier.
|
||||
local strict = true
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local bigint = {}
|
||||
setmetatable(bigint, {__call = function(_, arg) return bigint.new(arg) end})
|
||||
|
||||
local mt = {
|
||||
__add = function(lhs, rhs)
|
||||
return bigint.add(lhs, rhs)
|
||||
end,
|
||||
__unm = function(arg)
|
||||
return bigint.negate(arg)
|
||||
end,
|
||||
__sub = function(lhs, rhs)
|
||||
return bigint.subtract(lhs, rhs)
|
||||
end,
|
||||
__mul = function(lhs, rhs)
|
||||
return bigint.multiply(lhs, rhs)
|
||||
end,
|
||||
__div = function(lhs, rhs)
|
||||
return bigint.divide(lhs, rhs)
|
||||
end,
|
||||
__mod = function(lhs, rhs)
|
||||
return bigint.modulus(lhs, rhs)
|
||||
end,
|
||||
__pow = function(lhs, rhs)
|
||||
return bigint.exponentiate(lhs, rhs)
|
||||
end,
|
||||
__tostring = function(arg)
|
||||
return bigint.unserialize(arg, "s")
|
||||
end,
|
||||
__eq = function(lhs, rhs)
|
||||
return bigint.compare(lhs, rhs, "==")
|
||||
end,
|
||||
__lt = function(lhs, rhs)
|
||||
return bigint.compare(lhs, rhs, "<")
|
||||
end,
|
||||
__le = function(lhs, rhs)
|
||||
return bigint.compare(lhs, rhs, "<=")
|
||||
end
|
||||
}
|
||||
|
||||
local named_powers = require("libs.bigint.named-powers-of-ten")
|
||||
|
||||
-- Create a new bigint or convert a number or string into a big
|
||||
-- Returns an empty, positive bigint if no number or string is given
|
||||
function bigint.new(num)
|
||||
local self = {
|
||||
sign = "+",
|
||||
digits = {}
|
||||
}
|
||||
|
||||
-- Return a new bigint with the same sign and digits
|
||||
function self:clone()
|
||||
local newint = bigint.new()
|
||||
newint.sign = self.sign
|
||||
for _, digit in pairs(self.digits) do
|
||||
newint.digits[#newint.digits + 1] = digit
|
||||
end
|
||||
return newint
|
||||
end
|
||||
|
||||
setmetatable(self, mt)
|
||||
|
||||
if (num) then
|
||||
local num_string = tostring(num)
|
||||
for digit in string.gmatch(num_string, "[0-9]") do
|
||||
table.insert(self.digits, tonumber(digit))
|
||||
end
|
||||
if string.sub(num_string, 1, 1) == "-" then
|
||||
self.sign = "-"
|
||||
end
|
||||
end
|
||||
|
||||
return bigint.strip(self)
|
||||
end
|
||||
|
||||
-- Check the type of a big
|
||||
-- Normally only runs when global variable "strict" == true, but checking can be
|
||||
-- forced by supplying "true" as the second argument.
|
||||
function bigint.check(big, force)
|
||||
if (strict or force) then
|
||||
assert(getmetatable(big) == mt, "at least one arg is not a bigint")
|
||||
assert(#big.digits > 0, "bigint is empty")
|
||||
assert(big.sign == "+" or big.sign == "-", "bigint is unsigned")
|
||||
for _, digit in pairs(big.digits) do
|
||||
assert(type(digit) == "number", "at least one digit is invalid")
|
||||
assert(digit <= 9 and digit >= 0, digit .. " is not between 0 and 9")
|
||||
assert(math.floor(digit) == digit, digit .. " is not an integer")
|
||||
end
|
||||
end
|
||||
return true
|
||||
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
|
||||
-- value)
|
||||
function bigint.abs(big)
|
||||
bigint.check(big)
|
||||
local result = big:clone()
|
||||
result.sign = "+"
|
||||
return result
|
||||
end
|
||||
|
||||
-- Return a new big with the same digits but the opposite sign (negation)
|
||||
function bigint.negate(big)
|
||||
bigint.check(big)
|
||||
local result = big:clone()
|
||||
if (result.sign == "+") then
|
||||
result.sign = "-"
|
||||
else
|
||||
result.sign = "+"
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Return the number of digits in the big
|
||||
function bigint.digits(big)
|
||||
bigint.check(big)
|
||||
return #big.digits
|
||||
end
|
||||
|
||||
-- Convert a big to a number or string
|
||||
function bigint.unserialize(big, output_type, precision)
|
||||
bigint.check(big)
|
||||
|
||||
local num = ""
|
||||
if big.sign == "-" then
|
||||
num = "-"
|
||||
end
|
||||
|
||||
|
||||
if ((output_type == nil)
|
||||
or (output_type == "number")
|
||||
or (output_type == "n")
|
||||
or (output_type == "string")
|
||||
or (output_type == "s")) then
|
||||
-- Unserialization to a string or number requires reconstructing the
|
||||
-- entire number
|
||||
|
||||
for _, digit in pairs(big.digits) do
|
||||
num = num .. math.floor(digit) -- lazy way of getting rid of .0$
|
||||
end
|
||||
|
||||
if ((output_type == nil)
|
||||
or (output_type == "number")
|
||||
or (output_type == "n")) then
|
||||
return tonumber(num)
|
||||
else
|
||||
return num
|
||||
end
|
||||
|
||||
else
|
||||
-- Unserialization to human-readable form or scientific notation only
|
||||
-- requires reading the first few digits
|
||||
if (precision == nil) then
|
||||
precision = math.min(#big.digits, 3)
|
||||
else
|
||||
assert(precision > 0, "Precision cannot be less than 1")
|
||||
assert(math.floor(precision) == precision,
|
||||
"Precision must be a positive integer")
|
||||
end
|
||||
|
||||
-- num is the first (precision + 1) digits, the first being separated by
|
||||
-- a decimal point from the others
|
||||
num = num .. math.floor(big.digits[1])
|
||||
if (precision > 1) then
|
||||
num = num .. "."
|
||||
for i = 1, (precision - 1) do
|
||||
num = num .. math.floor(big.digits[i + 1])
|
||||
end
|
||||
end
|
||||
|
||||
if ((output_type == "human-readable")
|
||||
or (output_type == "human")
|
||||
or (output_type == "h"))
|
||||
and (#big.digits >= 3 and #big.digits <= 10002) then
|
||||
-- Human-readable output contributed by 123eee555
|
||||
|
||||
local name
|
||||
local walkback = 0 -- Used to enumerate "ten", "hundred", etc
|
||||
|
||||
-- Walk backwards in the index of named_powers starting at the
|
||||
-- number of digits of the input until the first value is found
|
||||
for i = (#big.digits - 1), (#big.digits - 4), -1 do
|
||||
name = named_powers[i]
|
||||
if (name) then
|
||||
if (walkback == 1) then
|
||||
name = "ten " .. name
|
||||
elseif (walkback == 2) then
|
||||
name = "hundred " .. name
|
||||
end
|
||||
break
|
||||
else
|
||||
walkback = walkback + 1
|
||||
end
|
||||
end
|
||||
|
||||
return num .. " " .. name
|
||||
|
||||
else
|
||||
return num .. "*10^" .. (#big.digits - 1)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Basic comparisons
|
||||
-- Accepts symbols (<, >=, ~=) and Unix shell-like options (lt, ge, ne)
|
||||
function bigint.compare(big1, big2, comparison)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
|
||||
local greater = false -- If big1.digits > big2.digits
|
||||
local equal = false
|
||||
|
||||
if (big1.sign == "-") and (big2.sign == "+") then
|
||||
greater = false
|
||||
elseif (#big1.digits > #big2.digits)
|
||||
or ((big1.sign == "+") and (big2.sign == "-")) then
|
||||
greater = true
|
||||
elseif (#big1.digits == #big2.digits) then
|
||||
-- Walk left to right, comparing digits
|
||||
for digit = 1, #big1.digits do
|
||||
if (big1.digits[digit] > big2.digits[digit]) then
|
||||
greater = true
|
||||
break
|
||||
elseif (big2.digits[digit] > big1.digits[digit]) then
|
||||
break
|
||||
elseif (digit == #big1.digits)
|
||||
and (big1.digits[digit] == big2.digits[digit]) then
|
||||
equal = true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- If both numbers are negative, then the requirements for greater are
|
||||
-- reversed
|
||||
if (not equal) and (big1.sign == "-") and (big2.sign == "-") then
|
||||
greater = not greater
|
||||
end
|
||||
|
||||
return (((comparison == "<") or (comparison == "lt"))
|
||||
and ((not greater) and (not equal)) and true)
|
||||
or (((comparison == ">") or (comparison == "gt"))
|
||||
and ((greater) and (not equal)) and true)
|
||||
or (((comparison == "==") or (comparison == "eq"))
|
||||
and (equal) and true)
|
||||
or (((comparison == ">=") or (comparison == "ge"))
|
||||
and (equal or greater) and true)
|
||||
or (((comparison == "<=") or (comparison == "le"))
|
||||
and (equal or not greater) and true)
|
||||
or (((comparison == "~=") or (comparison == "!=") or (comparison == "ne"))
|
||||
and (not equal) and true)
|
||||
or false
|
||||
end
|
||||
|
||||
-- BACKEND: Add big1 and big2, ignoring signs
|
||||
function bigint.add_raw(big1, big2)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
|
||||
local result = bigint.new()
|
||||
local max_digits = 0
|
||||
local carry = 0
|
||||
|
||||
if (#big1.digits >= #big2.digits) then
|
||||
max_digits = #big1.digits
|
||||
else
|
||||
max_digits = #big2.digits
|
||||
end
|
||||
|
||||
-- Walk backwards right to left, like in long addition
|
||||
for digit = 0, max_digits - 1 do
|
||||
local sum = (big1.digits[#big1.digits - digit] or 0)
|
||||
+ (big2.digits[#big2.digits - digit] or 0)
|
||||
+ carry
|
||||
|
||||
if (sum >= 10) then
|
||||
carry = 1
|
||||
sum = sum - 10
|
||||
else
|
||||
carry = 0
|
||||
end
|
||||
|
||||
result.digits[max_digits - digit] = sum
|
||||
end
|
||||
|
||||
-- Leftover carry in cases when #big1.digits == #big2.digits and sum > 10, ex. 7 + 9
|
||||
if (carry == 1) then
|
||||
table.insert(result.digits, 1, 1)
|
||||
end
|
||||
|
||||
return result
|
||||
|
||||
end
|
||||
|
||||
-- BACKEND: Subtract big2 from big1, ignoring signs
|
||||
function bigint.subtract_raw(big1, big2)
|
||||
-- Type checking is done by bigint.compare
|
||||
assert(bigint.compare(bigint.abs(big1), bigint.abs(big2), ">="),
|
||||
"Size of " .. bigint.unserialize(big1, "string") .. " is less than "
|
||||
.. bigint.unserialize(big2, "string"))
|
||||
|
||||
local result = big1:clone()
|
||||
local max_digits = #big1.digits
|
||||
local borrow = 0
|
||||
|
||||
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||
-- Walk backwards right to left, like in long subtraction
|
||||
for digit = 0, max_digits - 1 do
|
||||
local diff = (big1.digits[#big1.digits - digit] or 0)
|
||||
- (big2.digits[#big2.digits - digit] or 0)
|
||||
- borrow
|
||||
|
||||
if (diff < 0) then
|
||||
borrow = 1
|
||||
diff = diff + 10
|
||||
else
|
||||
borrow = 0
|
||||
end
|
||||
|
||||
result.digits[max_digits - digit] = diff
|
||||
end
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
|
||||
return bigint.strip(result)
|
||||
end
|
||||
|
||||
-- FRONTEND: Addition and subtraction operations, accounting for signs
|
||||
function bigint.add(big1, big2)
|
||||
-- Type checking is done by bigint.compare
|
||||
|
||||
local result
|
||||
|
||||
-- If adding numbers of different sign, subtract the smaller sized one from
|
||||
-- the bigger sized one and take the sign of the bigger sized one
|
||||
if (big1.sign ~= big2.sign) then
|
||||
if (bigint.compare(bigint.abs(big1), bigint.abs(big2), ">")) then
|
||||
result = bigint.subtract_raw(big1, big2)
|
||||
result.sign = big1.sign
|
||||
else
|
||||
result = bigint.subtract_raw(big2, big1)
|
||||
result.sign = big2.sign
|
||||
end
|
||||
|
||||
elseif (big1.sign == "+") and (big2.sign == "+") then
|
||||
result = bigint.add_raw(big1, big2)
|
||||
|
||||
elseif (big1.sign == "-") and (big2.sign == "-") then
|
||||
result = bigint.add_raw(big1, big2)
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function bigint.subtract(big1, big2)
|
||||
-- Type checking is done by bigint.compare in bigint.add
|
||||
-- Subtracting is like adding a negative
|
||||
local big2_local = big2:clone()
|
||||
if (big2.sign == "+") then
|
||||
big2_local.sign = "-"
|
||||
else
|
||||
big2_local.sign = "+"
|
||||
end
|
||||
return bigint.add(big1, big2_local)
|
||||
end
|
||||
|
||||
-- BACKEND: Multiply a big by a single digit big, ignoring signs
|
||||
function bigint.multiply_single(big1, big2)
|
||||
bigint.check(big1)
|
||||
bigint.check(big2)
|
||||
assert(#big2.digits == 1, bigint.unserialize(big2, "string")
|
||||
.. " has more than one digit")
|
||||
|
||||
local result = bigint.new()
|
||||
local carry = 0
|
||||
|
||||
-- Logic mostly copied from bigint.add_raw ---------------------------------
|
||||
-- Walk backwards right to left, like in long multiplication
|
||||
for digit = 0, #big1.digits - 1 do
|
||||
local this_digit = big1.digits[#big1.digits - digit]
|
||||
* big2.digits[1]
|
||||
+ carry
|
||||
|
||||
if (this_digit >= 10) then
|
||||
carry = math.floor(this_digit / 10)
|
||||
this_digit = this_digit - (carry * 10)
|
||||
else
|
||||
carry = 0
|
||||
end
|
||||
|
||||
result.digits[#big1.digits - digit] = this_digit
|
||||
end
|
||||
|
||||
-- Leftover carry in cases when big1.digits[1] * big2.digits[1] > 0
|
||||
if (carry > 0) then
|
||||
table.insert(result.digits, 1, carry)
|
||||
end
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- FRONTEND: Multiply two bigs, accounting for signs
|
||||
function bigint.multiply(big1, big2)
|
||||
-- Type checking done by bigint.multiply_single
|
||||
|
||||
local result = bigint.new(0)
|
||||
local larger, smaller -- Larger and smaller in terms of digits, not size
|
||||
|
||||
if (bigint.unserialize(big1) == 0) or (bigint.unserialize(big2) == 0) then
|
||||
return result
|
||||
end
|
||||
|
||||
if (#big1.digits >= #big2.digits) then
|
||||
larger = big1
|
||||
smaller = big2
|
||||
else
|
||||
larger = big2
|
||||
smaller = big1
|
||||
end
|
||||
|
||||
-- Walk backwards right to left, like in long multiplication
|
||||
for digit = 0, #smaller.digits - 1 do
|
||||
-- Sorry for going over column 80! There's lots of big names here
|
||||
local this_digit_product = bigint.multiply_single(larger,
|
||||
bigint.new(smaller.digits[#smaller.digits - digit]))
|
||||
|
||||
-- "Placeholding zeroes"
|
||||
if (digit > 0) then
|
||||
for placeholder = 1, digit do
|
||||
table.insert(this_digit_product.digits, 0)
|
||||
end
|
||||
end
|
||||
|
||||
result = bigint.add(result, this_digit_product)
|
||||
end
|
||||
|
||||
if (larger.sign == smaller.sign) then
|
||||
result.sign = "+"
|
||||
else
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Raise a big to a positive integer or big power (TODO: negative integer power)
|
||||
function bigint.exponentiate(big, power)
|
||||
-- Type checking for big done by bigint.multiply
|
||||
assert(bigint.compare(power, bigint.new(0), ">="),
|
||||
"negative powers are not supported")
|
||||
local exp = power:clone()
|
||||
|
||||
if (bigint.compare(exp, bigint.new(0), "==")) then
|
||||
return bigint.new(1)
|
||||
elseif (bigint.compare(exp, bigint.new(1), "==")) then
|
||||
return big:clone()
|
||||
else
|
||||
local result = bigint.new(1)
|
||||
local base = big:clone()
|
||||
|
||||
while (true) do
|
||||
if (bigint.compare(
|
||||
bigint.modulus(exp, bigint.new(2)), bigint.new(1), "=="
|
||||
)) then
|
||||
result = bigint.multiply(result, base)
|
||||
end
|
||||
if (bigint.compare(exp, bigint.new(1), "==")) then
|
||||
break
|
||||
else
|
||||
exp = bigint.divide(exp, bigint.new(2))
|
||||
base = bigint.multiply(base, base)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- BACKEND: Divide two bigs (decimals not supported), returning big result and
|
||||
-- big remainder
|
||||
-- WARNING: Only supports positive integers
|
||||
function bigint.divide_raw(big1, big2)
|
||||
-- Type checking done by bigint.compare
|
||||
if (bigint.compare(big1, big2, "==")) then
|
||||
return bigint.new(1), bigint.new(0)
|
||||
elseif (bigint.compare(big1, big2, "<")) then
|
||||
return bigint.new(0), big1:clone()
|
||||
else
|
||||
assert(bigint.compare(big2, bigint.new(0), "!="), "error: divide by zero")
|
||||
assert(big1.sign == "+", "error: big1 is not positive")
|
||||
assert(big2.sign == "+", "error: big2 is not positive")
|
||||
|
||||
local result = bigint.new()
|
||||
|
||||
local dividend = bigint.new() -- Dividend of a single operation
|
||||
|
||||
local neg_zero = bigint.new(0)
|
||||
neg_zero.sign = "-"
|
||||
|
||||
for i = 1, #big1.digits do
|
||||
-- Fixes a negative zero bug
|
||||
if (#dividend.digits ~= 0) and (bigint.compare(dividend, neg_zero, "==")) then
|
||||
dividend = bigint.new()
|
||||
end
|
||||
|
||||
table.insert(dividend.digits, big1.digits[i])
|
||||
|
||||
local factor = bigint.new(0)
|
||||
while bigint.compare(dividend, big2, ">=") do
|
||||
dividend = bigint.subtract(dividend, big2)
|
||||
factor = bigint.add(factor, bigint.new(1))
|
||||
end
|
||||
|
||||
for i = 0, #factor.digits - 1 do
|
||||
result.digits[#result.digits + 1 - i] = factor.digits[i + 1]
|
||||
end
|
||||
end
|
||||
|
||||
return bigint.strip(result), dividend
|
||||
end
|
||||
end
|
||||
|
||||
-- FRONTEND: Divide two bigs (decimals not supported), returning big result and
|
||||
-- big remainder, accounting for signs
|
||||
function bigint.divide(big1, big2)
|
||||
local result, remainder = bigint.divide_raw(bigint.abs(big1),
|
||||
bigint.abs(big2))
|
||||
if (big1.sign == big2.sign) then
|
||||
result.sign = "+"
|
||||
else
|
||||
result.sign = "-"
|
||||
end
|
||||
|
||||
return result, remainder
|
||||
end
|
||||
|
||||
-- FRONTEND: Return only the remainder from bigint.divide
|
||||
function bigint.modulus(big1, big2)
|
||||
local result, remainder = bigint.divide(big1, big2)
|
||||
|
||||
-- Remainder will always have the same sign as the dividend per C standard
|
||||
-- https://en.wikipedia.org/wiki/Modulo_operation#Remainder_calculation_for_the_modulo_operation
|
||||
remainder.sign = big1.sign
|
||||
return remainder
|
||||
end
|
||||
|
||||
return bigint
|
||||
3340
libs/bigint/named-powers-of-ten.lua
Normal file
3340
libs/bigint/named-powers-of-ten.lua
Normal file
File diff suppressed because it is too large
Load Diff
60
libs/cpml/LICENSE.md
Normal file
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
1048
libs/discordGameSDK.lua
Normal file
File diff suppressed because it is too large
Load Diff
13
libs/discordGameSDK/README.md
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
4199
libs/discordGameSDK/csharp/Core.cs
Normal file
File diff suppressed because it is too large
Load Diff
53
libs/discordGameSDK/csharp/ImageManager.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
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
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
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
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
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
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
Normal file
Binary file not shown.
BIN
libs/discordGameSDK/lib/x86/discord_game_sdk.dll.lib
Normal file
BIN
libs/discordGameSDK/lib/x86/discord_game_sdk.dll.lib
Normal file
Binary file not shown.
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.bundle
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.bundle
Normal file
Binary file not shown.
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll
Normal file
Binary file not shown.
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll.lib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll.lib
Normal file
Binary file not shown.
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dylib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dylib
Normal file
Binary file not shown.
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.so
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.so
Normal file
Binary file not shown.
@@ -24,6 +24,59 @@ if osname == "Linux" then
|
||||
elseif osname == "OS X" then
|
||||
discordRPClib = ffi.load(source.."/libs/discord-rpc.dylib")
|
||||
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")
|
||||
else
|
||||
-- Else it crashes later on
|
||||
|
||||
43
load/bgm.lua
43
load/bgm.lua
@@ -6,34 +6,45 @@ bgm = {
|
||||
}
|
||||
|
||||
local current_bgm = nil
|
||||
local pitch = 1
|
||||
local bgm_locked = false
|
||||
|
||||
function switchBGM(sound, subsound)
|
||||
if bgm_locked then return end
|
||||
if bgm_locked then
|
||||
return
|
||||
end
|
||||
if current_bgm ~= nil then
|
||||
current_bgm:stop()
|
||||
end
|
||||
if subsound ~= nil then
|
||||
current_bgm = bgm[sound][subsound]
|
||||
resetBGMFadeout()
|
||||
if config.bgm_volume <= 0 then
|
||||
current_bgm = nil
|
||||
elseif sound ~= nil then
|
||||
current_bgm = bgm[sound]
|
||||
resetBGMFadeout()
|
||||
if subsound ~= nil then
|
||||
current_bgm = bgm[sound][subsound]
|
||||
else
|
||||
current_bgm = bgm[sound]
|
||||
end
|
||||
else
|
||||
current_bgm = nil
|
||||
end
|
||||
if current_bgm ~= nil then
|
||||
resetBGMFadeout()
|
||||
end
|
||||
end
|
||||
|
||||
function switchBGMLoop(sound, subsound)
|
||||
if bgm_locked then return end
|
||||
switchBGM(sound, subsound)
|
||||
current_bgm:setLooping(true)
|
||||
if current_bgm then current_bgm:setLooping(true) end
|
||||
end
|
||||
|
||||
function lockBGM()
|
||||
bgm_locked = true
|
||||
end
|
||||
|
||||
function unlockBGM()
|
||||
bgm_locked = false
|
||||
end
|
||||
|
||||
local fading_bgm = false
|
||||
local fadeout_time = 0
|
||||
local total_fadeout_time = 0
|
||||
@@ -49,17 +60,19 @@ end
|
||||
function resetBGMFadeout(time)
|
||||
current_bgm:setVolume(config.bgm_volume)
|
||||
fading_bgm = false
|
||||
current_bgm:play()
|
||||
resumeBGM()
|
||||
end
|
||||
|
||||
function processBGMFadeout(dt)
|
||||
if fading_bgm then
|
||||
if current_bgm and fading_bgm then
|
||||
fadeout_time = fadeout_time - dt
|
||||
if fadeout_time < 0 then
|
||||
fadeout_time = 0
|
||||
fading_bgm = false
|
||||
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
|
||||
|
||||
@@ -72,5 +85,13 @@ end
|
||||
function resumeBGM()
|
||||
if current_bgm ~= nil then
|
||||
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
|
||||
|
||||
2
load/bigint.lua
Normal file
2
load/bigint.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
bigint = require "libs.bigint.bigint"
|
||||
number_names = require "libs.bigint.named-powers-of-ten"
|
||||
29
load/cpml.lua
Normal file
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
|
||||
)
|
||||
|
||||
font_8x11 = love.graphics.newImageFont(
|
||||
"res/fonts/8x11_medium.png",
|
||||
"0123456789:.",
|
||||
-- this would be font_8x11 with the other one as 8x11_2
|
||||
-- but that would break compatibility :(
|
||||
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
|
||||
)
|
||||
|
||||
13
load/gamesdk.lua
Normal file
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 = {
|
||||
[0] = love.graphics.newImage("res/backgrounds/0.png"),
|
||||
love.graphics.newImage("res/backgrounds/100.png"),
|
||||
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"),
|
||||
named_backgrounds = {
|
||||
"title", "title_no_icon", "title_night",
|
||||
"snow", "options_input", "options_game"
|
||||
}
|
||||
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 = {
|
||||
["2tie"] = {
|
||||
R = love.graphics.newImage("res/img/s1.png"),
|
||||
@@ -34,6 +120,8 @@ blocks = {
|
||||
C = love.graphics.newImage("res/img/s2.png"),
|
||||
B = love.graphics.newImage("res/img/s4.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"),
|
||||
A = love.graphics.newImage("res/img/s8.png"),
|
||||
X = love.graphics.newImage("res/img/s9.png"),
|
||||
@@ -46,6 +134,8 @@ blocks = {
|
||||
C = love.graphics.newImage("res/img/bone.png"),
|
||||
B = 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"),
|
||||
A = love.graphics.newImage("res/img/bone.png"),
|
||||
X = love.graphics.newImage("res/img/bone.png"),
|
||||
@@ -58,9 +148,17 @@ blocks = {
|
||||
C = love.graphics.newImage("res/img/gem2.png"),
|
||||
B = love.graphics.newImage("res/img/gem4.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"),
|
||||
A = love.graphics.newImage("res/img/gem9.png"),
|
||||
X = love.graphics.newImage("res/img/gem9.png"),
|
||||
},
|
||||
["square"] = {
|
||||
W = love.graphics.newImage("res/img/squares.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"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +180,7 @@ ColourSchemes = {
|
||||
Z = "R",
|
||||
O = "Y",
|
||||
T = "M",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for name, blockset in pairs(blocks) do
|
||||
@@ -97,5 +195,19 @@ misc_graphics = {
|
||||
go = love.graphics.newImage("res/img/go.png"),
|
||||
select_mode = love.graphics.newImage("res/img/select_mode.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,19 +6,51 @@ function loadSave()
|
||||
end
|
||||
|
||||
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
|
||||
return {} -- new object
|
||||
end
|
||||
return save_data[1]
|
||||
end
|
||||
|
||||
function initConfig()
|
||||
if not config.das then config.das = 10 end
|
||||
if not config.arr then config.arr = 2 end
|
||||
if not config.dcd then config.dcd = 0 end
|
||||
if not config.sfx_volume then config.sfx_volume = 0.5 end
|
||||
if not config.bgm_volume then config.bgm_volume = 0.5 end
|
||||
|
||||
if config.fullscreen == nil then config.fullscreen = false end
|
||||
if config.secret == nil then config.secret = false end
|
||||
|
||||
if not config.gamesettings then config.gamesettings = {} end
|
||||
for _, option in ipairs(GameConfigScene.options) do
|
||||
if not config.gamesettings[option[1]] then
|
||||
config.gamesettings[option[1]] = 1
|
||||
end
|
||||
end
|
||||
|
||||
if not config.input then
|
||||
scene = InputConfigScene()
|
||||
else
|
||||
if config.current_mode then current_mode = config.current_mode end
|
||||
if config.current_ruleset then current_ruleset = config.current_ruleset end
|
||||
scene = TitleScene()
|
||||
end
|
||||
end
|
||||
|
||||
function saveConfig()
|
||||
binser.writeFile('config.sav', config)
|
||||
love.filesystem.write(
|
||||
'config.sav', binser.serialize(config)
|
||||
)
|
||||
end
|
||||
|
||||
function saveHighscores()
|
||||
binser.writeFile('highscores.sav', highscores)
|
||||
love.filesystem.write(
|
||||
'highscores.sav', binser.serialize(highscores)
|
||||
)
|
||||
end
|
||||
|
||||
127
load/sounds.lua
127
load/sounds.lua
@@ -1,59 +1,98 @@
|
||||
sounds = {
|
||||
sound_paths = {
|
||||
blocks = {
|
||||
I = love.audio.newSource("res/se/piece_i.wav", "static"),
|
||||
J = love.audio.newSource("res/se/piece_j.wav", "static"),
|
||||
L = love.audio.newSource("res/se/piece_l.wav", "static"),
|
||||
O = love.audio.newSource("res/se/piece_o.wav", "static"),
|
||||
S = love.audio.newSource("res/se/piece_s.wav", "static"),
|
||||
T = love.audio.newSource("res/se/piece_t.wav", "static"),
|
||||
Z = love.audio.newSource("res/se/piece_z.wav", "static")
|
||||
I = "res/se/piece_i.wav",
|
||||
J = "res/se/piece_j.wav",
|
||||
L = "res/se/piece_l.wav",
|
||||
O = "res/se/piece_o.wav",
|
||||
S = "res/se/piece_s.wav",
|
||||
T = "res/se/piece_t.wav",
|
||||
Z = "res/se/piece_z.wav"
|
||||
},
|
||||
move = love.audio.newSource("res/se/move.wav", "static"),
|
||||
bottom = love.audio.newSource("res/se/bottom.wav", "static"),
|
||||
cursor = love.audio.newSource("res/se/cursor.wav", "static"),
|
||||
cursor_lr = love.audio.newSource("res/se/cursor_lr.wav", "static"),
|
||||
main_decide = love.audio.newSource("res/se/main_decide.wav", "static"),
|
||||
mode_decide = love.audio.newSource("res/se/mode_decide.wav", "static"),
|
||||
lock = love.audio.newSource("res/se/lock.wav", "static"),
|
||||
hold = love.audio.newSource("res/se/hold.wav", "static"),
|
||||
erase = love.audio.newSource("res/se/erase.wav", "static"),
|
||||
fall = love.audio.newSource("res/se/fall.wav", "static"),
|
||||
ready = love.audio.newSource("res/se/ready.wav", "static"),
|
||||
go = love.audio.newSource("res/se/go.wav", "static"),
|
||||
irs = love.audio.newSource("res/se/irs.wav", "static"),
|
||||
ihs = love.audio.newSource("res/se/ihs.wav", "static"),
|
||||
move = "res/se/move.wav",
|
||||
rotate = "res/se/rotate.wav",
|
||||
kick = "res/se/kick.wav",
|
||||
bottom = "res/se/bottom.wav",
|
||||
cursor = "res/se/cursor.wav",
|
||||
cursor_lr = "res/se/cursor_lr.wav",
|
||||
main_decide = "res/se/main_decide.wav",
|
||||
mode_decide = "res/se/mode_decide.wav",
|
||||
lock = "res/se/lock.wav",
|
||||
hold = "res/se/hold.wav",
|
||||
erase = {
|
||||
single = "res/se/single.wav",
|
||||
double = "res/se/double.wav",
|
||||
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!
|
||||
welcome = love.audio.newSource("res/se/welcomeToCambridge.wav", "static"),
|
||||
welcome = "res/se/welcomeToCambridge.wav",
|
||||
}
|
||||
|
||||
function playSE(sound, subsound)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound]:isPlaying() then
|
||||
sounds[sound]:stop()
|
||||
sounds = {}
|
||||
-- Replace each sound effect string with its love audiosource counterpart, but only if it exists. This lets the game handle missing SFX.
|
||||
for k,v in pairs(sound_paths) do
|
||||
if(type(v) == "table") then
|
||||
-- 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
|
||||
sounds[sound]:play()
|
||||
else
|
||||
sounds[sound][subsound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
sounds[sound][subsound]:stop()
|
||||
if(love.filesystem.getInfo(sound_paths[k])) then
|
||||
-- this file exists
|
||||
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
|
||||
sounds[sound][subsound]:play()
|
||||
end
|
||||
end
|
||||
|
||||
function playSEOnce(sound, subsound)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(config.sfx_volume)
|
||||
if sounds[sound]:isPlaying() then
|
||||
return
|
||||
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
|
||||
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
|
||||
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
|
||||
1
load/version.lua
Normal file
1
load/version.lua
Normal file
@@ -0,0 +1 @@
|
||||
version = "v0.3.4"
|
||||
266
main.lua
266
main.lua
@@ -1,59 +1,59 @@
|
||||
function love.load()
|
||||
math.randomseed(os.time())
|
||||
highscores = {}
|
||||
love.graphics.setDefaultFilter("linear", "nearest")
|
||||
require "load.rpc"
|
||||
require "load.graphics"
|
||||
require "load.fonts"
|
||||
require "load.sounds"
|
||||
require "load.bgm"
|
||||
require "load.save"
|
||||
require "load.bigint"
|
||||
require 'load.cpml'
|
||||
require "load.version"
|
||||
loadSave()
|
||||
require "funcs"
|
||||
require "scene"
|
||||
|
||||
--config["side_next"] = false
|
||||
--config["reverse_rotate"] = true
|
||||
config["fullscreen"] = false
|
||||
--config["das_last_key"] = false
|
||||
--config["fullscreen"] = false
|
||||
|
||||
love.window.setMode(love.graphics.getWidth(), love.graphics.getHeight(), {resizable = true});
|
||||
|
||||
-- used for screenshots
|
||||
GLOBAL_CANVAS = love.graphics.newCanvas()
|
||||
|
||||
if not config.das then config.das = 10 end
|
||||
if not config.arr then config.arr = 2 end
|
||||
if not config.sfx_volume then config.sfx_volume = 0.5 end
|
||||
if not config.bgm_volume then config.bgm_volume = 0.5 end
|
||||
|
||||
if config.secret == nil then config.secret = false
|
||||
elseif config.secret == true then playSE("welcome") end
|
||||
-- aliasing to prevent people using math.random by accident
|
||||
math.random = love.math.random
|
||||
math.randomseed = love.math.setRandomSeed
|
||||
math.randomseed(os.time())
|
||||
|
||||
if not config.gamesettings then
|
||||
config.gamesettings = {}
|
||||
config["das_last_key"] = false
|
||||
else
|
||||
config["das_last_key"] = config.gamesettings.das_last_key == 2
|
||||
end
|
||||
for _, option in ipairs(GameConfigScene.options) do
|
||||
if not config.gamesettings[option[1]] then
|
||||
config.gamesettings[option[1]] = 1
|
||||
end
|
||||
end
|
||||
|
||||
if not config.input then
|
||||
scene = InputConfigScene()
|
||||
else
|
||||
if config.current_mode then current_mode = config.current_mode end
|
||||
if config.current_ruleset then current_ruleset = config.current_ruleset end
|
||||
scene = TitleScene()
|
||||
end
|
||||
-- init config
|
||||
initConfig()
|
||||
config.depth_3d = 100 -- TODO add a setting for this to the menu
|
||||
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
if config.secret then playSE("welcome") end
|
||||
|
||||
-- import custom modules
|
||||
initModules()
|
||||
end
|
||||
|
||||
function initModules()
|
||||
-- replays are not loaded here, but they are cleared
|
||||
replays = {}
|
||||
game_modes = {}
|
||||
mode_list = love.filesystem.getDirectoryItems("tetris/modes")
|
||||
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))
|
||||
end
|
||||
end
|
||||
rulesets = {}
|
||||
rule_list = love.filesystem.getDirectoryItems("tetris/rulesets")
|
||||
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))
|
||||
end
|
||||
end
|
||||
@@ -63,52 +63,15 @@ function love.load()
|
||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) end)
|
||||
table.sort(rulesets, function(a,b)
|
||||
return tostring(a.name):gsub("%d+",padnum) < tostring(b.name):gsub("%d+",padnum) 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()
|
||||
love.graphics.setCanvas{GLOBAL_CANVAS, depth = config.depth_3d > 0}
|
||||
love.graphics.clear(0, 0, 0, 1, false, config.depth_3d > 0)
|
||||
|
||||
love.graphics.push()
|
||||
|
||||
-- get offset matrix
|
||||
love.graphics.setDefaultFilter("linear", "nearest")
|
||||
local width = love.graphics.getWidth()
|
||||
local height = love.graphics.getHeight()
|
||||
local scale_factor = math.min(width / 640, height / 480)
|
||||
@@ -117,26 +80,52 @@ function love.draw()
|
||||
(height - scale_factor * 480) / 2
|
||||
)
|
||||
love.graphics.scale(scale_factor)
|
||||
|
||||
|
||||
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.setCanvas()
|
||||
love.graphics.setColor(1,1,1,1)
|
||||
love.graphics.draw(GLOBAL_CANVAS)
|
||||
end
|
||||
|
||||
function love.keypressed(key, scancode)
|
||||
-- global hotkeys
|
||||
if scancode == "f4" then
|
||||
if scancode == "f11" then
|
||||
config["fullscreen"] = not config["fullscreen"]
|
||||
saveConfig()
|
||||
love.window.setFullscreen(config["fullscreen"])
|
||||
elseif scancode == "f2" and scene.title ~= "Input Config" and scene.title ~= "Game" then
|
||||
scene = InputConfigScene()
|
||||
switchBGM(nil)
|
||||
loadSave()
|
||||
-- secret sound playing :eyes:
|
||||
elseif scancode == "f8" and scene.title == "Title" then
|
||||
config.secret = not config.secret
|
||||
saveConfig()
|
||||
scene.restart_message = true
|
||||
if config.secret then playSE("mode_decide")
|
||||
else playSE("erase") end
|
||||
else playSE("erase", "single") end
|
||||
-- f12 is reserved for saving screenshots
|
||||
elseif scancode == "f12" then
|
||||
local ss_name = os.date("ss/%Y-%m-%d_%H-%M-%S.png")
|
||||
local info = love.filesystem.getInfo("ss", "directory")
|
||||
if not info then
|
||||
love.filesystem.remove("ss")
|
||||
love.filesystem.createDirectory("ss")
|
||||
end
|
||||
print("Saving screenshot as "..love.filesystem.getSaveDirectory().."/"..ss_name)
|
||||
GLOBAL_CANVAS:newImageData():encode("png", ss_name)
|
||||
-- function keys are reserved
|
||||
elseif string.match(scancode, "^f[1-9]$") or string.match(scancode, "^f[1-9][0-9]+$") then
|
||||
return
|
||||
@@ -207,13 +196,13 @@ function love.joystickaxis(joystick, axis, value)
|
||||
config.input.joysticks[joystick:getName()].axes and
|
||||
config.input.joysticks[joystick:getName()].axes[axis]
|
||||
then
|
||||
if math.abs(value) >= 0.5 then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 0.5 and "positive" or "negative"]
|
||||
if math.abs(value) >= 1 then
|
||||
input_pressed = config.input.joysticks[joystick:getName()].axes[axis][value >= 1 and "positive" or "negative"]
|
||||
end
|
||||
positive_released = config.input.joysticks[joystick:getName()].axes[axis].positive
|
||||
negative_released = config.input.joysticks[joystick:getName()].axes[axis].negative
|
||||
end
|
||||
if math.abs(value) >= 0.5 then
|
||||
if math.abs(value) >= 1 then
|
||||
scene:onInputPress({input=input_pressed, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
else
|
||||
scene:onInputRelease({input=positive_released, type="joyaxis", name=joystick:getName(), axis=axis, value=value})
|
||||
@@ -221,6 +210,14 @@ function love.joystickaxis(joystick, axis, value)
|
||||
end
|
||||
end
|
||||
|
||||
local last_hat_direction = ""
|
||||
local directions = {
|
||||
["u"] = "up",
|
||||
["d"] = "down",
|
||||
["l"] = "left",
|
||||
["r"] = "right",
|
||||
}
|
||||
|
||||
function love.joystickhat(joystick, hat, direction)
|
||||
local input_pressed = nil
|
||||
local has_hat = false
|
||||
@@ -237,24 +234,127 @@ function love.joystickhat(joystick, hat, direction)
|
||||
has_hat = true
|
||||
end
|
||||
if input_pressed then
|
||||
scene:onInputPress({input=input_pressed, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
for i = 1, #direction do
|
||||
local char = direction:sub(i, i)
|
||||
local _, count = last_hat_direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputPress({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
for i = 1, #last_hat_direction do
|
||||
local char = last_hat_direction:sub(i, i)
|
||||
local _, count = direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
last_hat_direction = direction
|
||||
elseif has_hat then
|
||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
||||
scene:onInputRelease({input=config.input.joysticks[joystick:getName()].hats[hat][direction], type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
end
|
||||
last_hat_direction = ""
|
||||
elseif direction ~= "c" then
|
||||
scene:onInputPress({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
for i = 1, #direction do
|
||||
local char = direction:sub(i, i)
|
||||
local _, count = last_hat_direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputPress({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
for i = 1, #last_hat_direction do
|
||||
local char = last_hat_direction:sub(i, i)
|
||||
local _, count = direction:gsub(char, char)
|
||||
if count == 0 then
|
||||
scene:onInputRelease({input=directions[char], type="joyhat", name=joystick:getName(), hat=hat, direction=char})
|
||||
end
|
||||
end
|
||||
last_hat_direction = direction
|
||||
else
|
||||
for i, direction in ipairs{"d", "l", "ld", "lu", "r", "rd", "ru", "u"} do
|
||||
scene:onInputRelease({input=nil, type="joyhat", name=joystick:getName(), hat=hat, direction=direction})
|
||||
end
|
||||
last_hat_direction = ""
|
||||
end
|
||||
end
|
||||
|
||||
function love.focus(f)
|
||||
if f and (scene.title ~= "Game" or not scene.paused) then
|
||||
resumeBGM()
|
||||
else
|
||||
pauseBGM()
|
||||
function love.wheelmoved(x, y)
|
||||
scene:onInputPress({input=nil, type="wheel", x=x, y=y})
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
GLOBAL_CANVAS:release()
|
||||
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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
tar -a -c -f cambridge.zip libs/binser.lua libs/classic.lua libs/simple-slider.lua libs/discordRPC.lua load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||
tar -a -c -f cambridge.zip libs load res scene tetris conf.lua main.lua scene.lua funcs.lua
|
||||
rename cambridge.zip cambridge.love
|
||||
@@ -1 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
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/windows
|
||||
mkdir dist/win32
|
||||
cp cambridge.love dist/
|
||||
mkdir dist/other
|
||||
cat dist/windows/love.exe cambridge.love > dist/windows/cambridge.exe
|
||||
zip dist/cambridge-windows.zip dist/windows/* SOURCES.md LICENSE.md
|
||||
cat dist/win32/love.exe cambridge.love > dist/win32/cambridge.exe
|
||||
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
10
release.bat
@@ -5,17 +5,23 @@ mkdir dist\windows
|
||||
mkdir dist\windows\libs
|
||||
mkdir dist\win32
|
||||
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\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\win32\libs
|
||||
copy libs\discord-rpc.* dist\other\libs
|
||||
|
||||
copy SOURCES.md dist\windows
|
||||
copy LICENSE.md dist\windows
|
||||
copy SOURCES.md dist\win32
|
||||
copy LICENSE.md dist\win32
|
||||
copy SOURCES.md dist\other
|
||||
copy LICENSE.md dist\other
|
||||
|
||||
cd dist\windows
|
||||
tar -a -c -f ..\cambridge-windows.zip cambridge.exe *.dll libs *.md
|
||||
@@ -23,4 +29,8 @@ cd ..\..
|
||||
|
||||
cd dist\win32
|
||||
tar -a -c -f ..\cambridge-win32.zip cambridge.exe *.dll libs *.md
|
||||
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
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
BIN
res/backgrounds/title-night.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
BIN
res/backgrounds/title-no-icon.jpg
Normal file
BIN
res/backgrounds/title-no-icon.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 343 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user