Compare commits
521 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 | ||
|
|
f22424d671 | ||
|
|
dd6baf1fe6 | ||
|
|
11cf5a9d55 | ||
|
|
5642ed1326 | ||
|
|
c0888c484f | ||
|
|
3ef3b193fd | ||
|
|
0c2e3efd1a | ||
|
|
5076adf022 | ||
|
|
1a75d983dc | ||
|
|
5b8e9586bd | ||
|
|
7d7dd8c3c2 | ||
|
|
29afdcecfc | ||
|
|
8b09833ae6 | ||
|
|
64047eaf9c | ||
|
|
125488b4d9 | ||
|
|
1fdd091456 | ||
|
|
ced40297cc | ||
|
|
32f2a0b3e7 | ||
|
|
dd5347ad8d | ||
|
|
b732ebb213 | ||
|
|
84634d6933 | ||
|
|
0d13a9f236 | ||
|
|
45120bc9f7 | ||
|
|
57c7d9c4c3 | ||
|
|
9f52d8bf10 | ||
|
|
57bd6a8286 | ||
|
|
1a68cd8fce | ||
|
|
56baf46839 | ||
|
|
305d07e10a | ||
|
|
8d954cabc2 | ||
|
|
0281220ea0 | ||
|
|
aef5d88d3f | ||
|
|
3676f7697c | ||
|
|
acb0eb1a71 | ||
|
|
a89bf05cab | ||
|
|
8008315994 | ||
|
|
90f62cb7dd | ||
|
|
eaee5fc7f0 | ||
|
|
e3b038b5a7 | ||
|
|
083693496e | ||
|
|
ba576dfc77 | ||
|
|
e195ccd721 | ||
|
|
70f703eb2f | ||
|
|
dc4d4a8259 | ||
|
|
565510c7b2 | ||
|
|
c26a3f37de | ||
|
|
0c1ce2f717 | ||
|
|
f14ab2a328 | ||
|
|
042dbd220b | ||
|
|
548612123a | ||
|
|
f4675da0b0 | ||
|
|
511e9592bc | ||
|
|
5f3990ff58 | ||
|
|
50ff4adf27 | ||
|
|
87b88f4b42 | ||
|
|
130c2ea403 | ||
|
|
1ea304916e | ||
|
|
e26b094830 | ||
|
|
bcb44725bf | ||
|
|
2990844c52 | ||
|
|
c343014d6f | ||
|
|
605add7e94 | ||
|
|
d3b647ca71 | ||
|
|
1101aa467d | ||
|
|
ce27a7ed18 | ||
|
|
f31beffab8 | ||
|
|
2ff8fb5edc | ||
|
|
1bf8f91ef2 | ||
|
|
ba5f78d5f1 | ||
|
|
f7c4908062 | ||
|
|
3aa5bae7be | ||
|
|
40a2e78280 | ||
|
|
696da3fa3f | ||
|
|
4afe9f2bd4 | ||
|
|
1f686fb5d4 | ||
|
|
f4779c9847 | ||
|
|
06cbec4bc8 | ||
|
|
668564ffb0 | ||
|
|
e6edeea3d1 | ||
|
|
513cd6ba90 | ||
|
|
1beef8f157 | ||
|
|
d3b2b4c2d9 | ||
|
|
2b8b9d5084 | ||
|
|
2728780c45 | ||
|
|
ca592a3bcf | ||
|
|
b6f4158d70 | ||
|
|
e43f5c470a | ||
|
|
7bcdc517c0 | ||
|
|
1d30987f9a | ||
|
|
1dd46a11ef | ||
|
|
935c7aa14c | ||
|
|
aea115d953 | ||
|
|
3d5b33f41a | ||
|
|
29f07bb6ab | ||
|
|
6d326a142c | ||
|
|
b6f1072587 | ||
|
|
eef04ebf05 | ||
|
|
e24737a3b8 |
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
*.sav
|
||||
*.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
|
||||
|
||||
92
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, or want mod pack support, 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,11 +42,19 @@ 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
|
||||
|
||||
You can download the .love file in the latest release, and run it with:
|
||||
|
||||
love cambridge.love
|
||||
|
||||
#### Installing from source
|
||||
|
||||
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.
|
||||
|
||||
@@ -74,9 +66,7 @@ It should run automatically!
|
||||
|
||||
## Installing modpacks
|
||||
|
||||
Simply drag your mode, ruleset, and randomizer Lua files into their respective directory, and they should appear automatically.
|
||||
|
||||
Alternatively, 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
|
||||
-------
|
||||
@@ -88,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
|
||||
|
||||

|
||||
|
||||
116
SOURCES.md
@@ -8,7 +8,7 @@ Some of the assets are used without proper licenses. We aim to have fully licens
|
||||
Backgrounds
|
||||
-----------
|
||||
|
||||
1. Title: "Motus Glacies." Contributed by Daniel "Explo" McCarthy.
|
||||
1. Title: Original picrute found on the Wikipedia article for Cambridge
|
||||
|
||||
1. *Gameplay level 0: "Quantum foam." Alex Sukontsev. https://www.flickr.com/photos/control9/14957509814/
|
||||
2. *Gameplay level 1: No name. http://www.onekind.tv/univision-mqb/q5mqh5brlvuuj2nhdx7ch7eum183uu
|
||||
@@ -34,10 +34,18 @@ Backgrounds
|
||||
Backgrounds marked with a * are placeholders that will be replaced in later versions due to incompatible licenses. We are generally aiming for public domain background images, but will also accept backgrounds given proper licenses to be included within Cambridge.
|
||||
|
||||
|
||||
Sounds
|
||||
------
|
||||
|
||||
All piece sounds are (c) 2020 Damian Yerrick.
|
||||
Other sounds from:
|
||||
- NullpoMino
|
||||
- DTET, (c) 2003 Mihys.
|
||||
|
||||
Music
|
||||
-----
|
||||
|
||||
1. TGM3 credit roll music.
|
||||
1. Second Reality opening scene music (1993).
|
||||
2. The FitnessGram™ Pacer Test.
|
||||
|
||||
All background music is (currently) only unofficially included. In later releases they may be replaced with specifically licensed music as applicable.
|
||||
@@ -106,3 +114,107 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
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.
|
||||
|
||||
simple-slider (https://love2d.org/forums/viewtopic.php?t=80711)
|
||||
--------------------
|
||||
|
||||
Copyright (c) 2016 George Prosser
|
||||
|
||||
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.
|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
60
libs/cpml/LICENSE.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Licenses
|
||||
|
||||
CPML is Copyright (c) 2016 Colby Klein <shakesoda@gmail.com>.
|
||||
|
||||
CPML is Copyright (c) 2016 Landon Manning <lmanning17@gmail.com>.
|
||||
|
||||
Code in vec3.lua is derived from hump.vector. (c) 2010-2013 Matthias Richter. MIT.
|
||||
|
||||
Portions of mat4.lua are from LuaMatrix, (c) 2010 Michael Lutz. MIT.
|
||||
|
||||
Code in simplex.lua is (c) 2011 Stefan Gustavson. MIT.
|
||||
|
||||
Code in bound2.lua and bound3.lua are (c) 2018 Andi McClure. MIT.
|
||||
|
||||
Code in quat.lua is from Andrew Stacey and covered under the CC0 license.
|
||||
|
||||
Code in octree.lua is derived from UnityOctree. (c) 2014 Nition. BSD-2-Clause.
|
||||
|
||||
# The MIT License (MIT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
# The BSD License (BSD-2-Clause)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
17
libs/cpml/_private_precond.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Preconditions for cpml functions.
|
||||
local precond = {}
|
||||
|
||||
|
||||
function precond.typeof(t, expected, msg)
|
||||
if type(t) ~= expected then
|
||||
error(("%s: %s (<%s> expected)"):format(msg, type(t), expected), 3)
|
||||
end
|
||||
end
|
||||
|
||||
function precond.assert(cond, msg, ...)
|
||||
if not cond then
|
||||
error(msg:format(...), 3)
|
||||
end
|
||||
end
|
||||
|
||||
return precond
|
||||
16
libs/cpml/_private_utils.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Functions exported by utils.lua but needed by vec2 or vec3 (which utils.lua requires)
|
||||
|
||||
local private = {}
|
||||
local floor = math.floor
|
||||
local ceil = math.ceil
|
||||
|
||||
function private.round(value, precision)
|
||||
if precision then return private.round(value / precision) * precision end
|
||||
return value >= 0 and floor(value+0.5) or ceil(value-0.5)
|
||||
end
|
||||
|
||||
function private.is_nan(a)
|
||||
return a ~= a
|
||||
end
|
||||
|
||||
return private
|
||||
199
libs/cpml/bound2.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
--- A 2 component bounding box.
|
||||
-- @module bound2
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local vec2 = require(modules .. "vec2")
|
||||
|
||||
local bound2 = {}
|
||||
local bound2_mt = {}
|
||||
|
||||
-- Private constructor.
|
||||
local function new(min, max)
|
||||
return setmetatable({
|
||||
min=min, -- min: vec2, minimum value for each component
|
||||
max=max, -- max: vec2, maximum value for each component
|
||||
}, bound2_mt)
|
||||
end
|
||||
|
||||
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||
local status, ffi
|
||||
if type(jit) == "table" and jit.status() then
|
||||
status, ffi = pcall(require, "ffi")
|
||||
if status then
|
||||
ffi.cdef "typedef struct { cpml_vec2 min, max; } cpml_bound2;"
|
||||
new = ffi.typeof("cpml_bound2")
|
||||
end
|
||||
end
|
||||
|
||||
bound2.zero = new(vec2.zero, vec2.zero)
|
||||
|
||||
--- The public constructor.
|
||||
-- @param min Can be of two types: </br>
|
||||
-- vec2 min, minimum value for each component
|
||||
-- nil Create bound at single point 0,0
|
||||
-- @tparam vec2 max, maximum value for each component
|
||||
-- @treturn bound2 out
|
||||
function bound2.new(min, max)
|
||||
if min and max then
|
||||
return new(min:clone(), max:clone())
|
||||
elseif min or max then
|
||||
error("Unexpected nil argument to bound2.new")
|
||||
else
|
||||
return new(vec2.zero, vec2.zero)
|
||||
end
|
||||
end
|
||||
|
||||
--- Clone a bound.
|
||||
-- @tparam bound2 a bound to be cloned
|
||||
-- @treturn bound2 out
|
||||
function bound2.clone(a)
|
||||
return new(a.min, a.max)
|
||||
end
|
||||
|
||||
--- Construct a bound covering one or two points
|
||||
-- @tparam vec2 a Any vector
|
||||
-- @tparam vec2 b Any second vector (optional)
|
||||
-- @treturn vec2 Minimum bound containing the given points
|
||||
function bound2.at(a, b) -- "bounded by". b may be nil
|
||||
if b then
|
||||
return bound2.new(a,b):check()
|
||||
else
|
||||
return bound2.zero:with_center(a)
|
||||
end
|
||||
end
|
||||
|
||||
--- Extend bound to include point
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 point to include
|
||||
-- @treturn bound2 Bound covering current min, current max and new point
|
||||
function bound2.extend(a, center)
|
||||
return bound2.new(a.min:component_min(center), a.max:component_max(center))
|
||||
end
|
||||
|
||||
--- Extend bound to entirety of other bound
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam bound2 bound to cover
|
||||
-- @treturn bound2 Bound covering current min and max of each bound in the pair
|
||||
function bound2.extend_bound(a, b)
|
||||
return a:extend(b.min):extend(b.max)
|
||||
end
|
||||
|
||||
--- Get size of bounding box as a vector
|
||||
-- @tparam bound2 a bound
|
||||
-- @treturn vec2 Vector spanning min to max points
|
||||
function bound2.size(a)
|
||||
return a.max - a.min
|
||||
end
|
||||
|
||||
--- Resize bounding box from minimum corner
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 new size
|
||||
-- @treturn bound2 resized bound
|
||||
function bound2.with_size(a, size)
|
||||
return bound2.new(a.min, a.min + size)
|
||||
end
|
||||
|
||||
--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
|
||||
-- @tparam bound2 a bound
|
||||
-- @treturn vec2 Vector spanning center to max point
|
||||
function bound2.radius(a)
|
||||
return a:size()/2
|
||||
end
|
||||
|
||||
--- Get center of bounding box
|
||||
-- @tparam bound2 a bound
|
||||
-- @treturn bound2 Point in center of bound
|
||||
function bound2.center(a)
|
||||
return (a.min + a.max)/2
|
||||
end
|
||||
|
||||
--- Move bounding box to new center
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 new center
|
||||
-- @treturn bound2 Bound with same size as input but different center
|
||||
function bound2.with_center(a, center)
|
||||
return bound2.offset(a, center - a:center())
|
||||
end
|
||||
|
||||
--- Resize bounding box from center
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 new size
|
||||
-- @treturn bound2 resized bound
|
||||
function bound2.with_size_centered(a, size)
|
||||
local center = a:center()
|
||||
local rad = size/2
|
||||
return bound2.new(center - rad, center + rad)
|
||||
end
|
||||
|
||||
--- Convert possibly-invalid bounding box to valid one
|
||||
-- @tparam bound2 a bound
|
||||
-- @treturn bound2 bound with all components corrected for min-max property
|
||||
function bound2.check(a)
|
||||
if a.min.x > a.max.x or a.min.y > a.max.y then
|
||||
return bound2.new(vec2.component_min(a.min, a.max), vec2.component_max(a.min, a.max))
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
--- Shrink bounding box with fixed margin
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 a margin
|
||||
-- @treturn bound2 bound with margin subtracted from all edges. May not be valid, consider calling check()
|
||||
function bound2.inset(a, v)
|
||||
return bound2.new(a.min + v, a.max - v)
|
||||
end
|
||||
|
||||
--- Expand bounding box with fixed margin
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 a margin
|
||||
-- @treturn bound2 bound with margin added to all edges. May not be valid, consider calling check()
|
||||
function bound2.outset(a, v)
|
||||
return bound2.new(a.min - v, a.max + v)
|
||||
end
|
||||
|
||||
--- Offset bounding box
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 offset
|
||||
-- @treturn bound2 bound with same size, but position moved by offset
|
||||
function bound2.offset(a, v)
|
||||
return bound2.new(a.min + v, a.max + v)
|
||||
end
|
||||
|
||||
--- Test if point in bound
|
||||
-- @tparam bound2 a bound
|
||||
-- @tparam vec2 point to test
|
||||
-- @treturn boolean true if point in bounding box
|
||||
function bound2.contains(a, v)
|
||||
return a.min.x <= v.x and a.min.y <= v.y
|
||||
and a.max.x >= v.x and a.max.y >= v.y
|
||||
end
|
||||
|
||||
-- Round all components of all vectors to nearest int (or other precision).
|
||||
-- @tparam vec3 a bound to round.
|
||||
-- @tparam precision Digits after the decimal (round number if unspecified)
|
||||
-- @treturn vec3 Rounded bound
|
||||
function bound2.round(a, precision)
|
||||
return bound2.new(a.min:round(precision), a.max:round(precision))
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam bound2 a bound to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function bound2.to_string(a)
|
||||
return string.format("(%s-%s)", a.min, a.max)
|
||||
end
|
||||
|
||||
bound2_mt.__index = bound2
|
||||
bound2_mt.__tostring = bound2.to_string
|
||||
|
||||
function bound2_mt.__call(_, a, b)
|
||||
return bound2.new(a, b)
|
||||
end
|
||||
|
||||
if status then
|
||||
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||
ffi.metatype(new, bound2_mt)
|
||||
end, function() end)
|
||||
end
|
||||
|
||||
return setmetatable({}, bound2_mt)
|
||||
199
libs/cpml/bound3.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
--- A 3-component axis-aligned bounding box.
|
||||
-- @module bound3
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local vec3 = require(modules .. "vec3")
|
||||
|
||||
local bound3 = {}
|
||||
local bound3_mt = {}
|
||||
|
||||
-- Private constructor.
|
||||
local function new(min, max)
|
||||
return setmetatable({
|
||||
min=min, -- min: vec3, minimum value for each component
|
||||
max=max -- max: vec3, maximum value for each component
|
||||
}, bound3_mt)
|
||||
end
|
||||
|
||||
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||
local status, ffi
|
||||
if type(jit) == "table" and jit.status() then
|
||||
status, ffi = pcall(require, "ffi")
|
||||
if status then
|
||||
ffi.cdef "typedef struct { cpml_vec3 min, max; } cpml_bound3;"
|
||||
new = ffi.typeof("cpml_bound3")
|
||||
end
|
||||
end
|
||||
|
||||
bound3.zero = new(vec3.zero, vec3.zero)
|
||||
|
||||
--- The public constructor.
|
||||
-- @param min Can be of two types: </br>
|
||||
-- vec3 min, minimum value for each component
|
||||
-- nil Create bound at single point 0,0,0
|
||||
-- @tparam vec3 max, maximum value for each component
|
||||
-- @treturn bound3 out
|
||||
function bound3.new(min, max)
|
||||
if min and max then
|
||||
return new(min:clone(), max:clone())
|
||||
elseif min or max then
|
||||
error("Unexpected nil argument to bound3.new")
|
||||
else
|
||||
return new(vec3.zero, vec3.zero)
|
||||
end
|
||||
end
|
||||
|
||||
--- Clone a bound.
|
||||
-- @tparam bound3 a bound to be cloned
|
||||
-- @treturn bound3 out
|
||||
function bound3.clone(a)
|
||||
return new(a.min, a.max)
|
||||
end
|
||||
|
||||
--- Construct a bound covering one or two points
|
||||
-- @tparam vec3 a Any vector
|
||||
-- @tparam vec3 b Any second vector (optional)
|
||||
-- @treturn vec3 Minimum bound containing the given points
|
||||
function bound3.at(a, b) -- "bounded by". b may be nil
|
||||
if b then
|
||||
return bound3.new(a,b):check()
|
||||
else
|
||||
return bound3.zero:with_center(a)
|
||||
end
|
||||
end
|
||||
|
||||
--- Extend bound to include point
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 point to include
|
||||
-- @treturn bound3 Bound covering current min, current max and new point
|
||||
function bound3.extend(a, center)
|
||||
return bound3.new(a.min:component_min(center), a.max:component_max(center))
|
||||
end
|
||||
|
||||
--- Extend bound to entirety of other bound
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam bound3 bound to cover
|
||||
-- @treturn bound3 Bound covering current min and max of each bound in the pair
|
||||
function bound3.extend_bound(a, b)
|
||||
return a:extend(b.min):extend(b.max)
|
||||
end
|
||||
|
||||
--- Get size of bounding box as a vector
|
||||
-- @tparam bound3 a bound
|
||||
-- @treturn vec3 Vector spanning min to max points
|
||||
function bound3.size(a)
|
||||
return a.max - a.min
|
||||
end
|
||||
|
||||
--- Resize bounding box from minimum corner
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 new size
|
||||
-- @treturn bound3 resized bound
|
||||
function bound3.with_size(a, size)
|
||||
return bound3.new(a.min, a.min + size)
|
||||
end
|
||||
|
||||
--- Get half-size of bounding box as a vector. A more correct term for this is probably "apothem"
|
||||
-- @tparam bound3 a bound
|
||||
-- @treturn vec3 Vector spanning center to max point
|
||||
function bound3.radius(a)
|
||||
return a:size()/2
|
||||
end
|
||||
|
||||
--- Get center of bounding box
|
||||
-- @tparam bound3 a bound
|
||||
-- @treturn bound3 Point in center of bound
|
||||
function bound3.center(a)
|
||||
return (a.min + a.max)/2
|
||||
end
|
||||
|
||||
--- Move bounding box to new center
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 new center
|
||||
-- @treturn bound3 Bound with same size as input but different center
|
||||
function bound3.with_center(a, center)
|
||||
return bound3.offset(a, center - a:center())
|
||||
end
|
||||
|
||||
--- Resize bounding box from center
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 new size
|
||||
-- @treturn bound3 resized bound
|
||||
function bound3.with_size_centered(a, size)
|
||||
local center = a:center()
|
||||
local rad = size/2
|
||||
return bound3.new(center - rad, center + rad)
|
||||
end
|
||||
|
||||
--- Convert possibly-invalid bounding box to valid one
|
||||
-- @tparam bound3 a bound
|
||||
-- @treturn bound3 bound with all components corrected for min-max property
|
||||
function bound3.check(a)
|
||||
if a.min.x > a.max.x or a.min.y > a.max.y or a.min.z > a.max.z then
|
||||
return bound3.new(vec3.component_min(a.min, a.max), vec3.component_max(a.min, a.max))
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
--- Shrink bounding box with fixed margin
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 a margin
|
||||
-- @treturn bound3 bound with margin subtracted from all edges. May not be valid, consider calling check()
|
||||
function bound3.inset(a, v)
|
||||
return bound3.new(a.min + v, a.max - v)
|
||||
end
|
||||
|
||||
--- Expand bounding box with fixed margin
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 a margin
|
||||
-- @treturn bound3 bound with margin added to all edges. May not be valid, consider calling check()
|
||||
function bound3.outset(a, v)
|
||||
return bound3.new(a.min - v, a.max + v)
|
||||
end
|
||||
|
||||
--- Offset bounding box
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 offset
|
||||
-- @treturn bound3 bound with same size, but position moved by offset
|
||||
function bound3.offset(a, v)
|
||||
return bound3.new(a.min + v, a.max + v)
|
||||
end
|
||||
|
||||
--- Test if point in bound
|
||||
-- @tparam bound3 a bound
|
||||
-- @tparam vec3 point to test
|
||||
-- @treturn boolean true if point in bounding box
|
||||
function bound3.contains(a, v)
|
||||
return a.min.x <= v.x and a.min.y <= v.y and a.min.z <= v.z
|
||||
and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z
|
||||
end
|
||||
|
||||
-- Round all components of all vectors to nearest int (or other precision).
|
||||
-- @tparam vec3 a bound to round.
|
||||
-- @tparam precision Digits after the decimal (round number if unspecified)
|
||||
-- @treturn vec3 Rounded bound
|
||||
function bound3.round(a, precision)
|
||||
return bound3.new(a.min:round(precision), a.max:round(precision))
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam bound3 a bound to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function bound3.to_string(a)
|
||||
return string.format("(%s-%s)", a.min, a.max)
|
||||
end
|
||||
|
||||
bound3_mt.__index = bound3
|
||||
bound3_mt.__tostring = bound3.to_string
|
||||
|
||||
function bound3_mt.__call(_, a, b)
|
||||
return bound3.new(a, b)
|
||||
end
|
||||
|
||||
if status then
|
||||
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||
ffi.metatype(new, bound3_mt)
|
||||
end, function() end)
|
||||
end
|
||||
|
||||
return setmetatable({}, bound3_mt)
|
||||
557
libs/cpml/bvh.lua
Normal file
@@ -0,0 +1,557 @@
|
||||
-- https://github.com/benraziel/bvh-tree
|
||||
|
||||
--- BVH Tree
|
||||
-- @module bvh
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local intersect = require(modules .. "intersect")
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local EPSILON = 1e-6
|
||||
local BVH = {}
|
||||
local BVHNode = {}
|
||||
local Node
|
||||
|
||||
BVH.__index = BVH
|
||||
BVHNode.__index = BVHNode
|
||||
|
||||
local function new(triangles, maxTrianglesPerNode)
|
||||
local tree = setmetatable({}, BVH)
|
||||
local trianglesArray = {}
|
||||
|
||||
for _, triangle in ipairs(triangles) do
|
||||
local p1 = triangle[1]
|
||||
local p2 = triangle[2]
|
||||
local p3 = triangle[3]
|
||||
|
||||
table.insert(trianglesArray, p1.x or p1[1])
|
||||
table.insert(trianglesArray, p1.y or p1[2])
|
||||
table.insert(trianglesArray, p1.z or p1[3])
|
||||
|
||||
table.insert(trianglesArray, p2.x or p2[1])
|
||||
table.insert(trianglesArray, p2.y or p2[2])
|
||||
table.insert(trianglesArray, p2.z or p2[3])
|
||||
|
||||
table.insert(trianglesArray, p3.x or p3[1])
|
||||
table.insert(trianglesArray, p3.y or p3[2])
|
||||
table.insert(trianglesArray, p3.z or p3[3])
|
||||
end
|
||||
|
||||
tree._trianglesArray = trianglesArray
|
||||
tree._maxTrianglesPerNode = maxTrianglesPerNode or 10
|
||||
tree._bboxArray = tree.calcBoundingBoxes(trianglesArray)
|
||||
|
||||
-- clone a helper array
|
||||
tree._bboxHelper = {}
|
||||
for _, bbox in ipairs(tree._bboxArray) do
|
||||
table.insert(tree._bboxHelper, bbox)
|
||||
end
|
||||
|
||||
-- create the root node, add all the triangles to it
|
||||
local triangleCount = #triangles
|
||||
local extents = tree:calcExtents(1, triangleCount, EPSILON)
|
||||
tree._rootNode = Node(extents[1], extents[2], 1, triangleCount, 1)
|
||||
|
||||
tree._nodes_to_split = { tree._rootNode }
|
||||
while #tree._nodes_to_split > 0 do
|
||||
local node = table.remove(tree._nodes_to_split)
|
||||
tree:splitNode(node)
|
||||
end
|
||||
return tree
|
||||
end
|
||||
|
||||
function BVH:intersectAABB(aabb)
|
||||
local nodesToIntersect = { self._rootNode }
|
||||
local trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)
|
||||
local intersectingTriangles = {}
|
||||
|
||||
-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the box.
|
||||
-- note: these triangles may not intersect the box themselves
|
||||
while #nodesToIntersect > 0 do
|
||||
local node = table.remove(nodesToIntersect)
|
||||
|
||||
local node_aabb = {
|
||||
min = node._extentsMin,
|
||||
max = node._extentsMax
|
||||
}
|
||||
|
||||
if intersect.aabb_aabb(aabb, node_aabb) then
|
||||
if node._node0 then
|
||||
table.insert(nodesToIntersect, node._node0)
|
||||
end
|
||||
|
||||
if node._node1 then
|
||||
table.insert(nodesToIntersect, node._node1)
|
||||
end
|
||||
|
||||
for i=node._startIndex, node._endIndex do
|
||||
table.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- insert all node triangles, don't bother being more specific yet.
|
||||
local triangle = { vec3(), vec3(), vec3() }
|
||||
|
||||
for i=1, #trianglesInIntersectingNodes do
|
||||
local triIndex = trianglesInIntersectingNodes[i]
|
||||
|
||||
-- print(triIndex, #self._trianglesArray)
|
||||
triangle[1].x = self._trianglesArray[1+(triIndex-1)*9]
|
||||
triangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]
|
||||
triangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]
|
||||
triangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]
|
||||
triangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]
|
||||
triangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]
|
||||
triangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]
|
||||
triangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]
|
||||
triangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]
|
||||
|
||||
table.insert(intersectingTriangles, {
|
||||
triangle = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },
|
||||
triangleIndex = triIndex
|
||||
})
|
||||
end
|
||||
|
||||
return intersectingTriangles
|
||||
end
|
||||
|
||||
function BVH:intersectRay(rayOrigin, rayDirection, backfaceCulling)
|
||||
local nodesToIntersect = { self._rootNode }
|
||||
local trianglesInIntersectingNodes = {} -- a list of nodes that intersect the ray (according to their bounding box)
|
||||
local intersectingTriangles = {}
|
||||
|
||||
local invRayDirection = vec3(
|
||||
1 / rayDirection.x,
|
||||
1 / rayDirection.y,
|
||||
1 / rayDirection.z
|
||||
)
|
||||
|
||||
-- go over the BVH tree, and extract the list of triangles that lie in nodes that intersect the ray.
|
||||
-- note: these triangles may not intersect the ray themselves
|
||||
while #nodesToIntersect > 0 do
|
||||
local node = table.remove(nodesToIntersect)
|
||||
|
||||
if BVH.intersectNodeBox(rayOrigin, invRayDirection, node) then
|
||||
if node._node0 then
|
||||
table.insert(nodesToIntersect, node._node0)
|
||||
end
|
||||
|
||||
if node._node1 then
|
||||
table.insert(nodesToIntersect, node._node1)
|
||||
end
|
||||
|
||||
for i=node._startIndex, node._endIndex do
|
||||
table.insert(trianglesInIntersectingNodes, self._bboxArray[1+(i-1)*7])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- go over the list of candidate triangles, and check each of them using ray triangle intersection
|
||||
local triangle = { vec3(), vec3(), vec3() }
|
||||
local ray = {
|
||||
position = vec3(rayOrigin.x, rayOrigin.y, rayOrigin.z),
|
||||
direction = vec3(rayDirection.x, rayDirection.y, rayDirection.z)
|
||||
}
|
||||
|
||||
for i=1, #trianglesInIntersectingNodes do
|
||||
local triIndex = trianglesInIntersectingNodes[i]
|
||||
|
||||
-- print(triIndex, #self._trianglesArray)
|
||||
triangle[1].x = self._trianglesArray[1+(triIndex-1)*9]
|
||||
triangle[1].y = self._trianglesArray[1+(triIndex-1)*9+1]
|
||||
triangle[1].z = self._trianglesArray[1+(triIndex-1)*9+2]
|
||||
triangle[2].x = self._trianglesArray[1+(triIndex-1)*9+3]
|
||||
triangle[2].y = self._trianglesArray[1+(triIndex-1)*9+4]
|
||||
triangle[2].z = self._trianglesArray[1+(triIndex-1)*9+5]
|
||||
triangle[3].x = self._trianglesArray[1+(triIndex-1)*9+6]
|
||||
triangle[3].y = self._trianglesArray[1+(triIndex-1)*9+7]
|
||||
triangle[3].z = self._trianglesArray[1+(triIndex-1)*9+8]
|
||||
|
||||
local intersectionPoint, intersectionDistance = intersect.ray_triangle(ray, triangle, backfaceCulling)
|
||||
|
||||
if intersectionPoint then
|
||||
table.insert(intersectingTriangles, {
|
||||
triangle = { triangle[1]:clone(), triangle[2]:clone(), triangle[3]:clone() },
|
||||
triangleIndex = triIndex,
|
||||
intersectionPoint = intersectionPoint,
|
||||
intersectionDistance = intersectionDistance
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return intersectingTriangles
|
||||
end
|
||||
|
||||
function BVH.calcBoundingBoxes(trianglesArray)
|
||||
local p1x, p1y, p1z
|
||||
local p2x, p2y, p2z
|
||||
local p3x, p3y, p3z
|
||||
local minX, minY, minZ
|
||||
local maxX, maxY, maxZ
|
||||
|
||||
local bboxArray = {}
|
||||
|
||||
for i=1, #trianglesArray / 9 do
|
||||
p1x = trianglesArray[1+(i-1)*9]
|
||||
p1y = trianglesArray[1+(i-1)*9+1]
|
||||
p1z = trianglesArray[1+(i-1)*9+2]
|
||||
p2x = trianglesArray[1+(i-1)*9+3]
|
||||
p2y = trianglesArray[1+(i-1)*9+4]
|
||||
p2z = trianglesArray[1+(i-1)*9+5]
|
||||
p3x = trianglesArray[1+(i-1)*9+6]
|
||||
p3y = trianglesArray[1+(i-1)*9+7]
|
||||
p3z = trianglesArray[1+(i-1)*9+8]
|
||||
|
||||
minX = math.min(p1x, p2x, p3x)
|
||||
minY = math.min(p1y, p2y, p3y)
|
||||
minZ = math.min(p1z, p2z, p3z)
|
||||
maxX = math.max(p1x, p2x, p3x)
|
||||
maxY = math.max(p1y, p2y, p3y)
|
||||
maxZ = math.max(p1z, p2z, p3z)
|
||||
|
||||
BVH.setBox(bboxArray, i, i, minX, minY, minZ, maxX, maxY, maxZ)
|
||||
end
|
||||
|
||||
return bboxArray
|
||||
end
|
||||
|
||||
function BVH:calcExtents(startIndex, endIndex, expandBy)
|
||||
expandBy = expandBy or 0
|
||||
|
||||
if startIndex > endIndex then
|
||||
return { vec3(), vec3() }
|
||||
end
|
||||
|
||||
local minX = math.huge
|
||||
local minY = math.huge
|
||||
local minZ = math.huge
|
||||
local maxX = -math.huge
|
||||
local maxY = -math.huge
|
||||
local maxZ = -math.huge
|
||||
|
||||
for i=startIndex, endIndex do
|
||||
minX = math.min(self._bboxArray[1+(i-1)*7+1], minX)
|
||||
minY = math.min(self._bboxArray[1+(i-1)*7+2], minY)
|
||||
minZ = math.min(self._bboxArray[1+(i-1)*7+3], minZ)
|
||||
maxX = math.max(self._bboxArray[1+(i-1)*7+4], maxX)
|
||||
maxY = math.max(self._bboxArray[1+(i-1)*7+5], maxY)
|
||||
maxZ = math.max(self._bboxArray[1+(i-1)*7+6], maxZ)
|
||||
end
|
||||
|
||||
return {
|
||||
vec3(minX - expandBy, minY - expandBy, minZ - expandBy),
|
||||
vec3(maxX + expandBy, maxY + expandBy, maxZ + expandBy)
|
||||
}
|
||||
end
|
||||
|
||||
function BVH:splitNode(node)
|
||||
local num_elements = node:elementCount()
|
||||
if (num_elements <= self._maxTrianglesPerNode) or (num_elements <= 0) then
|
||||
return
|
||||
end
|
||||
|
||||
local startIndex = node._startIndex
|
||||
local endIndex = node._endIndex
|
||||
|
||||
local leftNode = { {},{},{} }
|
||||
local rightNode = { {},{},{} }
|
||||
local extentCenters = { node:centerX(), node:centerY(), node:centerZ() }
|
||||
|
||||
local extentsLength = {
|
||||
node._extentsMax.x - node._extentsMin.x,
|
||||
node._extentsMax.y - node._extentsMin.y,
|
||||
node._extentsMax.z - node._extentsMin.z
|
||||
}
|
||||
|
||||
local objectCenter = {}
|
||||
for i=startIndex, endIndex do
|
||||
objectCenter[1] = (self._bboxArray[1+(i-1)*7+1] + self._bboxArray[1+(i-1)*7+4]) * 0.5 -- center = (min + max) / 2
|
||||
objectCenter[2] = (self._bboxArray[1+(i-1)*7+2] + self._bboxArray[1+(i-1)*7+5]) * 0.5 -- center = (min + max) / 2
|
||||
objectCenter[3] = (self._bboxArray[1+(i-1)*7+3] + self._bboxArray[1+(i-1)*7+6]) * 0.5 -- center = (min + max) / 2
|
||||
|
||||
for j=1, 3 do
|
||||
if objectCenter[j] < extentCenters[j] then
|
||||
table.insert(leftNode[j], i)
|
||||
else
|
||||
table.insert(rightNode[j], i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check if we couldn't split the node by any of the axes (x, y or z). halt
|
||||
-- here, dont try to split any more (cause it will always fail, and we'll
|
||||
-- enter an infinite loop
|
||||
local splitFailed = {
|
||||
#leftNode[1] == 0 or #rightNode[1] == 0,
|
||||
#leftNode[2] == 0 or #rightNode[2] == 0,
|
||||
#leftNode[3] == 0 or #rightNode[3] == 0
|
||||
}
|
||||
|
||||
if splitFailed[1] and splitFailed[2] and splitFailed[3] then
|
||||
return
|
||||
end
|
||||
|
||||
-- choose the longest split axis. if we can't split by it, choose next best one.
|
||||
local splitOrder = { 1, 2, 3 }
|
||||
table.sort(splitOrder, function(a, b)
|
||||
return extentsLength[a] > extentsLength[b]
|
||||
end)
|
||||
|
||||
local leftElements
|
||||
local rightElements
|
||||
|
||||
for i=1, 3 do
|
||||
local candidateIndex = splitOrder[i]
|
||||
if not splitFailed[candidateIndex] then
|
||||
leftElements = leftNode[candidateIndex]
|
||||
rightElements = rightNode[candidateIndex]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- sort the elements in range (startIndex, endIndex) according to which node they should be at
|
||||
local node0Start = startIndex
|
||||
local node1Start = node0Start + #leftElements
|
||||
local node0End = node1Start - 1
|
||||
local node1End = endIndex
|
||||
local currElement
|
||||
|
||||
local helperPos = node._startIndex
|
||||
local concatenatedElements = {}
|
||||
|
||||
for _, element in ipairs(leftElements) do
|
||||
table.insert(concatenatedElements, element)
|
||||
end
|
||||
|
||||
for _, element in ipairs(rightElements) do
|
||||
table.insert(concatenatedElements, element)
|
||||
end
|
||||
|
||||
-- print(#leftElements, #rightElements, #concatenatedElements)
|
||||
|
||||
for i=1, #concatenatedElements do
|
||||
currElement = concatenatedElements[i]
|
||||
BVH.copyBox(self._bboxArray, currElement, self._bboxHelper, helperPos)
|
||||
helperPos = helperPos + 1
|
||||
end
|
||||
|
||||
-- copy results back to main array
|
||||
for i=1+(node._startIndex-1)*7, node._endIndex*7 do
|
||||
self._bboxArray[i] = self._bboxHelper[i]
|
||||
end
|
||||
|
||||
-- create 2 new nodes for the node we just split, and add links to them from the parent node
|
||||
local node0Extents = self:calcExtents(node0Start, node0End, EPSILON)
|
||||
local node1Extents = self:calcExtents(node1Start, node1End, EPSILON)
|
||||
|
||||
local node0 = Node(node0Extents[1], node0Extents[2], node0Start, node0End, node._level + 1)
|
||||
local node1 = Node(node1Extents[1], node1Extents[2], node1Start, node1End, node._level + 1)
|
||||
|
||||
node._node0 = node0
|
||||
node._node1 = node1
|
||||
node:clearShapes()
|
||||
|
||||
-- add new nodes to the split queue
|
||||
table.insert(self._nodes_to_split, node0)
|
||||
table.insert(self._nodes_to_split, node1)
|
||||
end
|
||||
|
||||
function BVH._calcTValues(minVal, maxVal, rayOriginCoord, invdir)
|
||||
local res = { min=0, max=0 }
|
||||
|
||||
if invdir >= 0 then
|
||||
res.min = ( minVal - rayOriginCoord ) * invdir
|
||||
res.max = ( maxVal - rayOriginCoord ) * invdir
|
||||
else
|
||||
res.min = ( maxVal - rayOriginCoord ) * invdir
|
||||
res.max = ( minVal - rayOriginCoord ) * invdir
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
function BVH.intersectNodeBox(rayOrigin, invRayDirection, node)
|
||||
local t = BVH._calcTValues(node._extentsMin.x, node._extentsMax.x, rayOrigin.x, invRayDirection.x)
|
||||
local ty = BVH._calcTValues(node._extentsMin.y, node._extentsMax.y, rayOrigin.y, invRayDirection.y)
|
||||
|
||||
if t.min > ty.max or ty.min > t.max then
|
||||
return false
|
||||
end
|
||||
|
||||
-- These lines also handle the case where tmin or tmax is NaN
|
||||
-- (result of 0 * Infinity). x !== x returns true if x is NaN
|
||||
if ty.min > t.min or t.min ~= t.min then
|
||||
t.min = ty.min
|
||||
end
|
||||
|
||||
if ty.max < t.max or t.max ~= t.max then
|
||||
t.max = ty.max
|
||||
end
|
||||
|
||||
local tz = BVH._calcTValues(node._extentsMin.z, node._extentsMax.z, rayOrigin.z, invRayDirection.z)
|
||||
|
||||
if t.min > tz.max or tz.min > t.max then
|
||||
return false
|
||||
end
|
||||
|
||||
if tz.min > t.min or t.min ~= t.min then
|
||||
t.min = tz.min
|
||||
end
|
||||
|
||||
if tz.max < t.max or t.max ~= t.max then
|
||||
t.max = tz.max
|
||||
end
|
||||
|
||||
--return point closest to the ray (positive side)
|
||||
if t.max < 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function BVH.setBox(bboxArray, pos, triangleId, minX, minY, minZ, maxX, maxY, maxZ)
|
||||
bboxArray[1+(pos-1)*7] = triangleId
|
||||
bboxArray[1+(pos-1)*7+1] = minX
|
||||
bboxArray[1+(pos-1)*7+2] = minY
|
||||
bboxArray[1+(pos-1)*7+3] = minZ
|
||||
bboxArray[1+(pos-1)*7+4] = maxX
|
||||
bboxArray[1+(pos-1)*7+5] = maxY
|
||||
bboxArray[1+(pos-1)*7+6] = maxZ
|
||||
end
|
||||
|
||||
function BVH.copyBox(sourceArray, sourcePos, destArray, destPos)
|
||||
destArray[1+(destPos-1)*7] = sourceArray[1+(sourcePos-1)*7]
|
||||
destArray[1+(destPos-1)*7+1] = sourceArray[1+(sourcePos-1)*7+1]
|
||||
destArray[1+(destPos-1)*7+2] = sourceArray[1+(sourcePos-1)*7+2]
|
||||
destArray[1+(destPos-1)*7+3] = sourceArray[1+(sourcePos-1)*7+3]
|
||||
destArray[1+(destPos-1)*7+4] = sourceArray[1+(sourcePos-1)*7+4]
|
||||
destArray[1+(destPos-1)*7+5] = sourceArray[1+(sourcePos-1)*7+5]
|
||||
destArray[1+(destPos-1)*7+6] = sourceArray[1+(sourcePos-1)*7+6]
|
||||
end
|
||||
|
||||
function BVH.getBox(bboxArray, pos, outputBox)
|
||||
outputBox.triangleId = bboxArray[1+(pos-1)*7]
|
||||
outputBox.minX = bboxArray[1+(pos-1)*7+1]
|
||||
outputBox.minY = bboxArray[1+(pos-1)*7+2]
|
||||
outputBox.minZ = bboxArray[1+(pos-1)*7+3]
|
||||
outputBox.maxX = bboxArray[1+(pos-1)*7+4]
|
||||
outputBox.maxY = bboxArray[1+(pos-1)*7+5]
|
||||
outputBox.maxZ = bboxArray[1+(pos-1)*7+6]
|
||||
end
|
||||
|
||||
local function new_node(extentsMin, extentsMax, startIndex, endIndex, level)
|
||||
return setmetatable({
|
||||
_extentsMin = extentsMin,
|
||||
_extentsMax = extentsMax,
|
||||
_startIndex = startIndex,
|
||||
_endIndex = endIndex,
|
||||
_level = level
|
||||
--_node0 = nil
|
||||
--_node1 = nil
|
||||
}, BVHNode)
|
||||
end
|
||||
|
||||
function BVHNode:elementCount()
|
||||
return (self._endIndex + 1) - self._startIndex
|
||||
end
|
||||
|
||||
function BVHNode:centerX()
|
||||
return (self._extentsMin.x + self._extentsMax.x) * 0.5
|
||||
end
|
||||
|
||||
function BVHNode:centerY()
|
||||
return (self._extentsMin.y + self._extentsMax.y) * 0.5
|
||||
end
|
||||
|
||||
function BVHNode:centerZ()
|
||||
return (self._extentsMin.z + self._extentsMax.z) * 0.5
|
||||
end
|
||||
|
||||
function BVHNode:clearShapes()
|
||||
self._startIndex = 0
|
||||
self._endIndex = -1
|
||||
end
|
||||
|
||||
function BVHNode.ngSphereRadius(extentsMin, extentsMax)
|
||||
local centerX = (extentsMin.x + extentsMax.x) * 0.5
|
||||
local centerY = (extentsMin.y + extentsMax.y) * 0.5
|
||||
local centerZ = (extentsMin.z + extentsMax.z) * 0.5
|
||||
|
||||
local extentsMinDistSqr =
|
||||
(centerX - extentsMin.x) * (centerX - extentsMin.x) +
|
||||
(centerY - extentsMin.y) * (centerY - extentsMin.y) +
|
||||
(centerZ - extentsMin.z) * (centerZ - extentsMin.z)
|
||||
|
||||
local extentsMaxDistSqr =
|
||||
(centerX - extentsMax.x) * (centerX - extentsMax.x) +
|
||||
(centerY - extentsMax.y) * (centerY - extentsMax.y) +
|
||||
(centerZ - extentsMax.z) * (centerZ - extentsMax.z)
|
||||
|
||||
return math.sqrt(math.max(extentsMinDistSqr, extentsMaxDistSqr))
|
||||
end
|
||||
|
||||
--[[
|
||||
|
||||
--- Draws node boundaries visually for debugging.
|
||||
-- @param cube Cube model to draw
|
||||
-- @param depth Used for recurcive calls to self method
|
||||
function OctreeNode:draw_bounds(cube, depth)
|
||||
depth = depth or 0
|
||||
local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically
|
||||
|
||||
love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
|
||||
local m = mat4()
|
||||
:translate(self.center)
|
||||
:scale(vec3(self.adjLength, self.adjLength, self.adjLength))
|
||||
|
||||
love.graphics.updateMatrix("transform", m)
|
||||
love.graphics.setWireframe(true)
|
||||
love.graphics.draw(cube)
|
||||
love.graphics.setWireframe(false)
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
child:draw_bounds(cube, depth + 1)
|
||||
end
|
||||
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
end
|
||||
|
||||
--- Draws the bounds of all objects in the tree visually for debugging.
|
||||
-- @param cube Cube model to draw
|
||||
-- @param filter a function returning true or false to determine visibility.
|
||||
function OctreeNode:draw_objects(cube, filter)
|
||||
local tint = self.baseLength / 20
|
||||
love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)
|
||||
|
||||
for _, object in ipairs(self.objects) do
|
||||
if filter and filter(object.data) or not filter then
|
||||
local m = mat4()
|
||||
:translate(object.bounds.center)
|
||||
:scale(object.bounds.size)
|
||||
|
||||
love.graphics.updateMatrix("transform", m)
|
||||
love.graphics.draw(cube)
|
||||
end
|
||||
end
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
child:draw_objects(cube, filter)
|
||||
end
|
||||
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
end
|
||||
|
||||
--]]
|
||||
|
||||
Node = setmetatable({
|
||||
new = new_node
|
||||
}, {
|
||||
__call = function(_, ...) return new_node(...) end
|
||||
})
|
||||
|
||||
return setmetatable({
|
||||
new = new
|
||||
}, {
|
||||
__call = function(_, ...) return new(...) end
|
||||
})
|
||||
400
libs/cpml/color.lua
Normal file
@@ -0,0 +1,400 @@
|
||||
--- Color utilities
|
||||
-- @module color
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local constants = require(modules .. "constants")
|
||||
local utils = require(modules .. "utils")
|
||||
local precond = require(modules .. "_private_precond")
|
||||
local color = {}
|
||||
local color_mt = {}
|
||||
|
||||
local function new(r, g, b, a)
|
||||
local c = { r, g, b, a }
|
||||
c._c = c
|
||||
return setmetatable(c, color_mt)
|
||||
end
|
||||
|
||||
-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)
|
||||
-- hsv_to_color(hsv)
|
||||
-- Converts a set of HSV values to a color. hsv is a table.
|
||||
-- See also: hsv(h, s, v)
|
||||
local function hsv_to_color(hsv)
|
||||
local i
|
||||
local f, q, p, t
|
||||
local h, s, v
|
||||
local a = hsv[4] or 1
|
||||
s = hsv[2]
|
||||
v = hsv[3]
|
||||
|
||||
if s == 0 then
|
||||
return new(v, v, v, a)
|
||||
end
|
||||
|
||||
h = hsv[1] * 6 -- sector 0 to 5
|
||||
|
||||
i = math.floor(h)
|
||||
f = h - i -- factorial part of h
|
||||
p = v * (1-s)
|
||||
q = v * (1-s*f)
|
||||
t = v * (1-s*(1-f))
|
||||
|
||||
if i == 0 then return new(v, t, p, a)
|
||||
elseif i == 1 then return new(q, v, p, a)
|
||||
elseif i == 2 then return new(p, v, t, a)
|
||||
elseif i == 3 then return new(p, q, v, a)
|
||||
elseif i == 4 then return new(t, p, v, a)
|
||||
else return new(v, p, q, a)
|
||||
end
|
||||
end
|
||||
|
||||
-- color_to_hsv(c)
|
||||
-- Takes in a normal color and returns a table with the HSV values.
|
||||
local function color_to_hsv(c)
|
||||
local r = c[1]
|
||||
local g = c[2]
|
||||
local b = c[3]
|
||||
local a = c[4] or 1
|
||||
local h, s, v
|
||||
|
||||
local min = math.min(r, g, b)
|
||||
local max = math.max(r, g, b)
|
||||
v = max
|
||||
|
||||
local delta = max - min
|
||||
|
||||
-- black, nothing else is really possible here.
|
||||
if min == 0 and max == 0 then
|
||||
return { 0, 0, 0, a }
|
||||
end
|
||||
|
||||
if max ~= 0 then
|
||||
s = delta / max
|
||||
else
|
||||
-- r = g = b = 0 s = 0, v is undefined
|
||||
s = 0
|
||||
h = -1
|
||||
return { h, s, v, 1 }
|
||||
end
|
||||
|
||||
-- Prevent division by zero.
|
||||
if delta == 0 then
|
||||
delta = constants.DBL_EPSILON
|
||||
end
|
||||
|
||||
if r == max then
|
||||
h = ( g - b ) / delta -- yellow/magenta
|
||||
elseif g == max then
|
||||
h = 2 + ( b - r ) / delta -- cyan/yellow
|
||||
else
|
||||
h = 4 + ( r - g ) / delta -- magenta/cyan
|
||||
end
|
||||
|
||||
h = h / 6 -- normalize from segment 0..5
|
||||
|
||||
if h < 0 then
|
||||
h = h + 1
|
||||
end
|
||||
|
||||
return { h, s, v, a }
|
||||
end
|
||||
|
||||
--- The public constructor.
|
||||
-- @param x Can be of three types: </br>
|
||||
-- number red component 0-1
|
||||
-- table {r, g, b, a}
|
||||
-- nil for {0,0,0,0}
|
||||
-- @tparam number g Green component 0-1
|
||||
-- @tparam number b Blue component 0-1
|
||||
-- @tparam number a Alpha component 0-1
|
||||
-- @treturn color out
|
||||
function color.new(r, g, b, a)
|
||||
-- number, number, number, number
|
||||
if r and g and b and a then
|
||||
precond.typeof(r, "number", "new: Wrong argument type for r")
|
||||
precond.typeof(g, "number", "new: Wrong argument type for g")
|
||||
precond.typeof(b, "number", "new: Wrong argument type for b")
|
||||
precond.typeof(a, "number", "new: Wrong argument type for a")
|
||||
|
||||
return new(r, g, b, a)
|
||||
|
||||
-- {r, g, b, a}
|
||||
elseif type(r) == "table" then
|
||||
local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
|
||||
precond.typeof(rr, "number", "new: Wrong argument type for r")
|
||||
precond.typeof(gg, "number", "new: Wrong argument type for g")
|
||||
precond.typeof(bb, "number", "new: Wrong argument type for b")
|
||||
precond.typeof(aa, "number", "new: Wrong argument type for a")
|
||||
|
||||
return new(rr, gg, bb, aa)
|
||||
end
|
||||
|
||||
return new(0, 0, 0, 0)
|
||||
end
|
||||
|
||||
--- Convert hue,saturation,value table to color object.
|
||||
-- @tparam table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
|
||||
-- @treturn color out
|
||||
color.hsv_to_color_table = hsv_to_color
|
||||
|
||||
--- Convert color to hue,saturation,value table
|
||||
-- @tparam color in
|
||||
-- @treturn table hsva {hue 0-1, saturation 0-1, value 0-1, alpha 0-1}
|
||||
color.color_to_hsv_table = color_to_hsv
|
||||
|
||||
--- Convert hue,saturation,value to color object.
|
||||
-- @tparam number h hue 0-1
|
||||
-- @tparam number s saturation 0-1
|
||||
-- @tparam number v value 0-1
|
||||
-- @treturn color out
|
||||
function color.from_hsv(h, s, v)
|
||||
return hsv_to_color { h, s, v }
|
||||
end
|
||||
|
||||
--- Convert hue,saturation,value to color object.
|
||||
-- @tparam number h hue 0-1
|
||||
-- @tparam number s saturation 0-1
|
||||
-- @tparam number v value 0-1
|
||||
-- @tparam number a alpha 0-1
|
||||
-- @treturn color out
|
||||
function color.from_hsva(h, s, v, a)
|
||||
return hsv_to_color { h, s, v, a }
|
||||
end
|
||||
|
||||
--- Invert a color.
|
||||
-- @tparam color to invert
|
||||
-- @treturn color out
|
||||
function color.invert(c)
|
||||
return new(1 - c[1], 1 - c[2], 1 - c[3], c[4])
|
||||
end
|
||||
|
||||
--- Lighten a color by a component-wise fixed amount (alpha unchanged)
|
||||
-- @tparam color to lighten
|
||||
-- @tparam number amount to increase each component by, 0-1 scale
|
||||
-- @treturn color out
|
||||
function color.lighten(c, v)
|
||||
return new(
|
||||
utils.clamp(c[1] + v, 0, 1),
|
||||
utils.clamp(c[2] + v, 0, 1),
|
||||
utils.clamp(c[3] + v, 0, 1),
|
||||
c[4]
|
||||
)
|
||||
end
|
||||
|
||||
--- Interpolate between two colors.
|
||||
-- @tparam color at start
|
||||
-- @tparam color at end
|
||||
-- @tparam number s in 0-1 progress between the two colors
|
||||
-- @treturn color out
|
||||
function color.lerp(a, b, s)
|
||||
return a + s * (b - a)
|
||||
end
|
||||
|
||||
--- Unpack a color into individual components in 0-1.
|
||||
-- @tparam color to unpack
|
||||
-- @treturn number r in 0-1
|
||||
-- @treturn number g in 0-1
|
||||
-- @treturn number b in 0-1
|
||||
-- @treturn number a in 0-1
|
||||
function color.unpack(c)
|
||||
return c[1], c[2], c[3], c[4]
|
||||
end
|
||||
|
||||
--- Unpack a color into individual components in 0-255.
|
||||
-- @tparam color to unpack
|
||||
-- @treturn number r in 0-255
|
||||
-- @treturn number g in 0-255
|
||||
-- @treturn number b in 0-255
|
||||
-- @treturn number a in 0-255
|
||||
function color.as_255(c)
|
||||
return c[1] * 255, c[2] * 255, c[3] * 255, c[4] * 255
|
||||
end
|
||||
|
||||
--- Darken a color by a component-wise fixed amount (alpha unchanged)
|
||||
-- @tparam color to darken
|
||||
-- @tparam number amount to decrease each component by, 0-1 scale
|
||||
-- @treturn color out
|
||||
function color.darken(c, v)
|
||||
return new(
|
||||
utils.clamp(c[1] - v, 0, 1),
|
||||
utils.clamp(c[2] - v, 0, 1),
|
||||
utils.clamp(c[3] - v, 0, 1),
|
||||
c[4]
|
||||
)
|
||||
end
|
||||
|
||||
--- Multiply a color's components by a value (alpha unchanged)
|
||||
-- @tparam color to multiply
|
||||
-- @tparam number to multiply each component by
|
||||
-- @treturn color out
|
||||
function color.multiply(c, v)
|
||||
local t = color.new()
|
||||
for i = 1, 3 do
|
||||
t[i] = c[i] * v
|
||||
end
|
||||
|
||||
t[4] = c[4]
|
||||
return t
|
||||
end
|
||||
|
||||
-- directly set alpha channel
|
||||
-- @tparam color to alter
|
||||
-- @tparam number new alpha 0-1
|
||||
-- @treturn color out
|
||||
function color.alpha(c, v)
|
||||
local t = color.new()
|
||||
for i = 1, 3 do
|
||||
t[i] = c[i]
|
||||
end
|
||||
|
||||
t[4] = v
|
||||
return t
|
||||
end
|
||||
|
||||
--- Multiply a color's alpha by a value
|
||||
-- @tparam color to multiply
|
||||
-- @tparam number to multiply alpha by
|
||||
-- @treturn color out
|
||||
function color.opacity(c, v)
|
||||
local t = color.new()
|
||||
for i = 1, 3 do
|
||||
t[i] = c[i]
|
||||
end
|
||||
|
||||
t[4] = c[4] * v
|
||||
return t
|
||||
end
|
||||
|
||||
--- Set a color's hue (saturation, value, alpha unchanged)
|
||||
-- @tparam color to alter
|
||||
-- @tparam hue to set 0-1
|
||||
-- @treturn color out
|
||||
function color.hue(col, hue)
|
||||
local c = color_to_hsv(col)
|
||||
c[1] = (hue + 1) % 1
|
||||
return hsv_to_color(c)
|
||||
end
|
||||
|
||||
--- Set a color's saturation (hue, value, alpha unchanged)
|
||||
-- @tparam color to alter
|
||||
-- @tparam saturation to set 0-1
|
||||
-- @treturn color out
|
||||
function color.saturation(col, percent)
|
||||
local c = color_to_hsv(col)
|
||||
c[2] = utils.clamp(percent, 0, 1)
|
||||
return hsv_to_color(c)
|
||||
end
|
||||
|
||||
--- Set a color's value (saturation, hue, alpha unchanged)
|
||||
-- @tparam color to alter
|
||||
-- @tparam value to set 0-1
|
||||
-- @treturn color out
|
||||
function color.value(col, percent)
|
||||
local c = color_to_hsv(col)
|
||||
c[3] = utils.clamp(percent, 0, 1)
|
||||
return hsv_to_color(c)
|
||||
end
|
||||
|
||||
-- https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
|
||||
function color.gamma_to_linear(r, g, b, a)
|
||||
local function convert(c)
|
||||
if c > 1.0 then
|
||||
return 1.0
|
||||
elseif c < 0.0 then
|
||||
return 0.0
|
||||
elseif c <= 0.04045 then
|
||||
return c / 12.92
|
||||
else
|
||||
return math.pow((c + 0.055) / 1.055, 2.4)
|
||||
end
|
||||
end
|
||||
|
||||
if type(r) == "table" then
|
||||
local c = {}
|
||||
for i = 1, 3 do
|
||||
c[i] = convert(r[i])
|
||||
end
|
||||
|
||||
c[4] = r[4]
|
||||
return c
|
||||
else
|
||||
return convert(r), convert(g), convert(b), a or 1
|
||||
end
|
||||
end
|
||||
|
||||
-- https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB
|
||||
function color.linear_to_gamma(r, g, b, a)
|
||||
local function convert(c)
|
||||
if c > 1.0 then
|
||||
return 1.0
|
||||
elseif c < 0.0 then
|
||||
return 0.0
|
||||
elseif c < 0.0031308 then
|
||||
return c * 12.92
|
||||
else
|
||||
return 1.055 * math.pow(c, 0.41666) - 0.055
|
||||
end
|
||||
end
|
||||
|
||||
if type(r) == "table" then
|
||||
local c = {}
|
||||
for i = 1, 3 do
|
||||
c[i] = convert(r[i])
|
||||
end
|
||||
|
||||
c[4] = r[4]
|
||||
return c
|
||||
else
|
||||
return convert(r), convert(g), convert(b), a or 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if color is valid
|
||||
-- @tparam color to test
|
||||
-- @treturn boolean is color
|
||||
function color.is_color(a)
|
||||
if type(a) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, 4 do
|
||||
if type(a[i]) ~= "number" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam color a color to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function color.to_string(a)
|
||||
return string.format("[ %3.0f, %3.0f, %3.0f, %3.0f ]", a[1], a[2], a[3], a[4])
|
||||
end
|
||||
|
||||
color_mt.__index = color
|
||||
color_mt.__tostring = color.to_string
|
||||
|
||||
function color_mt.__call(_, r, g, b, a)
|
||||
return color.new(r, g, b, a)
|
||||
end
|
||||
|
||||
function color_mt.__add(a, b)
|
||||
return new(a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4])
|
||||
end
|
||||
|
||||
function color_mt.__sub(a, b)
|
||||
return new(a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4])
|
||||
end
|
||||
|
||||
function color_mt.__mul(a, b)
|
||||
if type(a) == "number" then
|
||||
return new(a * b[1], a * b[2], a * b[3], a * b[4])
|
||||
elseif type(b) == "number" then
|
||||
return new(b * a[1], b * a[2], b * a[3], b * a[4])
|
||||
else
|
||||
return new(a[1] * b[1], a[2] * b[2], a[3] * b[3], a[4] * b[4])
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable({}, color_mt)
|
||||
20
libs/cpml/constants.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
--- Various useful constants
|
||||
-- @module constants
|
||||
|
||||
--- Constants
|
||||
-- @table constants
|
||||
-- @field FLT_EPSILON Floating point precision breaks down
|
||||
-- @field DBL_EPSILON Double-precise floating point precision breaks down
|
||||
-- @field DOT_THRESHOLD Close enough to 1 for interpolations to occur
|
||||
local constants = {}
|
||||
|
||||
-- same as C's FLT_EPSILON
|
||||
constants.FLT_EPSILON = 1.19209290e-07
|
||||
|
||||
-- same as C's DBL_EPSILON
|
||||
constants.DBL_EPSILON = 2.2204460492503131e-16
|
||||
|
||||
-- used for quaternion.slerp
|
||||
constants.DOT_THRESHOLD = 0.9995
|
||||
|
||||
return constants
|
||||
709
libs/cpml/intersect.lua
Normal file
@@ -0,0 +1,709 @@
|
||||
--- Various geometric intersections
|
||||
-- @module intersect
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local constants = require(modules .. "constants")
|
||||
local mat4 = require(modules .. "mat4")
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local utils = require(modules .. "utils")
|
||||
local DBL_EPSILON = constants.DBL_EPSILON
|
||||
local sqrt = math.sqrt
|
||||
local abs = math.abs
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
local intersect = {}
|
||||
|
||||
-- https://blogs.msdn.microsoft.com/rezanour/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests/
|
||||
-- point is a vec3
|
||||
-- triangle[1] is a vec3
|
||||
-- triangle[2] is a vec3
|
||||
-- triangle[3] is a vec3
|
||||
function intersect.point_triangle(point, triangle)
|
||||
local u = triangle[2] - triangle[1]
|
||||
local v = triangle[3] - triangle[1]
|
||||
local w = point - triangle[1]
|
||||
|
||||
local vw = v:cross(w)
|
||||
local vu = v:cross(u)
|
||||
|
||||
if vw:dot(vu) < 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local uw = u:cross(w)
|
||||
local uv = u:cross(v)
|
||||
|
||||
if uw:dot(uv) < 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local d = uv:len()
|
||||
local r = vw:len() / d
|
||||
local t = uw:len() / d
|
||||
|
||||
return r + t <= 1
|
||||
end
|
||||
|
||||
-- point is a vec3
|
||||
-- aabb.min is a vec3
|
||||
-- aabb.max is a vec3
|
||||
function intersect.point_aabb(point, aabb)
|
||||
return
|
||||
aabb.min.x <= point.x and
|
||||
aabb.max.x >= point.x and
|
||||
aabb.min.y <= point.y and
|
||||
aabb.max.y >= point.y and
|
||||
aabb.min.z <= point.z and
|
||||
aabb.max.z >= point.z
|
||||
end
|
||||
|
||||
-- point is a vec3
|
||||
-- frustum.left is a plane { a, b, c, d }
|
||||
-- frustum.right is a plane { a, b, c, d }
|
||||
-- frustum.bottom is a plane { a, b, c, d }
|
||||
-- frustum.top is a plane { a, b, c, d }
|
||||
-- frustum.near is a plane { a, b, c, d }
|
||||
-- frustum.far is a plane { a, b, c, d }
|
||||
function intersect.point_frustum(point, frustum)
|
||||
local x, y, z = point:unpack()
|
||||
local planes = {
|
||||
frustum.left,
|
||||
frustum.right,
|
||||
frustum.bottom,
|
||||
frustum.top,
|
||||
frustum.near,
|
||||
frustum.far or false
|
||||
}
|
||||
|
||||
-- Skip the last test for infinite projections, it'll never fail.
|
||||
if not planes[6] then
|
||||
table.remove(planes)
|
||||
end
|
||||
|
||||
local dot
|
||||
for i = 1, #planes do
|
||||
dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
|
||||
if dot <= 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- http://www.lighthouse3d.com/tutorials/maths/ray-triangle-intersection/
|
||||
-- ray.position is a vec3
|
||||
-- ray.direction is a vec3
|
||||
-- triangle[1] is a vec3
|
||||
-- triangle[2] is a vec3
|
||||
-- triangle[3] is a vec3
|
||||
-- backface_cull is a boolean (optional)
|
||||
function intersect.ray_triangle(ray, triangle, backface_cull)
|
||||
local e1 = triangle[2] - triangle[1]
|
||||
local e2 = triangle[3] - triangle[1]
|
||||
local h = ray.direction:cross(e2)
|
||||
local a = h:dot(e1)
|
||||
|
||||
-- if a is negative, ray hits the backface
|
||||
if backface_cull and a < 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- if a is too close to 0, ray does not intersect triangle
|
||||
if abs(a) <= DBL_EPSILON then
|
||||
return false
|
||||
end
|
||||
|
||||
local f = 1 / a
|
||||
local s = ray.position - triangle[1]
|
||||
local u = s:dot(h) * f
|
||||
|
||||
-- ray does not intersect triangle
|
||||
if u < 0 or u > 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
local q = s:cross(e1)
|
||||
local v = ray.direction:dot(q) * f
|
||||
|
||||
-- ray does not intersect triangle
|
||||
if v < 0 or u + v > 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- at this stage we can compute t to find out where
|
||||
-- the intersection point is on the line
|
||||
local t = q:dot(e2) * f
|
||||
|
||||
-- return position of intersection and distance from ray origin
|
||||
if t >= DBL_EPSILON then
|
||||
return ray.position + ray.direction * t, t
|
||||
end
|
||||
|
||||
-- ray does not intersect triangle
|
||||
return false
|
||||
end
|
||||
|
||||
-- https://gamedev.stackexchange.com/questions/96459/fast-ray-sphere-collision-code
|
||||
-- ray.position is a vec3
|
||||
-- ray.direction is a vec3
|
||||
-- sphere.position is a vec3
|
||||
-- sphere.radius is a number
|
||||
function intersect.ray_sphere(ray, sphere)
|
||||
local offset = ray.position - sphere.position
|
||||
local b = offset:dot(ray.direction)
|
||||
local c = offset:dot(offset) - sphere.radius * sphere.radius
|
||||
|
||||
-- ray's position outside sphere (c > 0)
|
||||
-- ray's direction pointing away from sphere (b > 0)
|
||||
if c > 0 and b > 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local discr = b * b - c
|
||||
|
||||
-- negative discriminant
|
||||
if discr < 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Clamp t to 0
|
||||
local t = -b - sqrt(discr)
|
||||
t = t < 0 and 0 or t
|
||||
|
||||
-- Return collision point and distance from ray origin
|
||||
return ray.position + ray.direction * t, t
|
||||
end
|
||||
|
||||
-- http://gamedev.stackexchange.com/a/18459
|
||||
-- ray.position is a vec3
|
||||
-- ray.direction is a vec3
|
||||
-- aabb.min is a vec3
|
||||
-- aabb.max is a vec3
|
||||
function intersect.ray_aabb(ray, aabb)
|
||||
local dir = ray.direction:normalize()
|
||||
local dirfrac = vec3(
|
||||
1 / dir.x,
|
||||
1 / dir.y,
|
||||
1 / dir.z
|
||||
)
|
||||
|
||||
local t1 = (aabb.min.x - ray.position.x) * dirfrac.x
|
||||
local t2 = (aabb.max.x - ray.position.x) * dirfrac.x
|
||||
local t3 = (aabb.min.y - ray.position.y) * dirfrac.y
|
||||
local t4 = (aabb.max.y - ray.position.y) * dirfrac.y
|
||||
local t5 = (aabb.min.z - ray.position.z) * dirfrac.z
|
||||
local t6 = (aabb.max.z - ray.position.z) * dirfrac.z
|
||||
|
||||
local tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))
|
||||
local tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))
|
||||
|
||||
-- ray is intersecting AABB, but whole AABB is behind us
|
||||
if tmax < 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- ray does not intersect AABB
|
||||
if tmin > tmax then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Return collision point and distance from ray origin
|
||||
return ray.position + ray.direction * tmin, tmin
|
||||
end
|
||||
|
||||
-- http://stackoverflow.com/a/23976134/1190664
|
||||
-- ray.position is a vec3
|
||||
-- ray.direction is a vec3
|
||||
-- plane.position is a vec3
|
||||
-- plane.normal is a vec3
|
||||
function intersect.ray_plane(ray, plane)
|
||||
local denom = plane.normal:dot(ray.direction)
|
||||
|
||||
-- ray does not intersect plane
|
||||
if abs(denom) < DBL_EPSILON then
|
||||
return false
|
||||
end
|
||||
|
||||
-- distance of direction
|
||||
local d = plane.position - ray.position
|
||||
local t = d:dot(plane.normal) / denom
|
||||
|
||||
if t < DBL_EPSILON then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Return collision point and distance from ray origin
|
||||
return ray.position + ray.direction * t, t
|
||||
end
|
||||
|
||||
function intersect.ray_capsule(ray, capsule)
|
||||
local dist2, p1, p2 = intersect.closest_point_segment_segment(
|
||||
ray.position,
|
||||
ray.position + ray.direction * 1e10,
|
||||
capsule.a,
|
||||
capsule.b
|
||||
)
|
||||
if dist2 <= capsule.radius^2 then
|
||||
return p1
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- https://web.archive.org/web/20120414063459/http://local.wasp.uwa.edu.au/~pbourke//geometry/lineline3d/
|
||||
-- a[1] is a vec3
|
||||
-- a[2] is a vec3
|
||||
-- b[1] is a vec3
|
||||
-- b[2] is a vec3
|
||||
-- e is a number
|
||||
function intersect.line_line(a, b, e)
|
||||
-- new points
|
||||
local p13 = a[1] - b[1]
|
||||
local p43 = b[2] - b[1]
|
||||
local p21 = a[2] - a[1]
|
||||
|
||||
-- if lengths are negative or too close to 0, lines do not intersect
|
||||
if p43:len2() < DBL_EPSILON or p21:len2() < DBL_EPSILON then
|
||||
return false
|
||||
end
|
||||
|
||||
-- dot products
|
||||
local d1343 = p13:dot(p43)
|
||||
local d4321 = p43:dot(p21)
|
||||
local d1321 = p13:dot(p21)
|
||||
local d4343 = p43:dot(p43)
|
||||
local d2121 = p21:dot(p21)
|
||||
local denom = d2121 * d4343 - d4321 * d4321
|
||||
|
||||
-- if denom is too close to 0, lines do not intersect
|
||||
if abs(denom) < DBL_EPSILON then
|
||||
return false
|
||||
end
|
||||
|
||||
local numer = d1343 * d4321 - d1321 * d4343
|
||||
local mua = numer / denom
|
||||
local mub = (d1343 + d4321 * mua) / d4343
|
||||
|
||||
-- return positions of intersection on each line
|
||||
local out1 = a[1] + p21 * mua
|
||||
local out2 = b[1] + p43 * mub
|
||||
local dist = out1:dist(out2)
|
||||
|
||||
-- if distance of the shortest segment between lines is less than threshold
|
||||
if e and dist > e then
|
||||
return false
|
||||
end
|
||||
|
||||
return { out1, out2 }, dist
|
||||
end
|
||||
|
||||
-- a[1] is a vec3
|
||||
-- a[2] is a vec3
|
||||
-- b[1] is a vec3
|
||||
-- b[2] is a vec3
|
||||
-- e is a number
|
||||
function intersect.segment_segment(a, b, e)
|
||||
local c, d = intersect.line_line(a, b, e)
|
||||
|
||||
if c and ((
|
||||
a[1].x <= c[1].x and
|
||||
a[1].y <= c[1].y and
|
||||
a[1].z <= c[1].z and
|
||||
c[1].x <= a[2].x and
|
||||
c[1].y <= a[2].y and
|
||||
c[1].z <= a[2].z
|
||||
) or (
|
||||
a[1].x >= c[1].x and
|
||||
a[1].y >= c[1].y and
|
||||
a[1].z >= c[1].z and
|
||||
c[1].x >= a[2].x and
|
||||
c[1].y >= a[2].y and
|
||||
c[1].z >= a[2].z
|
||||
)) and ((
|
||||
b[1].x <= c[2].x and
|
||||
b[1].y <= c[2].y and
|
||||
b[1].z <= c[2].z and
|
||||
c[2].x <= b[2].x and
|
||||
c[2].y <= b[2].y and
|
||||
c[2].z <= b[2].z
|
||||
) or (
|
||||
b[1].x >= c[2].x and
|
||||
b[1].y >= c[2].y and
|
||||
b[1].z >= c[2].z and
|
||||
c[2].x >= b[2].x and
|
||||
c[2].y >= b[2].y and
|
||||
c[2].z >= b[2].z
|
||||
)) then
|
||||
return c, d
|
||||
end
|
||||
|
||||
-- segments do not intersect
|
||||
return false
|
||||
end
|
||||
|
||||
-- a.min is a vec3
|
||||
-- a.max is a vec3
|
||||
-- b.min is a vec3
|
||||
-- b.max is a vec3
|
||||
function intersect.aabb_aabb(a, b)
|
||||
return
|
||||
a.min.x <= b.max.x and
|
||||
a.max.x >= b.min.x and
|
||||
a.min.y <= b.max.y and
|
||||
a.max.y >= b.min.y and
|
||||
a.min.z <= b.max.z and
|
||||
a.max.z >= b.min.z
|
||||
end
|
||||
|
||||
-- aabb.position is a vec3
|
||||
-- aabb.extent is a vec3 (half-size)
|
||||
-- obb.position is a vec3
|
||||
-- obb.extent is a vec3 (half-size)
|
||||
-- obb.rotation is a mat4
|
||||
function intersect.aabb_obb(aabb, obb)
|
||||
local a = aabb.extent
|
||||
local b = obb.extent
|
||||
local T = obb.position - aabb.position
|
||||
local rot = mat4():transpose(obb.rotation)
|
||||
local B = {}
|
||||
local t
|
||||
|
||||
for i = 1, 3 do
|
||||
B[i] = {}
|
||||
for j = 1, 3 do
|
||||
assert((i - 1) * 4 + j < 16 and (i - 1) * 4 + j > 0)
|
||||
B[i][j] = abs(rot[(i - 1) * 4 + j]) + 1e-6
|
||||
end
|
||||
end
|
||||
|
||||
t = abs(T.x)
|
||||
if not (t <= (b.x + a.x * B[1][1] + b.y * B[1][2] + b.z * B[1][3])) then return false end
|
||||
t = abs(T.x * B[1][1] + T.y * B[2][1] + T.z * B[3][1])
|
||||
if not (t <= (b.x + a.x * B[1][1] + a.y * B[2][1] + a.z * B[3][1])) then return false end
|
||||
t = abs(T.y)
|
||||
if not (t <= (a.y + b.x * B[2][1] + b.y * B[2][2] + b.z * B[2][3])) then return false end
|
||||
t = abs(T.z)
|
||||
if not (t <= (a.z + b.x * B[3][1] + b.y * B[3][2] + b.z * B[3][3])) then return false end
|
||||
t = abs(T.x * B[1][2] + T.y * B[2][2] + T.z * B[3][2])
|
||||
if not (t <= (b.y + a.x * B[1][2] + a.y * B[2][2] + a.z * B[3][2])) then return false end
|
||||
t = abs(T.x * B[1][3] + T.y * B[2][3] + T.z * B[3][3])
|
||||
if not (t <= (b.z + a.x * B[1][3] + a.y * B[2][3] + a.z * B[3][3])) then return false end
|
||||
t = abs(T.z * B[2][1] - T.y * B[3][1])
|
||||
if not (t <= (a.y * B[3][1] + a.z * B[2][1] + b.y * B[1][3] + b.z * B[1][2])) then return false end
|
||||
t = abs(T.z * B[2][2] - T.y * B[3][2])
|
||||
if not (t <= (a.y * B[3][2] + a.z * B[2][2] + b.x * B[1][3] + b.z * B[1][1])) then return false end
|
||||
t = abs(T.z * B[2][3] - T.y * B[3][3])
|
||||
if not (t <= (a.y * B[3][3] + a.z * B[2][3] + b.x * B[1][2] + b.y * B[1][1])) then return false end
|
||||
t = abs(T.x * B[3][1] - T.z * B[1][1])
|
||||
if not (t <= (a.x * B[3][1] + a.z * B[1][1] + b.y * B[2][3] + b.z * B[2][2])) then return false end
|
||||
t = abs(T.x * B[3][2] - T.z * B[1][2])
|
||||
if not (t <= (a.x * B[3][2] + a.z * B[1][2] + b.x * B[2][3] + b.z * B[2][1])) then return false end
|
||||
t = abs(T.x * B[3][3] - T.z * B[1][3])
|
||||
if not (t <= (a.x * B[3][3] + a.z * B[1][3] + b.x * B[2][2] + b.y * B[2][1])) then return false end
|
||||
t = abs(T.y * B[1][1] - T.x * B[2][1])
|
||||
if not (t <= (a.x * B[2][1] + a.y * B[1][1] + b.y * B[3][3] + b.z * B[3][2])) then return false end
|
||||
t = abs(T.y * B[1][2] - T.x * B[2][2])
|
||||
if not (t <= (a.x * B[2][2] + a.y * B[1][2] + b.x * B[3][3] + b.z * B[3][1])) then return false end
|
||||
t = abs(T.y * B[1][3] - T.x * B[2][3])
|
||||
if not (t <= (a.x * B[2][3] + a.y * B[1][3] + b.x * B[3][2] + b.y * B[3][1])) then return false end
|
||||
|
||||
-- https://gamedev.stackexchange.com/questions/24078/which-side-was-hit
|
||||
-- Minkowski Sum
|
||||
local wy = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.y - obb.position.y)
|
||||
local hx = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.x - obb.position.x)
|
||||
|
||||
if wy.x > hx.x and wy.y > hx.y and wy.z > hx.z then
|
||||
if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
|
||||
return vec3(obb.rotation * { 0, -1, 0, 1 })
|
||||
else
|
||||
return vec3(obb.rotation * { -1, 0, 0, 1 })
|
||||
end
|
||||
else
|
||||
if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
|
||||
return vec3(obb.rotation * { 1, 0, 0, 1 })
|
||||
else
|
||||
return vec3(obb.rotation * { 0, 1, 0, 1 })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- http://stackoverflow.com/a/4579069/1190664
|
||||
-- aabb.min is a vec3
|
||||
-- aabb.max is a vec3
|
||||
-- sphere.position is a vec3
|
||||
-- sphere.radius is a number
|
||||
local axes = { "x", "y", "z" }
|
||||
function intersect.aabb_sphere(aabb, sphere)
|
||||
local dist2 = sphere.radius ^ 2
|
||||
|
||||
for _, axis in ipairs(axes) do
|
||||
local pos = sphere.position[axis]
|
||||
local amin = aabb.min[axis]
|
||||
local amax = aabb.max[axis]
|
||||
|
||||
if pos < amin then
|
||||
dist2 = dist2 - (pos - amin) ^ 2
|
||||
elseif pos > amax then
|
||||
dist2 = dist2 - (pos - amax) ^ 2
|
||||
end
|
||||
end
|
||||
|
||||
return dist2 > 0
|
||||
end
|
||||
|
||||
-- aabb.min is a vec3
|
||||
-- aabb.max is a vec3
|
||||
-- frustum.left is a plane { a, b, c, d }
|
||||
-- frustum.right is a plane { a, b, c, d }
|
||||
-- frustum.bottom is a plane { a, b, c, d }
|
||||
-- frustum.top is a plane { a, b, c, d }
|
||||
-- frustum.near is a plane { a, b, c, d }
|
||||
-- frustum.far is a plane { a, b, c, d }
|
||||
function intersect.aabb_frustum(aabb, frustum)
|
||||
-- Indexed for the 'index trick' later
|
||||
local box = {
|
||||
aabb.min,
|
||||
aabb.max
|
||||
}
|
||||
|
||||
-- We have 6 planes defining the frustum, 5 if infinite.
|
||||
local planes = {
|
||||
frustum.left,
|
||||
frustum.right,
|
||||
frustum.bottom,
|
||||
frustum.top,
|
||||
frustum.near,
|
||||
frustum.far or false
|
||||
}
|
||||
|
||||
-- Skip the last test for infinite projections, it'll never fail.
|
||||
if not planes[6] then
|
||||
table.remove(planes)
|
||||
end
|
||||
|
||||
for i = 1, #planes do
|
||||
-- This is the current plane
|
||||
local p = planes[i]
|
||||
|
||||
-- p-vertex selection (with the index trick)
|
||||
-- According to the plane normal we can know the
|
||||
-- indices of the positive vertex
|
||||
local px = p.a > 0.0 and 2 or 1
|
||||
local py = p.b > 0.0 and 2 or 1
|
||||
local pz = p.c > 0.0 and 2 or 1
|
||||
|
||||
-- project p-vertex on plane normal
|
||||
-- (How far is p-vertex from the origin)
|
||||
local dot = (p.a * box[px].x) + (p.b * box[py].y) + (p.c * box[pz].z)
|
||||
|
||||
-- Doesn't intersect if it is behind the plane
|
||||
if dot < -p.d then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- outer.min is a vec3
|
||||
-- outer.max is a vec3
|
||||
-- inner.min is a vec3
|
||||
-- inner.max is a vec3
|
||||
function intersect.encapsulate_aabb(outer, inner)
|
||||
return
|
||||
outer.min.x <= inner.min.x and
|
||||
outer.max.x >= inner.max.x and
|
||||
outer.min.y <= inner.min.y and
|
||||
outer.max.y >= inner.max.y and
|
||||
outer.min.z <= inner.min.z and
|
||||
outer.max.z >= inner.max.z
|
||||
end
|
||||
|
||||
-- a.position is a vec3
|
||||
-- a.radius is a number
|
||||
-- b.position is a vec3
|
||||
-- b.radius is a number
|
||||
function intersect.circle_circle(a, b)
|
||||
return a.position:dist(b.position) <= a.radius + b.radius
|
||||
end
|
||||
|
||||
-- a.position is a vec3
|
||||
-- a.radius is a number
|
||||
-- b.position is a vec3
|
||||
-- b.radius is a number
|
||||
function intersect.sphere_sphere(a, b)
|
||||
return intersect.circle_circle(a, b)
|
||||
end
|
||||
|
||||
-- http://realtimecollisiondetection.net/blog/?p=103
|
||||
-- sphere.position is a vec3
|
||||
-- sphere.radius is a number
|
||||
-- triangle[1] is a vec3
|
||||
-- triangle[2] is a vec3
|
||||
-- triangle[3] is a vec3
|
||||
function intersect.sphere_triangle(sphere, triangle)
|
||||
-- Sphere is centered at origin
|
||||
local A = triangle[1] - sphere.position
|
||||
local B = triangle[2] - sphere.position
|
||||
local C = triangle[3] - sphere.position
|
||||
|
||||
-- Compute normal of triangle plane
|
||||
local V = (B - A):cross(C - A)
|
||||
|
||||
-- Test if sphere lies outside triangle plane
|
||||
local rr = sphere.radius * sphere.radius
|
||||
local d = A:dot(V)
|
||||
local e = V:dot(V)
|
||||
local s1 = d * d > rr * e
|
||||
|
||||
-- Test if sphere lies outside triangle vertices
|
||||
local aa = A:dot(A)
|
||||
local ab = A:dot(B)
|
||||
local ac = A:dot(C)
|
||||
local bb = B:dot(B)
|
||||
local bc = B:dot(C)
|
||||
local cc = C:dot(C)
|
||||
|
||||
local s2 = (aa > rr) and (ab > aa) and (ac > aa)
|
||||
local s3 = (bb > rr) and (ab > bb) and (bc > bb)
|
||||
local s4 = (cc > rr) and (ac > cc) and (bc > cc)
|
||||
|
||||
-- Test is sphere lies outside triangle edges
|
||||
local AB = B - A
|
||||
local BC = C - B
|
||||
local CA = A - C
|
||||
|
||||
local d1 = ab - aa
|
||||
local d2 = bc - bb
|
||||
local d3 = ac - cc
|
||||
|
||||
local e1 = AB:dot(AB)
|
||||
local e2 = BC:dot(BC)
|
||||
local e3 = CA:dot(CA)
|
||||
|
||||
local Q1 = A * e1 - AB * d1
|
||||
local Q2 = B * e2 - BC * d2
|
||||
local Q3 = C * e3 - CA * d3
|
||||
|
||||
local QC = C * e1 - Q1
|
||||
local QA = A * e2 - Q2
|
||||
local QB = B * e3 - Q3
|
||||
|
||||
local s5 = (Q1:dot(Q1) > rr * e1 * e1) and (Q1:dot(QC) > 0)
|
||||
local s6 = (Q2:dot(Q2) > rr * e2 * e2) and (Q2:dot(QA) > 0)
|
||||
local s7 = (Q3:dot(Q3) > rr * e3 * e3) and (Q3:dot(QB) > 0)
|
||||
|
||||
-- Return whether or not any of the tests passed
|
||||
return s1 or s2 or s3 or s4 or s5 or s6 or s7
|
||||
end
|
||||
|
||||
-- sphere.position is a vec3
|
||||
-- sphere.radius is a number
|
||||
-- frustum.left is a plane { a, b, c, d }
|
||||
-- frustum.right is a plane { a, b, c, d }
|
||||
-- frustum.bottom is a plane { a, b, c, d }
|
||||
-- frustum.top is a plane { a, b, c, d }
|
||||
-- frustum.near is a plane { a, b, c, d }
|
||||
-- frustum.far is a plane { a, b, c, d }
|
||||
function intersect.sphere_frustum(sphere, frustum)
|
||||
local x, y, z = sphere.position:unpack()
|
||||
local planes = {
|
||||
frustum.left,
|
||||
frustum.right,
|
||||
frustum.bottom,
|
||||
frustum.top,
|
||||
frustum.near
|
||||
}
|
||||
|
||||
if frustum.far then
|
||||
table.insert(planes, frustum.far, 5)
|
||||
end
|
||||
|
||||
local dot
|
||||
for i = 1, #planes do
|
||||
dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
|
||||
|
||||
if dot <= -sphere.radius then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- dot + radius is the distance of the object from the near plane.
|
||||
-- make sure that the near plane is the last test!
|
||||
return dot + sphere.radius
|
||||
end
|
||||
|
||||
function intersect.capsule_capsule(c1, c2)
|
||||
local dist2, p1, p2 = intersect.closest_point_segment_segment(c1.a, c1.b, c2.a, c2.b)
|
||||
local radius = c1.radius + c2.radius
|
||||
|
||||
if dist2 <= radius * radius then
|
||||
return p1, p2
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function intersect.closest_point_segment_segment(p1, p2, p3, p4)
|
||||
local s -- Distance of intersection along segment 1
|
||||
local t -- Distance of intersection along segment 2
|
||||
local c1 -- Collision point on segment 1
|
||||
local c2 -- Collision point on segment 2
|
||||
|
||||
local d1 = p2 - p1 -- Direction of segment 1
|
||||
local d2 = p4 - p3 -- Direction of segment 2
|
||||
local r = p1 - p3
|
||||
local a = d1:dot(d1)
|
||||
local e = d2:dot(d2)
|
||||
local f = d2:dot(r)
|
||||
|
||||
-- Check if both segments degenerate into points
|
||||
if a <= DBL_EPSILON and e <= DBL_EPSILON then
|
||||
s = 0
|
||||
t = 0
|
||||
c1 = p1
|
||||
c2 = p3
|
||||
return (c1 - c2):dot(c1 - c2), s, t, c1, c2
|
||||
end
|
||||
|
||||
-- Check if segment 1 degenerates into a point
|
||||
if a <= DBL_EPSILON then
|
||||
s = 0
|
||||
t = utils.clamp(f / e, 0, 1)
|
||||
else
|
||||
local c = d1:dot(r)
|
||||
|
||||
-- Check is segment 2 degenerates into a point
|
||||
if e <= DBL_EPSILON then
|
||||
t = 0
|
||||
s = utils.clamp(-c / a, 0, 1)
|
||||
else
|
||||
local b = d1:dot(d2)
|
||||
local denom = a * e - b * b
|
||||
|
||||
if abs(denom) > 0 then
|
||||
s = utils.clamp((b * f - c * e) / denom, 0, 1)
|
||||
else
|
||||
s = 0
|
||||
end
|
||||
|
||||
t = (b * s + f) / e
|
||||
|
||||
if t < 0 then
|
||||
t = 0
|
||||
s = utils.clamp(-c / a, 0, 1)
|
||||
elseif t > 1 then
|
||||
t = 1
|
||||
s = utils.clamp((b - c) / a, 0, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
c1 = p1 + d1 * s
|
||||
c2 = p3 + d2 * t
|
||||
|
||||
return (c1 - c2):dot(c1 - c2), c1, c2, s, t
|
||||
end
|
||||
|
||||
return intersect
|
||||
943
libs/cpml/mat4.lua
Normal file
@@ -0,0 +1,943 @@
|
||||
--- double 4x4, 1-based, column major matrices
|
||||
-- @module mat4
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local constants = require(modules .. "constants")
|
||||
local vec2 = require(modules .. "vec2")
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local quat = require(modules .. "quat")
|
||||
local utils = require(modules .. "utils")
|
||||
local precond = require(modules .. "_private_precond")
|
||||
local private = require(modules .. "_private_utils")
|
||||
local sqrt = math.sqrt
|
||||
local cos = math.cos
|
||||
local sin = math.sin
|
||||
local tan = math.tan
|
||||
local rad = math.rad
|
||||
local mat4 = {}
|
||||
local mat4_mt = {}
|
||||
|
||||
-- Private constructor.
|
||||
local function new(m)
|
||||
m = m or {
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0
|
||||
}
|
||||
m._m = m
|
||||
return setmetatable(m, mat4_mt)
|
||||
end
|
||||
|
||||
-- Convert matrix into identity
|
||||
local function identity(m)
|
||||
m[1], m[2], m[3], m[4] = 1, 0, 0, 0
|
||||
m[5], m[6], m[7], m[8] = 0, 1, 0, 0
|
||||
m[9], m[10], m[11], m[12] = 0, 0, 1, 0
|
||||
m[13], m[14], m[15], m[16] = 0, 0, 0, 1
|
||||
return m
|
||||
end
|
||||
|
||||
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||
local status, ffi
|
||||
if type(jit) == "table" and jit.status() then
|
||||
-- status, ffi = pcall(require, "ffi")
|
||||
if status then
|
||||
ffi.cdef "typedef struct { double _m[16]; } cpml_mat4;"
|
||||
new = ffi.typeof("cpml_mat4")
|
||||
end
|
||||
end
|
||||
|
||||
-- Statically allocate a temporary variable used in some of our functions.
|
||||
local tmp = new()
|
||||
local tm4 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
local tv4 = { 0, 0, 0, 0 }
|
||||
|
||||
--- The public constructor.
|
||||
-- @param a Can be of four types: </br>
|
||||
-- table Length 16 (4x4 matrix)
|
||||
-- table Length 9 (3x3 matrix)
|
||||
-- table Length 4 (4 vec4s)
|
||||
-- nil
|
||||
-- @treturn mat4 out
|
||||
function mat4.new(a)
|
||||
local out = new()
|
||||
|
||||
-- 4x4 matrix
|
||||
if type(a) == "table" and #a == 16 then
|
||||
for i = 1, 16 do
|
||||
out[i] = tonumber(a[i])
|
||||
end
|
||||
|
||||
-- 3x3 matrix
|
||||
elseif type(a) == "table" and #a == 9 then
|
||||
out[1], out[2], out[3] = a[1], a[2], a[3]
|
||||
out[5], out[6], out[7] = a[4], a[5], a[6]
|
||||
out[9], out[10], out[11] = a[7], a[8], a[9]
|
||||
out[16] = 1
|
||||
|
||||
-- 4 vec4s
|
||||
elseif type(a) == "table" and type(a[1]) == "table" then
|
||||
local idx = 1
|
||||
for i = 1, 4 do
|
||||
for j = 1, 4 do
|
||||
out[idx] = a[i][j]
|
||||
idx = idx + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- nil
|
||||
else
|
||||
out[1] = 1
|
||||
out[6] = 1
|
||||
out[11] = 1
|
||||
out[16] = 1
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Create an identity matrix.
|
||||
-- @tparam mat4 a Matrix to overwrite
|
||||
-- @treturn mat4 out
|
||||
function mat4.identity(a)
|
||||
return identity(a or new())
|
||||
end
|
||||
|
||||
--- Create a matrix from an angle/axis pair.
|
||||
-- @tparam number angle Angle of rotation
|
||||
-- @tparam vec3 axis Axis of rotation
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_angle_axis(angle, axis)
|
||||
local l = axis:len()
|
||||
if l == 0 then
|
||||
return new()
|
||||
end
|
||||
|
||||
local x, y, z = axis.x / l, axis.y / l, axis.z / l
|
||||
local c = cos(angle)
|
||||
local s = sin(angle)
|
||||
|
||||
return new {
|
||||
x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0,
|
||||
x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0,
|
||||
x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
end
|
||||
|
||||
--- Create a matrix from a quaternion.
|
||||
-- @tparam quat q Rotation quaternion
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_quaternion(q)
|
||||
return mat4.from_angle_axis(q:to_angle_axis())
|
||||
end
|
||||
|
||||
--- Create a matrix from a direction/up pair.
|
||||
-- @tparam vec3 direction Vector direction
|
||||
-- @tparam vec3 up Up direction
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_direction(direction, up)
|
||||
local forward = vec3.normalize(direction)
|
||||
local side = vec3.cross(forward, up):normalize()
|
||||
local new_up = vec3.cross(side, forward):normalize()
|
||||
|
||||
local out = new()
|
||||
out[1] = side.x
|
||||
out[5] = side.y
|
||||
out[9] = side.z
|
||||
out[2] = new_up.x
|
||||
out[6] = new_up.y
|
||||
out[10] = new_up.z
|
||||
out[3] = forward.x
|
||||
out[7] = forward.y
|
||||
out[11] = forward.z
|
||||
out[16] = 1
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Create a matrix from a transform.
|
||||
-- @tparam vec3 trans Translation vector
|
||||
-- @tparam quat rot Rotation quaternion
|
||||
-- @tparam vec3 scale Scale vector
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_transform(trans, rot, scale)
|
||||
local rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
|
||||
|
||||
local sm = new {
|
||||
scale.x, 0, 0, 0,
|
||||
0, scale.y, 0, 0,
|
||||
0, 0, scale.z, 0,
|
||||
0, 0, 0, 1,
|
||||
}
|
||||
|
||||
local rm = new {
|
||||
1-2*(ry*ry+rz*rz), 2*(rx*ry-rz*rw), 2*(rx*rz+ry*rw), 0,
|
||||
2*(rx*ry+rz*rw), 1-2*(rx*rx+rz*rz), 2*(ry*rz-rx*rw), 0,
|
||||
2*(rx*rz-ry*rw), 2*(ry*rz+rx*rw), 1-2*(rx*rx+ry*ry), 0,
|
||||
0, 0, 0, 1
|
||||
}
|
||||
|
||||
local rsm = rm * sm
|
||||
|
||||
rsm[13] = trans.x
|
||||
rsm[14] = trans.y
|
||||
rsm[15] = trans.z
|
||||
|
||||
return rsm
|
||||
end
|
||||
|
||||
--- Create matrix from orthogonal.
|
||||
-- @tparam number left
|
||||
-- @tparam number right
|
||||
-- @tparam number top
|
||||
-- @tparam number bottom
|
||||
-- @tparam number near
|
||||
-- @tparam number far
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_ortho(left, right, top, bottom, near, far)
|
||||
local out = new()
|
||||
out[1] = 2 / (right - left)
|
||||
out[6] = 2 / (top - bottom)
|
||||
out[11] = -2 / (far - near)
|
||||
out[13] = -((right + left) / (right - left))
|
||||
out[14] = -((top + bottom) / (top - bottom))
|
||||
out[15] = -((far + near) / (far - near))
|
||||
out[16] = 1
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Create matrix from perspective.
|
||||
-- @tparam number fovy Field of view
|
||||
-- @tparam number aspect Aspect ratio
|
||||
-- @tparam number near Near plane
|
||||
-- @tparam number far Far plane
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_perspective(fovy, aspect, near, far)
|
||||
assert(aspect ~= 0)
|
||||
assert(near ~= far)
|
||||
|
||||
local t = tan(rad(fovy) / 2)
|
||||
local out = new()
|
||||
out[1] = 1 / (t * aspect)
|
||||
out[6] = 1 / t
|
||||
out[11] = -(far + near) / (far - near)
|
||||
out[12] = -1
|
||||
out[15] = -(2 * far * near) / (far - near)
|
||||
out[16] = 0
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
-- Adapted from the Oculus SDK.
|
||||
--- Create matrix from HMD perspective.
|
||||
-- @tparam number tanHalfFov Tangent of half of the field of view
|
||||
-- @tparam number zNear Near plane
|
||||
-- @tparam number zFar Far plane
|
||||
-- @tparam boolean flipZ Z axis is flipped or not
|
||||
-- @tparam boolean farAtInfinity Far plane is infinite or not
|
||||
-- @treturn mat4 out
|
||||
function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity)
|
||||
-- CPML is right-handed and intended for GL, so these don't need to be arguments.
|
||||
local rightHanded = true
|
||||
local isOpenGL = true
|
||||
|
||||
local function CreateNDCScaleAndOffsetFromFov(tanHalfFov)
|
||||
local x_scale = 2 / (tanHalfFov.LeftTan + tanHalfFov.RightTan)
|
||||
local x_offset = (tanHalfFov.LeftTan - tanHalfFov.RightTan) * x_scale * 0.5
|
||||
local y_scale = 2 / (tanHalfFov.UpTan + tanHalfFov.DownTan )
|
||||
local y_offset = (tanHalfFov.UpTan - tanHalfFov.DownTan ) * y_scale * 0.5
|
||||
|
||||
local result = {
|
||||
Scale = vec2(x_scale, y_scale),
|
||||
Offset = vec2(x_offset, y_offset)
|
||||
}
|
||||
|
||||
-- Hey - why is that Y.Offset negated?
|
||||
-- It's because a projection matrix transforms from world coords with Y=up,
|
||||
-- whereas this is from NDC which is Y=down.
|
||||
return result
|
||||
end
|
||||
|
||||
if not flipZ and farAtInfinity then
|
||||
print("Error: Cannot push Far Clip to Infinity when Z-order is not flipped")
|
||||
farAtInfinity = false
|
||||
end
|
||||
|
||||
-- A projection matrix is very like a scaling from NDC, so we can start with that.
|
||||
local scaleAndOffset = CreateNDCScaleAndOffsetFromFov(tanHalfFov)
|
||||
local handednessScale = rightHanded and -1.0 or 1.0
|
||||
local projection = new()
|
||||
|
||||
-- Produces X result, mapping clip edges to [-w,+w]
|
||||
projection[1] = scaleAndOffset.Scale.x
|
||||
projection[2] = 0
|
||||
projection[3] = handednessScale * scaleAndOffset.Offset.x
|
||||
projection[4] = 0
|
||||
|
||||
-- Produces Y result, mapping clip edges to [-w,+w]
|
||||
-- Hey - why is that YOffset negated?
|
||||
-- It's because a projection matrix transforms from world coords with Y=up,
|
||||
-- whereas this is derived from an NDC scaling, which is Y=down.
|
||||
projection[5] = 0
|
||||
projection[6] = scaleAndOffset.Scale.y
|
||||
projection[7] = handednessScale * -scaleAndOffset.Offset.y
|
||||
projection[8] = 0
|
||||
|
||||
-- Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
|
||||
-- We'll just use some defaults for now.
|
||||
projection[9] = 0
|
||||
projection[10] = 0
|
||||
|
||||
if farAtInfinity then
|
||||
if isOpenGL then
|
||||
-- It's not clear this makes sense for OpenGL - you don't get the same precision benefits you do in D3D.
|
||||
projection[11] = -handednessScale
|
||||
projection[12] = 2.0 * zNear
|
||||
else
|
||||
projection[11] = 0
|
||||
projection[12] = zNear
|
||||
end
|
||||
else
|
||||
if isOpenGL then
|
||||
-- Clip range is [-w,+w], so 0 is at the middle of the range.
|
||||
projection[11] = -handednessScale * (flipZ and -1.0 or 1.0) * (zNear + zFar) / (zNear - zFar)
|
||||
projection[12] = 2.0 * ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
|
||||
else
|
||||
-- Clip range is [0,+w], so 0 is at the start of the range.
|
||||
projection[11] = -handednessScale * (flipZ and -zNear or zFar) / (zNear - zFar)
|
||||
projection[12] = ((flipZ and -zFar or zFar) * zNear) / (zNear - zFar)
|
||||
end
|
||||
end
|
||||
|
||||
-- Produces W result (= Z in)
|
||||
projection[13] = 0
|
||||
projection[14] = 0
|
||||
projection[15] = handednessScale
|
||||
projection[16] = 0
|
||||
|
||||
return projection:transpose(projection)
|
||||
end
|
||||
|
||||
--- Clone a matrix.
|
||||
-- @tparam mat4 a Matrix to clone
|
||||
-- @treturn mat4 out
|
||||
function mat4.clone(a)
|
||||
return new(a)
|
||||
end
|
||||
|
||||
function mul_internal(out, a, b)
|
||||
tm4[1] = b[1] * a[1] + b[2] * a[5] + b[3] * a[9] + b[4] * a[13]
|
||||
tm4[2] = b[1] * a[2] + b[2] * a[6] + b[3] * a[10] + b[4] * a[14]
|
||||
tm4[3] = b[1] * a[3] + b[2] * a[7] + b[3] * a[11] + b[4] * a[15]
|
||||
tm4[4] = b[1] * a[4] + b[2] * a[8] + b[3] * a[12] + b[4] * a[16]
|
||||
tm4[5] = b[5] * a[1] + b[6] * a[5] + b[7] * a[9] + b[8] * a[13]
|
||||
tm4[6] = b[5] * a[2] + b[6] * a[6] + b[7] * a[10] + b[8] * a[14]
|
||||
tm4[7] = b[5] * a[3] + b[6] * a[7] + b[7] * a[11] + b[8] * a[15]
|
||||
tm4[8] = b[5] * a[4] + b[6] * a[8] + b[7] * a[12] + b[8] * a[16]
|
||||
tm4[9] = b[9] * a[1] + b[10] * a[5] + b[11] * a[9] + b[12] * a[13]
|
||||
tm4[10] = b[9] * a[2] + b[10] * a[6] + b[11] * a[10] + b[12] * a[14]
|
||||
tm4[11] = b[9] * a[3] + b[10] * a[7] + b[11] * a[11] + b[12] * a[15]
|
||||
tm4[12] = b[9] * a[4] + b[10] * a[8] + b[11] * a[12] + b[12] * a[16]
|
||||
tm4[13] = b[13] * a[1] + b[14] * a[5] + b[15] * a[9] + b[16] * a[13]
|
||||
tm4[14] = b[13] * a[2] + b[14] * a[6] + b[15] * a[10] + b[16] * a[14]
|
||||
tm4[15] = b[13] * a[3] + b[14] * a[7] + b[15] * a[11] + b[16] * a[15]
|
||||
tm4[16] = b[13] * a[4] + b[14] * a[8] + b[15] * a[12] + b[16] * a[16]
|
||||
|
||||
for i = 1, 16 do
|
||||
out[i] = tm4[i]
|
||||
end
|
||||
end
|
||||
|
||||
--- Multiply N matrices.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 or {mat4, ...} left hand operand(s)
|
||||
-- @tparam mat4 right hand operand if a is not table
|
||||
-- @treturn mat4 out multiplied matrix result
|
||||
function mat4.mul(out, a, b)
|
||||
if mat4.is_mat4(a) then
|
||||
mul_internal(out, a, b)
|
||||
return out
|
||||
end
|
||||
if #a == 0 then
|
||||
identity(out)
|
||||
elseif #a == 1 then
|
||||
-- only one matrix, just copy
|
||||
for i = 1, 16 do
|
||||
out[i] = a[1][i]
|
||||
end
|
||||
else
|
||||
local ma = a[1]
|
||||
local mb = a[2]
|
||||
for i = 2, #a do
|
||||
mul_internal(out, ma, mb)
|
||||
ma = out
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Multiply a matrix and a vec3, with perspective division.
|
||||
-- This function uses an implicit 1 for the fourth component.
|
||||
-- @tparam vec3 out vec3 to store the result
|
||||
-- @tparam mat4 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function mat4.mul_vec3_perspective(out, a, b)
|
||||
local v4x = b.x * a[1] + b.y * a[5] + b.z * a[9] + a[13]
|
||||
local v4y = b.x * a[2] + b.y * a[6] + b.z * a[10] + a[14]
|
||||
local v4z = b.x * a[3] + b.y * a[7] + b.z * a[11] + a[15]
|
||||
local v4w = b.x * a[4] + b.y * a[8] + b.z * a[12] + a[16]
|
||||
local inv_w = 0
|
||||
if v4w ~= 0 then
|
||||
inv_w = utils.sign(v4w) / v4w
|
||||
end
|
||||
out.x = v4x * inv_w
|
||||
out.y = v4y * inv_w
|
||||
out.z = v4z * inv_w
|
||||
return out
|
||||
end
|
||||
|
||||
--- Multiply a matrix and a vec4.
|
||||
-- @tparam table out table to store the result
|
||||
-- @tparam mat4 a Left hand operand
|
||||
-- @tparam table b Right hand operand
|
||||
-- @treturn vec4 out
|
||||
function mat4.mul_vec4(out, a, b)
|
||||
tv4[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9] + b[4] * a[13]
|
||||
tv4[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14]
|
||||
tv4[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15]
|
||||
tv4[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16]
|
||||
|
||||
for i = 1, 4 do
|
||||
out[i] = tv4[i]
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Invert a matrix.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 a Matrix to invert
|
||||
-- @treturn mat4 out
|
||||
function mat4.invert(out, a)
|
||||
tm4[1] = a[6] * a[11] * a[16] - a[6] * a[12] * a[15] - a[10] * a[7] * a[16] + a[10] * a[8] * a[15] + a[14] * a[7] * a[12] - a[14] * a[8] * a[11]
|
||||
tm4[2] = -a[2] * a[11] * a[16] + a[2] * a[12] * a[15] + a[10] * a[3] * a[16] - a[10] * a[4] * a[15] - a[14] * a[3] * a[12] + a[14] * a[4] * a[11]
|
||||
tm4[3] = a[2] * a[7] * a[16] - a[2] * a[8] * a[15] - a[6] * a[3] * a[16] + a[6] * a[4] * a[15] + a[14] * a[3] * a[8] - a[14] * a[4] * a[7]
|
||||
tm4[4] = -a[2] * a[7] * a[12] + a[2] * a[8] * a[11] + a[6] * a[3] * a[12] - a[6] * a[4] * a[11] - a[10] * a[3] * a[8] + a[10] * a[4] * a[7]
|
||||
tm4[5] = -a[5] * a[11] * a[16] + a[5] * a[12] * a[15] + a[9] * a[7] * a[16] - a[9] * a[8] * a[15] - a[13] * a[7] * a[12] + a[13] * a[8] * a[11]
|
||||
tm4[6] = a[1] * a[11] * a[16] - a[1] * a[12] * a[15] - a[9] * a[3] * a[16] + a[9] * a[4] * a[15] + a[13] * a[3] * a[12] - a[13] * a[4] * a[11]
|
||||
tm4[7] = -a[1] * a[7] * a[16] + a[1] * a[8] * a[15] + a[5] * a[3] * a[16] - a[5] * a[4] * a[15] - a[13] * a[3] * a[8] + a[13] * a[4] * a[7]
|
||||
tm4[8] = a[1] * a[7] * a[12] - a[1] * a[8] * a[11] - a[5] * a[3] * a[12] + a[5] * a[4] * a[11] + a[9] * a[3] * a[8] - a[9] * a[4] * a[7]
|
||||
tm4[9] = a[5] * a[10] * a[16] - a[5] * a[12] * a[14] - a[9] * a[6] * a[16] + a[9] * a[8] * a[14] + a[13] * a[6] * a[12] - a[13] * a[8] * a[10]
|
||||
tm4[10] = -a[1] * a[10] * a[16] + a[1] * a[12] * a[14] + a[9] * a[2] * a[16] - a[9] * a[4] * a[14] - a[13] * a[2] * a[12] + a[13] * a[4] * a[10]
|
||||
tm4[11] = a[1] * a[6] * a[16] - a[1] * a[8] * a[14] - a[5] * a[2] * a[16] + a[5] * a[4] * a[14] + a[13] * a[2] * a[8] - a[13] * a[4] * a[6]
|
||||
tm4[12] = -a[1] * a[6] * a[12] + a[1] * a[8] * a[10] + a[5] * a[2] * a[12] - a[5] * a[4] * a[10] - a[9] * a[2] * a[8] + a[9] * a[4] * a[6]
|
||||
tm4[13] = -a[5] * a[10] * a[15] + a[5] * a[11] * a[14] + a[9] * a[6] * a[15] - a[9] * a[7] * a[14] - a[13] * a[6] * a[11] + a[13] * a[7] * a[10]
|
||||
tm4[14] = a[1] * a[10] * a[15] - a[1] * a[11] * a[14] - a[9] * a[2] * a[15] + a[9] * a[3] * a[14] + a[13] * a[2] * a[11] - a[13] * a[3] * a[10]
|
||||
tm4[15] = -a[1] * a[6] * a[15] + a[1] * a[7] * a[14] + a[5] * a[2] * a[15] - a[5] * a[3] * a[14] - a[13] * a[2] * a[7] + a[13] * a[3] * a[6]
|
||||
tm4[16] = a[1] * a[6] * a[11] - a[1] * a[7] * a[10] - a[5] * a[2] * a[11] + a[5] * a[3] * a[10] + a[9] * a[2] * a[7] - a[9] * a[3] * a[6]
|
||||
|
||||
local det = a[1] * tm4[1] + a[2] * tm4[5] + a[3] * tm4[9] + a[4] * tm4[13]
|
||||
|
||||
if det == 0 then return a end
|
||||
|
||||
det = 1 / det
|
||||
|
||||
for i = 1, 16 do
|
||||
out[i] = tm4[i] * det
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Scale a matrix.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 a Matrix to scale
|
||||
-- @tparam vec3 s Scalar
|
||||
-- @treturn mat4 out
|
||||
function mat4.scale(out, a, s)
|
||||
identity(tmp)
|
||||
tmp[1] = s.x
|
||||
tmp[6] = s.y
|
||||
tmp[11] = s.z
|
||||
|
||||
return out:mul(tmp, a)
|
||||
end
|
||||
|
||||
--- Rotate a matrix.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 a Matrix to rotate
|
||||
-- @tparam number angle Angle to rotate by (in radians)
|
||||
-- @tparam vec3 axis Axis to rotate on
|
||||
-- @treturn mat4 out
|
||||
function mat4.rotate(out, a, angle, axis)
|
||||
if type(angle) == "table" or type(angle) == "cdata" then
|
||||
angle, axis = angle:to_angle_axis()
|
||||
end
|
||||
|
||||
local l = axis:len()
|
||||
|
||||
if l == 0 then
|
||||
return a
|
||||
end
|
||||
|
||||
local x, y, z = axis.x / l, axis.y / l, axis.z / l
|
||||
local c = cos(angle)
|
||||
local s = sin(angle)
|
||||
|
||||
identity(tmp)
|
||||
tmp[1] = x * x * (1 - c) + c
|
||||
tmp[2] = y * x * (1 - c) + z * s
|
||||
tmp[3] = x * z * (1 - c) - y * s
|
||||
tmp[5] = x * y * (1 - c) - z * s
|
||||
tmp[6] = y * y * (1 - c) + c
|
||||
tmp[7] = y * z * (1 - c) + x * s
|
||||
tmp[9] = x * z * (1 - c) + y * s
|
||||
tmp[10] = y * z * (1 - c) - x * s
|
||||
tmp[11] = z * z * (1 - c) + c
|
||||
|
||||
return out:mul(tmp, a)
|
||||
end
|
||||
|
||||
--- Translate a matrix.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 a Matrix to translate
|
||||
-- @tparam vec3 t Translation vector
|
||||
-- @treturn mat4 out
|
||||
function mat4.translate(out, a, t)
|
||||
identity(tmp)
|
||||
tmp[13] = t.x
|
||||
tmp[14] = t.y
|
||||
tmp[15] = t.z
|
||||
|
||||
return out:mul(tmp, a)
|
||||
end
|
||||
|
||||
--- Shear a matrix.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 a Matrix to translate
|
||||
-- @tparam number yx
|
||||
-- @tparam number zx
|
||||
-- @tparam number xy
|
||||
-- @tparam number zy
|
||||
-- @tparam number xz
|
||||
-- @tparam number yz
|
||||
-- @treturn mat4 out
|
||||
function mat4.shear(out, a, yx, zx, xy, zy, xz, yz)
|
||||
identity(tmp)
|
||||
tmp[2] = yx or 0
|
||||
tmp[3] = zx or 0
|
||||
tmp[5] = xy or 0
|
||||
tmp[7] = zy or 0
|
||||
tmp[9] = xz or 0
|
||||
tmp[10] = yz or 0
|
||||
|
||||
return out:mul(tmp, a)
|
||||
end
|
||||
|
||||
--- Reflect a matrix across a plane.
|
||||
-- @tparam mat4 Matrix to store the result
|
||||
-- @tparam a Matrix to reflect
|
||||
-- @tparam vec3 position A point on the plane
|
||||
-- @tparam vec3 normal The (normalized!) normal vector of the plane
|
||||
function mat4.reflect(out, a, position, normal)
|
||||
local nx, ny, nz = normal:unpack()
|
||||
local d = -position:dot(normal)
|
||||
tmp[1] = 1 - 2 * nx ^ 2
|
||||
tmp[2] = 2 * nx * ny
|
||||
tmp[3] = -2 * nx * nz
|
||||
tmp[4] = 0
|
||||
tmp[5] = -2 * nx * ny
|
||||
tmp[6] = 1 - 2 * ny ^ 2
|
||||
tmp[7] = -2 * ny * nz
|
||||
tmp[8] = 0
|
||||
tmp[9] = -2 * nx * nz
|
||||
tmp[10] = -2 * ny * nz
|
||||
tmp[11] = 1 - 2 * nz ^ 2
|
||||
tmp[12] = 0
|
||||
tmp[13] = -2 * nx * d
|
||||
tmp[14] = -2 * ny * d
|
||||
tmp[15] = -2 * nz * d
|
||||
tmp[16] = 1
|
||||
|
||||
return out:mul(tmp, a)
|
||||
end
|
||||
|
||||
--- Transform matrix to look at a point.
|
||||
-- @tparam mat4 out Matrix to store result
|
||||
-- @tparam vec3 eye Location of viewer's view plane
|
||||
-- @tparam vec3 center Location of object to view
|
||||
-- @tparam vec3 up Up direction
|
||||
-- @treturn mat4 out
|
||||
function mat4.look_at(out, eye, look_at, up)
|
||||
local z_axis = (eye - look_at):normalize()
|
||||
local x_axis = up:cross(z_axis):normalize()
|
||||
local y_axis = z_axis:cross(x_axis)
|
||||
out[1] = x_axis.x
|
||||
out[2] = y_axis.x
|
||||
out[3] = z_axis.x
|
||||
out[4] = 0
|
||||
out[5] = x_axis.y
|
||||
out[6] = y_axis.y
|
||||
out[7] = z_axis.y
|
||||
out[8] = 0
|
||||
out[9] = x_axis.z
|
||||
out[10] = y_axis.z
|
||||
out[11] = z_axis.z
|
||||
out[12] = 0
|
||||
out[13] = -out[ 1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z
|
||||
out[14] = -out[ 2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z
|
||||
out[15] = -out[ 3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z
|
||||
out[16] = -out[ 4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1
|
||||
return out
|
||||
end
|
||||
|
||||
--- Transform matrix to target a point.
|
||||
-- @tparam mat4 out Matrix to store result
|
||||
-- @tparam vec3 eye Location of viewer's view plane
|
||||
-- @tparam vec3 center Location of object to view
|
||||
-- @tparam vec3 up Up direction
|
||||
-- @treturn mat4 out
|
||||
function mat4.target(out, from, to, up)
|
||||
local z_axis = (from - to):normalize()
|
||||
local x_axis = up:cross(z_axis):normalize()
|
||||
local y_axis = z_axis:cross(x_axis)
|
||||
out[1] = x_axis.x
|
||||
out[2] = x_axis.y
|
||||
out[3] = x_axis.z
|
||||
out[4] = 0
|
||||
out[5] = y_axis.x
|
||||
out[6] = y_axis.y
|
||||
out[7] = y_axis.z
|
||||
out[8] = 0
|
||||
out[9] = z_axis.x
|
||||
out[10] = z_axis.y
|
||||
out[11] = z_axis.z
|
||||
out[12] = 0
|
||||
out[13] = from.x
|
||||
out[14] = from.y
|
||||
out[15] = from.z
|
||||
out[16] = 1
|
||||
return out
|
||||
end
|
||||
|
||||
--- Transpose a matrix.
|
||||
-- @tparam mat4 out Matrix to store the result
|
||||
-- @tparam mat4 a Matrix to transpose
|
||||
-- @treturn mat4 out
|
||||
function mat4.transpose(out, a)
|
||||
tm4[1] = a[1]
|
||||
tm4[2] = a[5]
|
||||
tm4[3] = a[9]
|
||||
tm4[4] = a[13]
|
||||
tm4[5] = a[2]
|
||||
tm4[6] = a[6]
|
||||
tm4[7] = a[10]
|
||||
tm4[8] = a[14]
|
||||
tm4[9] = a[3]
|
||||
tm4[10] = a[7]
|
||||
tm4[11] = a[11]
|
||||
tm4[12] = a[15]
|
||||
tm4[13] = a[4]
|
||||
tm4[14] = a[8]
|
||||
tm4[15] = a[12]
|
||||
tm4[16] = a[16]
|
||||
|
||||
for i = 1, 16 do
|
||||
out[i] = tm4[i]
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
|
||||
--- Project a point into screen space
|
||||
-- @tparam vec3 obj Object position in world space
|
||||
-- @tparam mat4 mvp Projection matrix
|
||||
-- @tparam table viewport XYWH of viewport
|
||||
-- @treturn vec3 win
|
||||
function mat4.project(obj, mvp, viewport)
|
||||
local point = mat4.mul_vec3_perspective(vec3(), mvp, obj)
|
||||
point.x = point.x * 0.5 + 0.5
|
||||
point.y = point.y * 0.5 + 0.5
|
||||
point.z = point.z * 0.5 + 0.5
|
||||
point.x = point.x * viewport[3] + viewport[1]
|
||||
point.y = point.y * viewport[4] + viewport[2]
|
||||
return point
|
||||
end
|
||||
|
||||
--- Unproject a point from screen space to world space.
|
||||
-- @tparam vec3 win Object position in screen space
|
||||
-- @tparam mat4 mvp Projection matrix
|
||||
-- @tparam table viewport XYWH of viewport
|
||||
-- @treturn vec3 obj
|
||||
function mat4.unproject(win, mvp, viewport)
|
||||
local point = vec3.clone(win)
|
||||
|
||||
-- 0..n -> 0..1
|
||||
point.x = (point.x - viewport[1]) / viewport[3]
|
||||
point.y = (point.y - viewport[2]) / viewport[4]
|
||||
|
||||
-- 0..1 -> -1..1
|
||||
point.x = point.x * 2 - 1
|
||||
point.y = point.y * 2 - 1
|
||||
point.z = point.z * 2 - 1
|
||||
|
||||
return mat4.mul_vec3_perspective(point, tmp:invert(mvp), point)
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a mat4.
|
||||
-- @tparam mat4 a Matrix to be tested
|
||||
-- @treturn boolean is_mat4
|
||||
function mat4.is_mat4(a)
|
||||
if type(a) == "cdata" then
|
||||
return ffi.istype("cpml_mat4", a)
|
||||
end
|
||||
|
||||
if type(a) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, 16 do
|
||||
if type(a[i]) ~= "number" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Return whether any component is NaN
|
||||
-- @tparam mat4 a Matrix to be tested
|
||||
-- @treturn boolean if any component is NaN
|
||||
function vec2.has_nan(a)
|
||||
for i = 1, 16 do
|
||||
if private.is_nan(a[i]) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam mat4 a Matrix to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function mat4.to_string(a)
|
||||
local str = "[ "
|
||||
for i = 1, 16 do
|
||||
str = str .. string.format("%+0.3f", a[i])
|
||||
if i < 16 then
|
||||
str = str .. ", "
|
||||
end
|
||||
end
|
||||
str = str .. " ]"
|
||||
return str
|
||||
end
|
||||
|
||||
--- Convert a matrix to row vec4s.
|
||||
-- @tparam mat4 a Matrix to be converted
|
||||
-- @treturn table vec4s
|
||||
function mat4.to_vec4s(a)
|
||||
return {
|
||||
{ a[1], a[2], a[3], a[4] },
|
||||
{ a[5], a[6], a[7], a[8] },
|
||||
{ a[9], a[10], a[11], a[12] },
|
||||
{ a[13], a[14], a[15], a[16] }
|
||||
}
|
||||
end
|
||||
|
||||
--- Convert a matrix to col vec4s.
|
||||
-- @tparam mat4 a Matrix to be converted
|
||||
-- @treturn table vec4s
|
||||
function mat4.to_vec4s_cols(a)
|
||||
return {
|
||||
{ a[1], a[5], a[9], a[13] },
|
||||
{ a[2], a[6], a[10], a[14] },
|
||||
{ a[3], a[7], a[11], a[15] },
|
||||
{ a[4], a[8], a[12], a[16] }
|
||||
}
|
||||
end
|
||||
|
||||
-- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
|
||||
--- Convert a matrix to a quaternion.
|
||||
-- @tparam mat4 a Matrix to be converted
|
||||
-- @treturn quat out
|
||||
function mat4.to_quat(a)
|
||||
identity(tmp):transpose(a)
|
||||
|
||||
local w = sqrt(1 + tmp[1] + tmp[6] + tmp[11]) / 2
|
||||
local scale = w * 4
|
||||
local q = quat.new(
|
||||
tmp[10] - tmp[7] / scale,
|
||||
tmp[3] - tmp[9] / scale,
|
||||
tmp[5] - tmp[2] / scale,
|
||||
w
|
||||
)
|
||||
|
||||
return q:normalize(q)
|
||||
end
|
||||
|
||||
-- http://www.crownandcutlass.com/features/technicaldetails/frustum.html
|
||||
--- Convert a matrix to a frustum.
|
||||
-- @tparam mat4 a Matrix to be converted (projection * view)
|
||||
-- @tparam boolean infinite Infinite removes the far plane
|
||||
-- @treturn frustum out
|
||||
function mat4.to_frustum(a, infinite)
|
||||
local t
|
||||
local frustum = {}
|
||||
|
||||
-- Extract the LEFT plane
|
||||
frustum.left = {}
|
||||
frustum.left.a = a[4] + a[1]
|
||||
frustum.left.b = a[8] + a[5]
|
||||
frustum.left.c = a[12] + a[9]
|
||||
frustum.left.d = a[16] + a[13]
|
||||
|
||||
-- Normalize the result
|
||||
t = sqrt(frustum.left.a * frustum.left.a + frustum.left.b * frustum.left.b + frustum.left.c * frustum.left.c)
|
||||
frustum.left.a = frustum.left.a / t
|
||||
frustum.left.b = frustum.left.b / t
|
||||
frustum.left.c = frustum.left.c / t
|
||||
frustum.left.d = frustum.left.d / t
|
||||
|
||||
-- Extract the RIGHT plane
|
||||
frustum.right = {}
|
||||
frustum.right.a = a[4] - a[1]
|
||||
frustum.right.b = a[8] - a[5]
|
||||
frustum.right.c = a[12] - a[9]
|
||||
frustum.right.d = a[16] - a[13]
|
||||
|
||||
-- Normalize the result
|
||||
t = sqrt(frustum.right.a * frustum.right.a + frustum.right.b * frustum.right.b + frustum.right.c * frustum.right.c)
|
||||
frustum.right.a = frustum.right.a / t
|
||||
frustum.right.b = frustum.right.b / t
|
||||
frustum.right.c = frustum.right.c / t
|
||||
frustum.right.d = frustum.right.d / t
|
||||
|
||||
-- Extract the BOTTOM plane
|
||||
frustum.bottom = {}
|
||||
frustum.bottom.a = a[4] + a[2]
|
||||
frustum.bottom.b = a[8] + a[6]
|
||||
frustum.bottom.c = a[12] + a[10]
|
||||
frustum.bottom.d = a[16] + a[14]
|
||||
|
||||
-- Normalize the result
|
||||
t = sqrt(frustum.bottom.a * frustum.bottom.a + frustum.bottom.b * frustum.bottom.b + frustum.bottom.c * frustum.bottom.c)
|
||||
frustum.bottom.a = frustum.bottom.a / t
|
||||
frustum.bottom.b = frustum.bottom.b / t
|
||||
frustum.bottom.c = frustum.bottom.c / t
|
||||
frustum.bottom.d = frustum.bottom.d / t
|
||||
|
||||
-- Extract the TOP plane
|
||||
frustum.top = {}
|
||||
frustum.top.a = a[4] - a[2]
|
||||
frustum.top.b = a[8] - a[6]
|
||||
frustum.top.c = a[12] - a[10]
|
||||
frustum.top.d = a[16] - a[14]
|
||||
|
||||
-- Normalize the result
|
||||
t = sqrt(frustum.top.a * frustum.top.a + frustum.top.b * frustum.top.b + frustum.top.c * frustum.top.c)
|
||||
frustum.top.a = frustum.top.a / t
|
||||
frustum.top.b = frustum.top.b / t
|
||||
frustum.top.c = frustum.top.c / t
|
||||
frustum.top.d = frustum.top.d / t
|
||||
|
||||
-- Extract the NEAR plane
|
||||
frustum.near = {}
|
||||
frustum.near.a = a[4] + a[3]
|
||||
frustum.near.b = a[8] + a[7]
|
||||
frustum.near.c = a[12] + a[11]
|
||||
frustum.near.d = a[16] + a[15]
|
||||
|
||||
-- Normalize the result
|
||||
t = sqrt(frustum.near.a * frustum.near.a + frustum.near.b * frustum.near.b + frustum.near.c * frustum.near.c)
|
||||
frustum.near.a = frustum.near.a / t
|
||||
frustum.near.b = frustum.near.b / t
|
||||
frustum.near.c = frustum.near.c / t
|
||||
frustum.near.d = frustum.near.d / t
|
||||
|
||||
if not infinite then
|
||||
-- Extract the FAR plane
|
||||
frustum.far = {}
|
||||
frustum.far.a = a[4] - a[3]
|
||||
frustum.far.b = a[8] - a[7]
|
||||
frustum.far.c = a[12] - a[11]
|
||||
frustum.far.d = a[16] - a[15]
|
||||
|
||||
-- Normalize the result
|
||||
t = sqrt(frustum.far.a * frustum.far.a + frustum.far.b * frustum.far.b + frustum.far.c * frustum.far.c)
|
||||
frustum.far.a = frustum.far.a / t
|
||||
frustum.far.b = frustum.far.b / t
|
||||
frustum.far.c = frustum.far.c / t
|
||||
frustum.far.d = frustum.far.d / t
|
||||
end
|
||||
|
||||
return frustum
|
||||
end
|
||||
|
||||
function mat4_mt.__index(t, k)
|
||||
if type(t) == "cdata" then
|
||||
if type(k) == "number" then
|
||||
return t._m[k-1]
|
||||
end
|
||||
end
|
||||
|
||||
return rawget(mat4, k)
|
||||
end
|
||||
|
||||
function mat4_mt.__newindex(t, k, v)
|
||||
if type(t) == "cdata" then
|
||||
if type(k) == "number" then
|
||||
t._m[k-1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mat4_mt.__tostring = mat4.to_string
|
||||
|
||||
function mat4_mt.__call(_, a)
|
||||
return mat4.new(a)
|
||||
end
|
||||
|
||||
function mat4_mt.__unm(a)
|
||||
return new():invert(a)
|
||||
end
|
||||
|
||||
function mat4_mt.__eq(a, b)
|
||||
if not mat4.is_mat4(a) or not mat4.is_mat4(b) then
|
||||
return false
|
||||
end
|
||||
|
||||
for i = 1, 16 do
|
||||
if not utils.tolerance(b[i]-a[i], constants.FLT_EPSILON) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function mat4_mt.__mul(a, b)
|
||||
precond.assert(mat4.is_mat4(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.mat4> expected)", type(a))
|
||||
|
||||
if vec3.is_vec3(b) then
|
||||
return mat4.mul_vec3_perspective(vec3(), a, b)
|
||||
end
|
||||
|
||||
assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operand. (<cpml.mat4> or table #4 expected)")
|
||||
|
||||
if mat4.is_mat4(b) then
|
||||
return new():mul(a, b)
|
||||
end
|
||||
|
||||
return mat4.mul_vec4({}, a, b)
|
||||
end
|
||||
|
||||
if status then
|
||||
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||
ffi.metatype(new, mat4_mt)
|
||||
end, function() end)
|
||||
end
|
||||
|
||||
return setmetatable({}, mat4_mt)
|
||||
51
libs/cpml/mesh.lua
Normal file
@@ -0,0 +1,51 @@
|
||||
--- Mesh utilities
|
||||
-- @module mesh
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local mesh = {}
|
||||
|
||||
-- vertices is an arbitrary list of vec3s
|
||||
function mesh.average(vertices)
|
||||
local out = vec3()
|
||||
for _, v in ipairs(vertices) do
|
||||
out = out + v
|
||||
end
|
||||
return out / #vertices
|
||||
end
|
||||
|
||||
-- triangle[1] is a vec3
|
||||
-- triangle[2] is a vec3
|
||||
-- triangle[3] is a vec3
|
||||
function mesh.normal(triangle)
|
||||
local ba = triangle[2] - triangle[1]
|
||||
local ca = triangle[3] - triangle[1]
|
||||
return ba:cross(ca):normalize()
|
||||
end
|
||||
|
||||
-- triangle[1] is a vec3
|
||||
-- triangle[2] is a vec3
|
||||
-- triangle[3] is a vec3
|
||||
function mesh.plane_from_triangle(triangle)
|
||||
return {
|
||||
origin = triangle[1],
|
||||
normal = mesh.normal(triangle)
|
||||
}
|
||||
end
|
||||
|
||||
-- plane.origin is a vec3
|
||||
-- plane.normal is a vec3
|
||||
-- direction is a vec3
|
||||
function mesh.is_front_facing(plane, direction)
|
||||
return plane.normal:dot(direction) >= 0
|
||||
end
|
||||
|
||||
-- point is a vec3
|
||||
-- plane.origin is a vec3
|
||||
-- plane.normal is a vec3
|
||||
-- plane.dot is a number
|
||||
function mesh.signed_distance(point, plane)
|
||||
return point:dot(plane.normal) - plane.normal:dot(plane.origin)
|
||||
end
|
||||
|
||||
return mesh
|
||||
634
libs/cpml/octree.lua
Normal file
@@ -0,0 +1,634 @@
|
||||
-- https://github.com/Nition/UnityOctree
|
||||
-- https://github.com/Nition/UnityOctree/blob/master/LICENCE
|
||||
-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctree.cs
|
||||
-- https://github.com/Nition/UnityOctree/blob/master/Scripts/BoundsOctreeNode.cs
|
||||
|
||||
--- Octree
|
||||
-- @module octree
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local intersect = require(modules .. "intersect")
|
||||
local mat4 = require(modules .. "mat4")
|
||||
local utils = require(modules .. "utils")
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local Octree = {}
|
||||
local OctreeNode = {}
|
||||
local Node
|
||||
|
||||
Octree.__index = Octree
|
||||
OctreeNode.__index = OctreeNode
|
||||
|
||||
--== Octree ==--
|
||||
|
||||
--- Constructor for the bounds octree.
|
||||
-- @param initialWorldSize Size of the sides of the initial node, in metres. The octree will never shrink smaller than this
|
||||
-- @param initialWorldPos Position of the centre of the initial node
|
||||
-- @param minNodeSize Nodes will stop splitting if the new nodes would be smaller than this (metres)
|
||||
-- @param looseness Clamped between 1 and 2. Values > 1 let nodes overlap
|
||||
local function new(initialWorldSize, initialWorldPos, minNodeSize, looseness)
|
||||
local tree = setmetatable({}, Octree)
|
||||
|
||||
if minNodeSize > initialWorldSize then
|
||||
print("Minimum node size must be at least as big as the initial world size. Was: " .. minNodeSize .. " Adjusted to: " .. initialWorldSize)
|
||||
minNodeSize = initialWorldSize
|
||||
end
|
||||
|
||||
-- The total amount of objects currently in the tree
|
||||
tree.count = 0
|
||||
|
||||
-- Size that the octree was on creation
|
||||
tree.initialSize = initialWorldSize
|
||||
|
||||
-- Minimum side length that a node can be - essentially an alternative to having a max depth
|
||||
tree.minSize = minNodeSize
|
||||
|
||||
-- Should be a value between 1 and 2. A multiplier for the base size of a node.
|
||||
-- 1.0 is a "normal" octree, while values > 1 have overlap
|
||||
tree.looseness = utils.clamp(looseness, 1, 2)
|
||||
|
||||
-- Root node of the octree
|
||||
tree.rootNode = Node(tree.initialSize, tree.minSize, tree.looseness, initialWorldPos)
|
||||
|
||||
return tree
|
||||
end
|
||||
|
||||
--- Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
|
||||
-- @param xDir X direction of growth. 1 or -1
|
||||
-- @param yDir Y direction of growth. 1 or -1
|
||||
-- @param zDir Z direction of growth. 1 or -1
|
||||
-- @return Octant where the root node should be
|
||||
local function get_root_pos_index(xDir, yDir, zDir)
|
||||
local result = xDir > 0 and 1 or 0
|
||||
if yDir < 0 then return result + 4 end
|
||||
if zDir > 0 then return result + 2 end
|
||||
end
|
||||
|
||||
--- Add an object.
|
||||
-- @param obj Object to add
|
||||
-- @param objBounds 3D bounding box around the object
|
||||
function Octree:add(obj, objBounds)
|
||||
-- Add object or expand the octree until it can be added
|
||||
local count = 0 -- Safety check against infinite/excessive growth
|
||||
|
||||
while not self.rootNode:add(obj, objBounds) do
|
||||
count = count + 1
|
||||
self:grow(objBounds.center - self.rootNode.center)
|
||||
|
||||
if count > 20 then
|
||||
print("Aborted Add operation as it seemed to be going on forever (" .. count - 1 .. ") attempts at growing the octree.")
|
||||
return
|
||||
end
|
||||
|
||||
self.count = self.count + 1
|
||||
end
|
||||
end
|
||||
|
||||
--- Remove an object. Makes the assumption that the object only exists once in the tree.
|
||||
-- @param obj Object to remove
|
||||
-- @return bool True if the object was removed successfully
|
||||
function Octree:remove(obj)
|
||||
local removed = self.rootNode:remove(obj)
|
||||
|
||||
-- See if we can shrink the octree down now that we've removed the item
|
||||
if removed then
|
||||
self.count = self.count - 1
|
||||
self:shrink()
|
||||
end
|
||||
|
||||
return removed
|
||||
end
|
||||
|
||||
--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
|
||||
-- @param checkBounds bounds to check
|
||||
-- @return bool True if there was a collision
|
||||
function Octree:is_colliding(checkBounds)
|
||||
return self.rootNode:is_colliding(checkBounds)
|
||||
end
|
||||
|
||||
--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
|
||||
-- @param checkBounds bounds to check
|
||||
-- @return table Objects that intersect with the specified bounds
|
||||
function Octree:get_colliding(checkBounds)
|
||||
return self.rootNode:get_colliding(checkBounds)
|
||||
end
|
||||
|
||||
--- Cast a ray through the node and its children
|
||||
-- @param ray Ray with a position and a direction
|
||||
-- @param func Function to execute on any objects within child nodes
|
||||
-- @param out Table to store results of func in
|
||||
-- @return boolean True if an intersect detected
|
||||
function Octree:cast_ray(ray, func, out)
|
||||
assert(func)
|
||||
return self.rootNode:cast_ray(ray, func, out)
|
||||
end
|
||||
|
||||
--- Draws node boundaries visually for debugging.
|
||||
function Octree:draw_bounds(cube)
|
||||
self.rootNode:draw_bounds(cube)
|
||||
end
|
||||
|
||||
--- Draws the bounds of all objects in the tree visually for debugging.
|
||||
function Octree:draw_objects(cube, filter)
|
||||
self.rootNode:draw_objects(cube, filter)
|
||||
end
|
||||
|
||||
--- Grow the octree to fit in all objects.
|
||||
-- @param direction Direction to grow
|
||||
function Octree:grow(direction)
|
||||
local xDirection = direction.x >= 0 and 1 or -1
|
||||
local yDirection = direction.y >= 0 and 1 or -1
|
||||
local zDirection = direction.z >= 0 and 1 or -1
|
||||
|
||||
local oldRoot = self.rootNode
|
||||
local half = self.rootNode.baseLength / 2
|
||||
local newLength = self.rootNode.baseLength * 2
|
||||
local newCenter = self.rootNode.center + vec3(xDirection * half, yDirection * half, zDirection * half)
|
||||
|
||||
-- Create a new, bigger octree root node
|
||||
self.rootNode = Node(newLength, self.minSize, self.looseness, newCenter)
|
||||
|
||||
-- Create 7 new octree children to go with the old root as children of the new root
|
||||
local rootPos = get_root_pos_index(xDirection, yDirection, zDirection)
|
||||
local children = {}
|
||||
|
||||
for i = 0, 7 do
|
||||
if i == rootPos then
|
||||
children[i+1] = oldRoot
|
||||
else
|
||||
xDirection = i % 2 == 0 and -1 or 1
|
||||
yDirection = i > 3 and -1 or 1
|
||||
zDirection = (i < 2 or (i > 3 and i < 6)) and -1 or 1
|
||||
children[i+1] = Node(self.rootNode.baseLength, self.minSize, self.looseness, newCenter + vec3(xDirection * half, yDirection * half, zDirection * half))
|
||||
end
|
||||
end
|
||||
|
||||
-- Attach the new children to the new root node
|
||||
self.rootNode:set_children(children)
|
||||
end
|
||||
|
||||
--- Shrink the octree if possible, else leave it the same.
|
||||
function Octree:shrink()
|
||||
self.rootNode = self.rootNode:shrink_if_possible(self.initialSize)
|
||||
end
|
||||
|
||||
--== Octree Node ==--
|
||||
|
||||
--- Constructor.
|
||||
-- @param baseLength Length of this node, not taking looseness into account
|
||||
-- @param minSize Minimum size of nodes in this octree
|
||||
-- @param looseness Multiplier for baseLengthVal to get the actual size
|
||||
-- @param center Centre position of this node
|
||||
local function new_node(baseLength, minSize, looseness, center)
|
||||
local node = setmetatable({}, OctreeNode)
|
||||
|
||||
-- Objects in this node
|
||||
node.objects = {}
|
||||
|
||||
-- Child nodes
|
||||
node.children = {}
|
||||
|
||||
-- If there are already numObjectsAllowed in a node, we split it into children
|
||||
-- A generally good number seems to be something around 8-15
|
||||
node.numObjectsAllowed = 8
|
||||
|
||||
node:set_values(baseLength, minSize, looseness, center)
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
local function new_bound(center, size)
|
||||
return {
|
||||
center = center,
|
||||
size = size,
|
||||
min = center - (size / 2),
|
||||
max = center + (size / 2)
|
||||
}
|
||||
end
|
||||
|
||||
--- Add an object.
|
||||
-- @param obj Object to add
|
||||
-- @param objBounds 3D bounding box around the object
|
||||
-- @return boolean True if the object fits entirely within this node
|
||||
function OctreeNode:add(obj, objBounds)
|
||||
if not intersect.encapsulate_aabb(self.bounds, objBounds) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- We know it fits at this level if we've got this far
|
||||
-- Just add if few objects are here, or children would be below min size
|
||||
if #self.objects < self.numObjectsAllowed
|
||||
or self.baseLength / 2 < self.minSize then
|
||||
table.insert(self.objects, {
|
||||
data = obj,
|
||||
bounds = objBounds
|
||||
})
|
||||
else
|
||||
-- Fits at this level, but we can go deeper. Would it fit there?
|
||||
|
||||
local best_fit_child
|
||||
|
||||
-- Create the 8 children
|
||||
if #self.children == 0 then
|
||||
self:split()
|
||||
|
||||
if #self.children == 0 then
|
||||
print("Child creation failed for an unknown reason. Early exit.")
|
||||
return false
|
||||
end
|
||||
|
||||
-- Now that we have the new children, see if this node's existing objects would fit there
|
||||
for i = #self.objects, 1, -1 do
|
||||
local object = self.objects[i]
|
||||
-- Find which child the object is closest to based on where the
|
||||
-- object's center is located in relation to the octree's center.
|
||||
best_fit_child = self:best_fit_child(object.bounds)
|
||||
|
||||
-- Does it fit?
|
||||
if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, object.bounds) then
|
||||
self.children[best_fit_child]:add(object.data, object.bounds) -- Go a level deeper
|
||||
table.remove(self.objects, i) -- Remove from here
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Now handle the new object we're adding now
|
||||
best_fit_child = self:best_fit_child(objBounds)
|
||||
|
||||
if intersect.encapsulate_aabb(self.children[best_fit_child].bounds, objBounds) then
|
||||
self.children[best_fit_child]:add(obj, objBounds)
|
||||
else
|
||||
table.insert(self.objects, {
|
||||
data = obj,
|
||||
bounds = objBounds
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Remove an object. Makes the assumption that the object only exists once in the tree.
|
||||
-- @param obj Object to remove
|
||||
-- @return boolean True if the object was removed successfully
|
||||
function OctreeNode:remove(obj)
|
||||
local removed = false
|
||||
|
||||
for i, object in ipairs(self.objects) do
|
||||
if object == obj then
|
||||
removed = table.remove(self.objects, i) and true or false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not removed then
|
||||
for _, child in ipairs(self.children) do
|
||||
removed = child:remove(obj)
|
||||
if removed then break end
|
||||
end
|
||||
end
|
||||
|
||||
if removed then
|
||||
-- Check if we should merge nodes now that we've removed an item
|
||||
if self:should_merge() then
|
||||
self:merge()
|
||||
end
|
||||
end
|
||||
|
||||
return removed
|
||||
end
|
||||
|
||||
--- Check if the specified bounds intersect with anything in the tree. See also: get_colliding.
|
||||
-- @param checkBounds Bounds to check
|
||||
-- @return boolean True if there was a collision
|
||||
function OctreeNode:is_colliding(checkBounds)
|
||||
-- Are the input bounds at least partially in this node?
|
||||
if not intersect.aabb_aabb(self.bounds, checkBounds) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check against any objects in this node
|
||||
for _, object in ipairs(self.objects) do
|
||||
if intersect.aabb_aabb(object.bounds, checkBounds) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Check children
|
||||
for _, child in ipairs(self.children) do
|
||||
if child:is_colliding(checkBounds) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: is_colliding.
|
||||
-- @param checkBounds Bounds to check. Passing by ref as it improve performance with structs
|
||||
-- @param results List results
|
||||
-- @return table Objects that intersect with the specified bounds
|
||||
function OctreeNode:get_colliding(checkBounds, results)
|
||||
results = results or {}
|
||||
|
||||
-- Are the input bounds at least partially in this node?
|
||||
if not intersect.aabb_aabb(self.bounds, checkBounds) then
|
||||
return results
|
||||
end
|
||||
|
||||
-- Check against any objects in this node
|
||||
for _, object in ipairs(self.objects) do
|
||||
if intersect.aabb_aabb(object.bounds, checkBounds) then
|
||||
table.insert(results, object.data)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check children
|
||||
for _, child in ipairs(self.children) do
|
||||
results = child:get_colliding(checkBounds, results)
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--- Cast a ray through the node and its children
|
||||
-- @param ray Ray with a position and a direction
|
||||
-- @param func Function to execute on any objects within child nodes
|
||||
-- @param out Table to store results of func in
|
||||
-- @param depth (used internally)
|
||||
-- @return boolean True if an intersect is detected
|
||||
function OctreeNode:cast_ray(ray, func, out, depth)
|
||||
depth = depth or 1
|
||||
|
||||
if intersect.ray_aabb(ray, self.bounds) then
|
||||
if #self.objects > 0 then
|
||||
local hit = func(ray, self.objects, out)
|
||||
|
||||
if hit then
|
||||
return hit
|
||||
end
|
||||
end
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
local hit = child:cast_ray(ray, func, out, depth + 1)
|
||||
|
||||
if hit then
|
||||
return hit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Set the 8 children of this octree.
|
||||
-- @param childOctrees The 8 new child nodes
|
||||
function OctreeNode:set_children(childOctrees)
|
||||
if #childOctrees ~= 8 then
|
||||
print("Child octree array must be length 8. Was length: " .. #childOctrees)
|
||||
return
|
||||
end
|
||||
|
||||
self.children = childOctrees
|
||||
end
|
||||
|
||||
--- We can shrink the octree if:
|
||||
--- - This node is >= double minLength in length
|
||||
--- - All objects in the root node are within one octant
|
||||
--- - This node doesn't have children, or does but 7/8 children are empty
|
||||
--- We can also shrink it if there are no objects left at all!
|
||||
-- @param minLength Minimum dimensions of a node in this octree
|
||||
-- @return table The new root, or the existing one if we didn't shrink
|
||||
function OctreeNode:shrink_if_possible(minLength)
|
||||
if self.baseLength < 2 * minLength then
|
||||
return self
|
||||
end
|
||||
|
||||
if #self.objects == 0 and #self.children == 0 then
|
||||
return self
|
||||
end
|
||||
|
||||
-- Check objects in root
|
||||
local bestFit = 0
|
||||
|
||||
for i, object in ipairs(self.objects) do
|
||||
local newBestFit = self:best_fit_child(object.bounds)
|
||||
|
||||
if i == 1 or newBestFit == bestFit then
|
||||
-- In same octant as the other(s). Does it fit completely inside that octant?
|
||||
if intersect.encapsulate_aabb(self.childBounds[newBestFit], object.bounds) then
|
||||
if bestFit < 1 then
|
||||
bestFit = newBestFit
|
||||
end
|
||||
else
|
||||
-- Nope, so we can't reduce. Otherwise we continue
|
||||
return self
|
||||
end
|
||||
else
|
||||
return self -- Can't reduce - objects fit in different octants
|
||||
end
|
||||
end
|
||||
|
||||
-- Check objects in children if there are any
|
||||
if #self.children > 0 then
|
||||
local childHadContent = false
|
||||
|
||||
for i, child in ipairs(self.children) do
|
||||
if child:has_any_objects() then
|
||||
if childHadContent then
|
||||
return self -- Can't shrink - another child had content already
|
||||
end
|
||||
|
||||
if bestFit > 0 and bestFit ~= i then
|
||||
return self -- Can't reduce - objects in root are in a different octant to objects in child
|
||||
end
|
||||
|
||||
childHadContent = true
|
||||
bestFit = i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Can reduce
|
||||
if #self.children == 0 then
|
||||
-- We don't have any children, so just shrink this node to the new size
|
||||
-- We already know that everything will still fit in it
|
||||
self:set_values(self.baseLength / 2, self.minSize, self.looseness, self.childBounds[bestFit].center)
|
||||
return self
|
||||
end
|
||||
|
||||
-- We have children. Use the appropriate child as the new root node
|
||||
return self.children[bestFit]
|
||||
end
|
||||
|
||||
--- Set values for this node.
|
||||
-- @param baseLength Length of this node, not taking looseness into account
|
||||
-- @param minSize Minimum size of nodes in this octree
|
||||
-- @param looseness Multiplier for baseLengthVal to get the actual size
|
||||
-- @param center Centre position of this node
|
||||
function OctreeNode:set_values(baseLength, minSize, looseness, center)
|
||||
-- Length of this node if it has a looseness of 1.0
|
||||
self.baseLength = baseLength
|
||||
|
||||
-- Minimum size for a node in this octree
|
||||
self.minSize = minSize
|
||||
|
||||
-- Looseness value for this node
|
||||
self.looseness = looseness
|
||||
|
||||
-- Centre of this node
|
||||
self.center = center
|
||||
|
||||
-- Actual length of sides, taking the looseness value into account
|
||||
self.adjLength = self.looseness * self.baseLength
|
||||
|
||||
-- Create the bounding box.
|
||||
self.size = vec3(self.adjLength, self.adjLength, self.adjLength)
|
||||
|
||||
-- Bounding box that represents this node
|
||||
self.bounds = new_bound(self.center, self.size)
|
||||
|
||||
self.quarter = self.baseLength / 4
|
||||
self.childActualLength = (self.baseLength / 2) * self.looseness
|
||||
self.childActualSize = vec3(self.childActualLength, self.childActualLength, self.childActualLength)
|
||||
|
||||
-- Bounds of potential children to this node. These are actual size (with looseness taken into account), not base size
|
||||
self.childBounds = {
|
||||
new_bound(self.center + vec3(-self.quarter, self.quarter, -self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3( self.quarter, self.quarter, -self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3(-self.quarter, self.quarter, self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3( self.quarter, self.quarter, self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3(-self.quarter, -self.quarter, -self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3( self.quarter, -self.quarter, -self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3(-self.quarter, -self.quarter, self.quarter), self.childActualSize),
|
||||
new_bound(self.center + vec3( self.quarter, -self.quarter, self.quarter), self.childActualSize)
|
||||
}
|
||||
end
|
||||
|
||||
--- Splits the octree into eight children.
|
||||
function OctreeNode:split()
|
||||
if #self.children > 0 then return end
|
||||
|
||||
local quarter = self.baseLength / 4
|
||||
local newLength = self.baseLength / 2
|
||||
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, quarter, -quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, quarter, -quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, quarter, quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, quarter, quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, -quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, -quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3(-quarter, -quarter, quarter)))
|
||||
table.insert(self.children, Node(newLength, self.minSize, self.looseness, self.center + vec3( quarter, -quarter, quarter)))
|
||||
end
|
||||
|
||||
--- Merge all children into this node - the opposite of Split.
|
||||
--- Note: We only have to check one level down since a merge will never happen if the children already have children,
|
||||
--- since THAT won't happen unless there are already too many objects to merge.
|
||||
function OctreeNode:merge()
|
||||
for _, child in ipairs(self.children) do
|
||||
for _, object in ipairs(child.objects) do
|
||||
table.insert(self.objects, object)
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove the child nodes (and the objects in them - they've been added elsewhere now)
|
||||
self.children = {}
|
||||
end
|
||||
|
||||
--- Find which child node this object would be most likely to fit in.
|
||||
-- @param objBounds The object's bounds
|
||||
-- @return number One of the eight child octants
|
||||
function OctreeNode:best_fit_child(objBounds)
|
||||
return (objBounds.center.x <= self.center.x and 0 or 1) + (objBounds.center.y >= self.center.y and 0 or 4) + (objBounds.center.z <= self.center.z and 0 or 2) + 1
|
||||
end
|
||||
|
||||
--- Checks if there are few enough objects in this node and its children that the children should all be merged into this.
|
||||
-- @return boolean True there are less or the same abount of objects in this and its children than numObjectsAllowed
|
||||
function OctreeNode:should_merge()
|
||||
local totalObjects = #self.objects
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
if #child.children > 0 then
|
||||
-- If any of the *children* have children, there are definitely too many to merge,
|
||||
-- or the child would have been merged already
|
||||
return false
|
||||
end
|
||||
|
||||
totalObjects = totalObjects + #child.objects
|
||||
end
|
||||
|
||||
return totalObjects <= self.numObjectsAllowed
|
||||
end
|
||||
|
||||
--- Checks if this node or anything below it has something in it.
|
||||
-- @return boolean True if this node or any of its children, grandchildren etc have something in the
|
||||
function OctreeNode:has_any_objects()
|
||||
if #self.objects > 0 then return true end
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
if child:has_any_objects() then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Draws node boundaries visually for debugging.
|
||||
-- @param cube Cube model to draw
|
||||
-- @param depth Used for recurcive calls to this method
|
||||
function OctreeNode:draw_bounds(cube, depth)
|
||||
depth = depth or 0
|
||||
local tint = depth / 7 -- Will eventually get values > 1. Color rounds to 1 automatically
|
||||
|
||||
love.graphics.setColor(tint * 255, 0, (1 - tint) * 255)
|
||||
local m = mat4()
|
||||
:translate(self.center)
|
||||
:scale(vec3(self.adjLength, self.adjLength, self.adjLength))
|
||||
|
||||
love.graphics.updateMatrix("transform", m)
|
||||
love.graphics.setWireframe(true)
|
||||
love.graphics.draw(cube)
|
||||
love.graphics.setWireframe(false)
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
child:draw_bounds(cube, depth + 1)
|
||||
end
|
||||
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
end
|
||||
|
||||
--- Draws the bounds of all objects in the tree visually for debugging.
|
||||
-- @param cube Cube model to draw
|
||||
-- @param filter a function returning true or false to determine visibility.
|
||||
function OctreeNode:draw_objects(cube, filter)
|
||||
local tint = self.baseLength / 20
|
||||
love.graphics.setColor(0, (1 - tint) * 255, tint * 255, 63)
|
||||
|
||||
for _, object in ipairs(self.objects) do
|
||||
if filter and filter(object.data) or not filter then
|
||||
local m = mat4()
|
||||
:translate(object.bounds.center)
|
||||
:scale(object.bounds.size)
|
||||
|
||||
love.graphics.updateMatrix("transform", m)
|
||||
love.graphics.draw(cube)
|
||||
end
|
||||
end
|
||||
|
||||
for _, child in ipairs(self.children) do
|
||||
child:draw_objects(cube, filter)
|
||||
end
|
||||
|
||||
love.graphics.setColor(255, 255, 255)
|
||||
end
|
||||
|
||||
Node = setmetatable({
|
||||
new = new_node
|
||||
}, {
|
||||
__call = function(_, ...) return new_node(...) end
|
||||
})
|
||||
|
||||
return setmetatable({
|
||||
new = new
|
||||
}, {
|
||||
__call = function(_, ...) return new(...) end
|
||||
})
|
||||
498
libs/cpml/quat.lua
Normal file
@@ -0,0 +1,498 @@
|
||||
--- A quaternion and associated utilities.
|
||||
-- @module quat
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local constants = require(modules .. "constants")
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local precond = require(modules .. "_private_precond")
|
||||
local private = require(modules .. "_private_utils")
|
||||
local DOT_THRESHOLD = constants.DOT_THRESHOLD
|
||||
local DBL_EPSILON = constants.DBL_EPSILON
|
||||
local acos = math.acos
|
||||
local cos = math.cos
|
||||
local sin = math.sin
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
local sqrt = math.sqrt
|
||||
local quat = {}
|
||||
local quat_mt = {}
|
||||
|
||||
-- Private constructor.
|
||||
local function new(x, y, z, w)
|
||||
return setmetatable({
|
||||
x = x or 0,
|
||||
y = y or 0,
|
||||
z = z or 0,
|
||||
w = w or 1
|
||||
}, quat_mt)
|
||||
end
|
||||
|
||||
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||
local status, ffi
|
||||
if type(jit) == "table" and jit.status() then
|
||||
status, ffi = pcall(require, "ffi")
|
||||
if status then
|
||||
ffi.cdef "typedef struct { double x, y, z, w;} cpml_quat;"
|
||||
new = ffi.typeof("cpml_quat")
|
||||
end
|
||||
end
|
||||
|
||||
-- Statically allocate a temporary variable used in some of our functions.
|
||||
local tmp = new()
|
||||
local qv, uv, uuv = vec3(), vec3(), vec3()
|
||||
|
||||
--- Constants
|
||||
-- @table quat
|
||||
-- @field unit Unit quaternion
|
||||
-- @field zero Empty quaternion
|
||||
quat.unit = new(0, 0, 0, 1)
|
||||
quat.zero = new(0, 0, 0, 0)
|
||||
|
||||
--- The public constructor.
|
||||
-- @param x Can be of two types: </br>
|
||||
-- number x X component
|
||||
-- table {x, y, z, w} or {x=x, y=y, z=z, w=w}
|
||||
-- @tparam number y Y component
|
||||
-- @tparam number z Z component
|
||||
-- @tparam number w W component
|
||||
-- @treturn quat out
|
||||
function quat.new(x, y, z, w)
|
||||
-- number, number, number, number
|
||||
if x and y and z and w then
|
||||
precond.typeof(x, "number", "new: Wrong argument type for x")
|
||||
precond.typeof(y, "number", "new: Wrong argument type for y")
|
||||
precond.typeof(z, "number", "new: Wrong argument type for z")
|
||||
precond.typeof(w, "number", "new: Wrong argument type for w")
|
||||
|
||||
return new(x, y, z, w)
|
||||
|
||||
-- {x, y, z, w} or {x=x, y=y, z=z, w=w}
|
||||
elseif type(x) == "table" then
|
||||
local xx, yy, zz, ww = x.x or x[1], x.y or x[2], x.z or x[3], x.w or x[4]
|
||||
precond.typeof(xx, "number", "new: Wrong argument type for x")
|
||||
precond.typeof(yy, "number", "new: Wrong argument type for y")
|
||||
precond.typeof(zz, "number", "new: Wrong argument type for z")
|
||||
precond.typeof(ww, "number", "new: Wrong argument type for w")
|
||||
|
||||
return new(xx, yy, zz, ww)
|
||||
end
|
||||
|
||||
return new(0, 0, 0, 1)
|
||||
end
|
||||
|
||||
--- Create a quaternion from an angle/axis pair.
|
||||
-- @tparam number angle Angle (in radians)
|
||||
-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis
|
||||
-- @param y axis -- y component of axis (optional, only if x component param used)
|
||||
-- @param z axis -- z component of axis (optional, only if x component param used)
|
||||
-- @treturn quat out
|
||||
function quat.from_angle_axis(angle, axis, a3, a4)
|
||||
if axis and a3 and a4 then
|
||||
local x, y, z = axis, a3, a4
|
||||
local s = sin(angle * 0.5)
|
||||
local c = cos(angle * 0.5)
|
||||
return new(x * s, y * s, z * s, c)
|
||||
else
|
||||
return quat.from_angle_axis(angle, axis.x, axis.y, axis.z)
|
||||
end
|
||||
end
|
||||
|
||||
--- Create a quaternion from a normal/up vector pair.
|
||||
-- @tparam vec3 normal
|
||||
-- @tparam vec3 up (optional)
|
||||
-- @treturn quat out
|
||||
function quat.from_direction(normal, up)
|
||||
local u = up or vec3.unit_z
|
||||
local n = normal:normalize()
|
||||
local a = u:cross(n)
|
||||
local d = u:dot(n)
|
||||
return new(a.x, a.y, a.z, d + 1)
|
||||
end
|
||||
|
||||
--- Clone a quaternion.
|
||||
-- @tparam quat a Quaternion to clone
|
||||
-- @treturn quat out
|
||||
function quat.clone(a)
|
||||
return new(a.x, a.y, a.z, a.w)
|
||||
end
|
||||
|
||||
--- Add two quaternions.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam quat b Right hand operand
|
||||
-- @treturn quat out
|
||||
function quat.add(a, b)
|
||||
return new(
|
||||
a.x + b.x,
|
||||
a.y + b.y,
|
||||
a.z + b.z,
|
||||
a.w + b.w
|
||||
)
|
||||
end
|
||||
|
||||
--- Subtract a quaternion from another.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam quat b Right hand operand
|
||||
-- @treturn quat out
|
||||
function quat.sub(a, b)
|
||||
return new(
|
||||
a.x - b.x,
|
||||
a.y - b.y,
|
||||
a.z - b.z,
|
||||
a.w - b.w
|
||||
)
|
||||
end
|
||||
|
||||
--- Multiply two quaternions.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam quat b Right hand operand
|
||||
-- @treturn quat quaternion equivalent to "apply b, then a"
|
||||
function quat.mul(a, b)
|
||||
return new(
|
||||
a.x * b.w + a.w * b.x + a.y * b.z - a.z * b.y,
|
||||
a.y * b.w + a.w * b.y + a.z * b.x - a.x * b.z,
|
||||
a.z * b.w + a.w * b.z + a.x * b.y - a.y * b.x,
|
||||
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
|
||||
)
|
||||
end
|
||||
|
||||
--- Multiply a quaternion and a vec3.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function quat.mul_vec3(a, b)
|
||||
qv.x = a.x
|
||||
qv.y = a.y
|
||||
qv.z = a.z
|
||||
uv = qv:cross(b)
|
||||
uuv = qv:cross(uv)
|
||||
return b + ((uv * a.w) + uuv) * 2
|
||||
end
|
||||
|
||||
--- Raise a normalized quaternion to a scalar power.
|
||||
-- @tparam quat a Left hand operand (should be a unit quaternion)
|
||||
-- @tparam number s Right hand operand
|
||||
-- @treturn quat out
|
||||
function quat.pow(a, s)
|
||||
-- Do it as a slerp between identity and a (code borrowed from slerp)
|
||||
if a.w < 0 then
|
||||
a = -a
|
||||
end
|
||||
local dot = a.w
|
||||
|
||||
dot = min(max(dot, -1), 1)
|
||||
|
||||
local theta = acos(dot) * s
|
||||
local c = new(a.x, a.y, a.z, 0):normalize() * sin(theta)
|
||||
c.w = cos(theta)
|
||||
return c
|
||||
end
|
||||
|
||||
--- Normalize a quaternion.
|
||||
-- @tparam quat a Quaternion to normalize
|
||||
-- @treturn quat out
|
||||
function quat.normalize(a)
|
||||
if a:is_zero() then
|
||||
return new(0, 0, 0, 0)
|
||||
end
|
||||
return a:scale(1 / a:len())
|
||||
end
|
||||
|
||||
--- Get the dot product of two quaternions.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam quat b Right hand operand
|
||||
-- @treturn number dot
|
||||
function quat.dot(a, b)
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
|
||||
end
|
||||
|
||||
--- Return the length of a quaternion.
|
||||
-- @tparam quat a Quaternion to get length of
|
||||
-- @treturn number len
|
||||
function quat.len(a)
|
||||
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w)
|
||||
end
|
||||
|
||||
--- Return the squared length of a quaternion.
|
||||
-- @tparam quat a Quaternion to get length of
|
||||
-- @treturn number len
|
||||
function quat.len2(a)
|
||||
return a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w
|
||||
end
|
||||
|
||||
--- Multiply a quaternion by a scalar.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam number s Right hand operand
|
||||
-- @treturn quat out
|
||||
function quat.scale(a, s)
|
||||
return new(
|
||||
a.x * s,
|
||||
a.y * s,
|
||||
a.z * s,
|
||||
a.w * s
|
||||
)
|
||||
end
|
||||
|
||||
--- Alias of from_angle_axis.
|
||||
-- @tparam number angle Angle (in radians)
|
||||
-- @param axis/x -- Can be of two types, a vec3 axis, or the x component of that axis
|
||||
-- @param y axis -- y component of axis (optional, only if x component param used)
|
||||
-- @param z axis -- z component of axis (optional, only if x component param used)
|
||||
-- @treturn quat out
|
||||
function quat.rotate(angle, axis, a3, a4)
|
||||
return quat.from_angle_axis(angle, axis, a3, a4)
|
||||
end
|
||||
|
||||
--- Return the conjugate of a quaternion.
|
||||
-- @tparam quat a Quaternion to conjugate
|
||||
-- @treturn quat out
|
||||
function quat.conjugate(a)
|
||||
return new(-a.x, -a.y, -a.z, a.w)
|
||||
end
|
||||
|
||||
--- Return the inverse of a quaternion.
|
||||
-- @tparam quat a Quaternion to invert
|
||||
-- @treturn quat out
|
||||
function quat.inverse(a)
|
||||
tmp.x = -a.x
|
||||
tmp.y = -a.y
|
||||
tmp.z = -a.z
|
||||
tmp.w = a.w
|
||||
return tmp:normalize()
|
||||
end
|
||||
|
||||
--- Return the reciprocal of a quaternion.
|
||||
-- @tparam quat a Quaternion to reciprocate
|
||||
-- @treturn quat out
|
||||
function quat.reciprocal(a)
|
||||
if a:is_zero() then
|
||||
error("Cannot reciprocate a zero quaternion")
|
||||
return false
|
||||
end
|
||||
|
||||
tmp.x = -a.x
|
||||
tmp.y = -a.y
|
||||
tmp.z = -a.z
|
||||
tmp.w = a.w
|
||||
|
||||
return tmp:scale(1 / a:len2())
|
||||
end
|
||||
|
||||
--- Lerp between two quaternions.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam quat b Right hand operand
|
||||
-- @tparam number s Step value
|
||||
-- @treturn quat out
|
||||
function quat.lerp(a, b, s)
|
||||
return (a + (b - a) * s):normalize()
|
||||
end
|
||||
|
||||
--- Slerp between two quaternions.
|
||||
-- @tparam quat a Left hand operand
|
||||
-- @tparam quat b Right hand operand
|
||||
-- @tparam number s Step value
|
||||
-- @treturn quat out
|
||||
function quat.slerp(a, b, s)
|
||||
local dot = a:dot(b)
|
||||
|
||||
if dot < 0 then
|
||||
a = -a
|
||||
dot = -dot
|
||||
end
|
||||
|
||||
if dot > DOT_THRESHOLD then
|
||||
return a:lerp(b, s)
|
||||
end
|
||||
|
||||
dot = min(max(dot, -1), 1)
|
||||
|
||||
local theta = acos(dot) * s
|
||||
local c = (b - a * dot):normalize()
|
||||
return a * cos(theta) + c * sin(theta)
|
||||
end
|
||||
|
||||
--- Unpack a quaternion into individual components.
|
||||
-- @tparam quat a Quaternion to unpack
|
||||
-- @treturn number x
|
||||
-- @treturn number y
|
||||
-- @treturn number z
|
||||
-- @treturn number w
|
||||
function quat.unpack(a)
|
||||
return a.x, a.y, a.z, a.w
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a quat.
|
||||
-- @tparam quat a Quaternion to be tested
|
||||
-- @treturn boolean is_quat
|
||||
function quat.is_quat(a)
|
||||
if type(a) == "cdata" then
|
||||
return ffi.istype("cpml_quat", a)
|
||||
end
|
||||
|
||||
return
|
||||
type(a) == "table" and
|
||||
type(a.x) == "number" and
|
||||
type(a.y) == "number" and
|
||||
type(a.z) == "number" and
|
||||
type(a.w) == "number"
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a zero quat.
|
||||
-- @tparam quat a Quaternion to be tested
|
||||
-- @treturn boolean is_zero
|
||||
function quat.is_zero(a)
|
||||
return
|
||||
a.x == 0 and
|
||||
a.y == 0 and
|
||||
a.z == 0 and
|
||||
a.w == 0
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a real quat.
|
||||
-- @tparam quat a Quaternion to be tested
|
||||
-- @treturn boolean is_real
|
||||
function quat.is_real(a)
|
||||
return
|
||||
a.x == 0 and
|
||||
a.y == 0 and
|
||||
a.z == 0
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not an imaginary quat.
|
||||
-- @tparam quat a Quaternion to be tested
|
||||
-- @treturn boolean is_imaginary
|
||||
function quat.is_imaginary(a)
|
||||
return a.w == 0
|
||||
end
|
||||
|
||||
--- Return whether any component is NaN
|
||||
-- @tparam quat a Quaternion to be tested
|
||||
-- @treturn boolean if x,y,z, or w is NaN
|
||||
function quat.has_nan(a)
|
||||
return private.is_nan(a.x) or
|
||||
private.is_nan(a.y) or
|
||||
private.is_nan(a.z) or
|
||||
private.is_nan(a.w)
|
||||
end
|
||||
|
||||
--- Convert a quaternion into an angle plus axis components.
|
||||
-- @tparam quat a Quaternion to convert
|
||||
-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,0,0,1)
|
||||
-- @treturn number angle
|
||||
-- @treturn x axis-x
|
||||
-- @treturn y axis-y
|
||||
-- @treturn z axis-z
|
||||
function quat.to_angle_axis_unpack(a, identityAxis)
|
||||
if a.w > 1 or a.w < -1 then
|
||||
a = a:normalize()
|
||||
end
|
||||
|
||||
-- If length of xyz components is less than DBL_EPSILON, this is zero or close enough (an identity quaternion)
|
||||
-- Normally an identity quat would return a nonsense answer, so we return an arbitrary zero rotation early.
|
||||
-- FIXME: Is it safe to assume there are *no* valid quaternions with nonzero degenerate lengths?
|
||||
if a.x*a.x + a.y*a.y + a.z*a.z < constants.DBL_EPSILON*constants.DBL_EPSILON then
|
||||
if identityAxis then
|
||||
return 0,identityAxis:unpack()
|
||||
else
|
||||
return 0,0,0,1
|
||||
end
|
||||
end
|
||||
|
||||
local x, y, z
|
||||
local angle = 2 * acos(a.w)
|
||||
local s = sqrt(1 - a.w * a.w)
|
||||
|
||||
if s < DBL_EPSILON then
|
||||
x = a.x
|
||||
y = a.y
|
||||
z = a.z
|
||||
else
|
||||
x = a.x / s
|
||||
y = a.y / s
|
||||
z = a.z / s
|
||||
end
|
||||
|
||||
return angle, x, y, z
|
||||
end
|
||||
|
||||
--- Convert a quaternion into an angle/axis pair.
|
||||
-- @tparam quat a Quaternion to convert
|
||||
-- @tparam identityAxis vec3 of axis to use on identity/degenerate quaternions (optional, default returns 0,vec3(0,0,1))
|
||||
-- @treturn number angle
|
||||
-- @treturn vec3 axis
|
||||
function quat.to_angle_axis(a, identityAxis)
|
||||
local angle, x, y, z = a:to_angle_axis_unpack(identityAxis)
|
||||
return angle, vec3(x, y, z)
|
||||
end
|
||||
|
||||
--- Convert a quaternion into a vec3.
|
||||
-- @tparam quat a Quaternion to convert
|
||||
-- @treturn vec3 out
|
||||
function quat.to_vec3(a)
|
||||
return vec3(a.x, a.y, a.z)
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam quat a Quaternion to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function quat.to_string(a)
|
||||
return string.format("(%+0.3f,%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z, a.w)
|
||||
end
|
||||
|
||||
quat_mt.__index = quat
|
||||
quat_mt.__tostring = quat.to_string
|
||||
|
||||
function quat_mt.__call(_, x, y, z, w)
|
||||
return quat.new(x, y, z, w)
|
||||
end
|
||||
|
||||
function quat_mt.__unm(a)
|
||||
return a:scale(-1)
|
||||
end
|
||||
|
||||
function quat_mt.__eq(a,b)
|
||||
if not quat.is_quat(a) or not quat.is_quat(b) then
|
||||
return false
|
||||
end
|
||||
return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w
|
||||
end
|
||||
|
||||
function quat_mt.__add(a, b)
|
||||
precond.assert(quat.is_quat(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||
precond.assert(quat.is_quat(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
|
||||
return a:add(b)
|
||||
end
|
||||
|
||||
function quat_mt.__sub(a, b)
|
||||
precond.assert(quat.is_quat(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||
precond.assert(quat.is_quat(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.quat> expected)", type(b))
|
||||
return a:sub(b)
|
||||
end
|
||||
|
||||
function quat_mt.__mul(a, b)
|
||||
precond.assert(quat.is_quat(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||
assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.quat> or <cpml.vec3> or <number> expected)")
|
||||
|
||||
if quat.is_quat(b) then
|
||||
return a:mul(b)
|
||||
end
|
||||
|
||||
if type(b) == "number" then
|
||||
return a:scale(b)
|
||||
end
|
||||
|
||||
return a:mul_vec3(b)
|
||||
end
|
||||
|
||||
function quat_mt.__pow(a, n)
|
||||
precond.assert(quat.is_quat(a), "__pow: Wrong argument type '%s' for left hand operand. (<cpml.quat> expected)", type(a))
|
||||
precond.typeof(n, "number", "__pow: Wrong argument type for right hand operand.")
|
||||
return a:pow(n)
|
||||
end
|
||||
|
||||
if status then
|
||||
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||
ffi.metatype(new, quat_mt)
|
||||
end, function() end)
|
||||
end
|
||||
|
||||
return setmetatable({}, quat_mt)
|
||||
349
libs/cpml/simplex.lua
Normal file
@@ -0,0 +1,349 @@
|
||||
--- Simplex Noise
|
||||
-- @module simplex
|
||||
|
||||
--
|
||||
-- Based on code in "Simplex noise demystified", by Stefan Gustavson
|
||||
-- www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
|
||||
--
|
||||
-- Thanks to Mike Pall for some cleanup and improvements (and for LuaJIT!)
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining
|
||||
-- a copy of this software and associated documentation files (the
|
||||
-- "Software"), to deal in the Software without restriction, including
|
||||
-- without limitation the rights to use, copy, modify, merge, publish,
|
||||
-- distribute, sublicense, and/or sell copies of the Software, and to
|
||||
-- permit persons to whom the Software is furnished to do so, subject to
|
||||
-- the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be
|
||||
-- included in all copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
--
|
||||
-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||
--
|
||||
|
||||
if _G.love and _G.love.math then
|
||||
return love.math.noise
|
||||
end
|
||||
|
||||
-- Bail out with dummy module if FFI is missing.
|
||||
local has_ffi, ffi = pcall(require, "ffi")
|
||||
if not has_ffi then
|
||||
return function()
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
-- Modules --
|
||||
local bit = require("bit")
|
||||
|
||||
-- Imports --
|
||||
local band = bit.band
|
||||
local bor = bit.bor
|
||||
local floor = math.floor
|
||||
local lshift = bit.lshift
|
||||
local max = math.max
|
||||
local rshift = bit.rshift
|
||||
|
||||
-- Permutation of 0-255, replicated to allow easy indexing with sums of two bytes --
|
||||
local Perms = ffi.new("uint8_t[512]", {
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
|
||||
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
|
||||
247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
|
||||
57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
|
||||
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
|
||||
60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
|
||||
65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
|
||||
200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,
|
||||
52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,
|
||||
207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
|
||||
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
||||
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
|
||||
218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
|
||||
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
|
||||
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
|
||||
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
|
||||
})
|
||||
|
||||
-- The above, mod 12 for each element --
|
||||
local Perms12 = ffi.new("uint8_t[512]")
|
||||
|
||||
for i = 0, 255 do
|
||||
local x = Perms[i] % 12
|
||||
|
||||
Perms[i + 256], Perms12[i], Perms12[i + 256] = Perms[i], x, x
|
||||
end
|
||||
|
||||
-- Gradients for 2D, 3D case --
|
||||
local Grads3 = ffi.new("const double[12][3]",
|
||||
{ 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 },
|
||||
{ 1, 0, 1 }, { -1, 0, 1 }, { 1, 0, -1 }, { -1, 0, -1 },
|
||||
{ 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }
|
||||
)
|
||||
|
||||
-- 2D weight contribution
|
||||
local function GetN2(bx, by, x, y)
|
||||
local t = .5 - x * x - y * y
|
||||
local index = Perms12[bx + Perms[by]]
|
||||
|
||||
return max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y)
|
||||
end
|
||||
|
||||
local function simplex_2d(x, y)
|
||||
--[[
|
||||
2D skew factors:
|
||||
F = (math.sqrt(3) - 1) / 2
|
||||
G = (3 - math.sqrt(3)) / 6
|
||||
G2 = 2 * G - 1
|
||||
]]
|
||||
|
||||
-- Skew the input space to determine which simplex cell we are in.
|
||||
local s = (x + y) * 0.366025403 -- F
|
||||
local ix, iy = floor(x + s), floor(y + s)
|
||||
|
||||
-- Unskew the cell origin back to (x, y) space.
|
||||
local t = (ix + iy) * 0.211324865 -- G
|
||||
local x0 = x + t - ix
|
||||
local y0 = y + t - iy
|
||||
|
||||
-- Calculate the contribution from the two fixed corners.
|
||||
-- A step of (1,0) in (i,j) means a step of (1-G,-G) in (x,y), and
|
||||
-- A step of (0,1) in (i,j) means a step of (-G,1-G) in (x,y).
|
||||
ix, iy = band(ix, 255), band(iy, 255)
|
||||
|
||||
local n0 = GetN2(ix, iy, x0, y0)
|
||||
local n2 = GetN2(ix + 1, iy + 1, x0 - 0.577350270, y0 - 0.577350270) -- G2
|
||||
|
||||
--[[
|
||||
Determine other corner based on simplex (equilateral triangle) we are in:
|
||||
if x0 > y0 then
|
||||
ix, x1 = ix + 1, x1 - 1
|
||||
else
|
||||
iy, y1 = iy + 1, y1 - 1
|
||||
end
|
||||
]]
|
||||
local xi = rshift(floor(y0 - x0), 31) -- y0 < x0
|
||||
local n1 = GetN2(ix + xi, iy + (1 - xi), x0 + 0.211324865 - xi, y0 - 0.788675135 + xi) -- x0 + G - xi, y0 + G - (1 - xi)
|
||||
|
||||
-- Add contributions from each corner to get the final noise value.
|
||||
-- The result is scaled to return values in the interval [-1,1].
|
||||
return 70.1480580019 * (n0 + n1 + n2)
|
||||
end
|
||||
|
||||
-- 3D weight contribution
|
||||
local function GetN3(ix, iy, iz, x, y, z)
|
||||
local t = .6 - x * x - y * y - z * z
|
||||
local index = Perms12[ix + Perms[iy + Perms[iz]]]
|
||||
|
||||
return max(0, (t * t) * (t * t)) * (Grads3[index][0] * x + Grads3[index][1] * y + Grads3[index][2] * z)
|
||||
end
|
||||
|
||||
local function simplex_3d(x, y, z)
|
||||
--[[
|
||||
3D skew factors:
|
||||
F = 1 / 3
|
||||
G = 1 / 6
|
||||
G2 = 2 * G
|
||||
G3 = 3 * G - 1
|
||||
]]
|
||||
|
||||
-- Skew the input space to determine which simplex cell we are in.
|
||||
local s = (x + y + z) * 0.333333333 -- F
|
||||
local ix, iy, iz = floor(x + s), floor(y + s), floor(z + s)
|
||||
|
||||
-- Unskew the cell origin back to (x, y, z) space.
|
||||
local t = (ix + iy + iz) * 0.166666667 -- G
|
||||
local x0 = x + t - ix
|
||||
local y0 = y + t - iy
|
||||
local z0 = z + t - iz
|
||||
|
||||
-- Calculate the contribution from the two fixed corners.
|
||||
-- A step of (1,0,0) in (i,j,k) means a step of (1-G,-G,-G) in (x,y,z);
|
||||
-- a step of (0,1,0) in (i,j,k) means a step of (-G,1-G,-G) in (x,y,z);
|
||||
-- a step of (0,0,1) in (i,j,k) means a step of (-G,-G,1-G) in (x,y,z).
|
||||
ix, iy, iz = band(ix, 255), band(iy, 255), band(iz, 255)
|
||||
|
||||
local n0 = GetN3(ix, iy, iz, x0, y0, z0)
|
||||
local n3 = GetN3(ix + 1, iy + 1, iz + 1, x0 - 0.5, y0 - 0.5, z0 - 0.5) -- G3
|
||||
|
||||
--[[
|
||||
Determine other corners based on simplex (skewed tetrahedron) we are in:
|
||||
|
||||
if x0 >= y0 then -- ~A
|
||||
if y0 >= z0 then -- ~A and ~B
|
||||
i1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 1, 0
|
||||
elseif x0 >= z0 then -- ~A and B and ~C
|
||||
i1, j1, k1, i2, j2, k2 = 1, 0, 0, 1, 0, 1
|
||||
else -- ~A and B and C
|
||||
i1, j1, k1, i2, j2, k2 = 0, 0, 1, 1, 0, 1
|
||||
end
|
||||
else -- A
|
||||
if y0 < z0 then -- A and B
|
||||
i1, j1, k1, i2, j2, k2 = 0, 0, 1, 0, 1, 1
|
||||
elseif x0 < z0 then -- A and ~B and C
|
||||
i1, j1, k1, i2, j2, k2 = 0, 1, 0, 0, 1, 1
|
||||
else -- A and ~B and ~C
|
||||
i1, j1, k1, i2, j2, k2 = 0, 1, 0, 1, 1, 0
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
local xLy = rshift(floor(x0 - y0), 31) -- x0 < y0
|
||||
local yLz = rshift(floor(y0 - z0), 31) -- y0 < z0
|
||||
local xLz = rshift(floor(x0 - z0), 31) -- x0 < z0
|
||||
|
||||
local i1 = band(1 - xLy, bor(1 - yLz, 1 - xLz)) -- x0 >= y0 and (y0 >= z0 or x0 >= z0)
|
||||
local j1 = band(xLy, 1 - yLz) -- x0 < y0 and y0 >= z0
|
||||
local k1 = band(yLz, bor(xLy, xLz)) -- y0 < z0 and (x0 < y0 or x0 < z0)
|
||||
|
||||
local i2 = bor(1 - xLy, band(1 - yLz, 1 - xLz)) -- x0 >= y0 or (y0 >= z0 and x0 >= z0)
|
||||
local j2 = bor(xLy, 1 - yLz) -- x0 < y0 or y0 >= z0
|
||||
local k2 = bor(band(1 - xLy, yLz), band(xLy, bor(yLz, xLz))) -- (x0 >= y0 and y0 < z0) or (x0 < y0 and (y0 < z0 or x0 < z0))
|
||||
|
||||
local n1 = GetN3(ix + i1, iy + j1, iz + k1, x0 + 0.166666667 - i1, y0 + 0.166666667 - j1, z0 + 0.166666667 - k1) -- G
|
||||
local n2 = GetN3(ix + i2, iy + j2, iz + k2, x0 + 0.333333333 - i2, y0 + 0.333333333 - j2, z0 + 0.333333333 - k2) -- G2
|
||||
|
||||
-- Add contributions from each corner to get the final noise value.
|
||||
-- The result is scaled to stay just inside [-1,1]
|
||||
return 28.452842 * (n0 + n1 + n2 + n3)
|
||||
end
|
||||
|
||||
-- Gradients for 4D case --
|
||||
local Grads4 = ffi.new("const double[32][4]",
|
||||
{ 0, 1, 1, 1 }, { 0, 1, 1, -1 }, { 0, 1, -1, 1 }, { 0, 1, -1, -1 },
|
||||
{ 0, -1, 1, 1 }, { 0, -1, 1, -1 }, { 0, -1, -1, 1 }, { 0, -1, -1, -1 },
|
||||
{ 1, 0, 1, 1 }, { 1, 0, 1, -1 }, { 1, 0, -1, 1 }, { 1, 0, -1, -1 },
|
||||
{ -1, 0, 1, 1 }, { -1, 0, 1, -1 }, { -1, 0, -1, 1 }, { -1, 0, -1, -1 },
|
||||
{ 1, 1, 0, 1 }, { 1, 1, 0, -1 }, { 1, -1, 0, 1 }, { 1, -1, 0, -1 },
|
||||
{ -1, 1, 0, 1 }, { -1, 1, 0, -1 }, { -1, -1, 0, 1 }, { -1, -1, 0, -1 },
|
||||
{ 1, 1, 1, 0 }, { 1, 1, -1, 0 }, { 1, -1, 1, 0 }, { 1, -1, -1, 0 },
|
||||
{ -1, 1, 1, 0 }, { -1, 1, -1, 0 }, { -1, -1, 1, 0 }, { -1, -1, -1, 0 }
|
||||
)
|
||||
|
||||
-- 4D weight contribution
|
||||
local function GetN4(ix, iy, iz, iw, x, y, z, w)
|
||||
local t = .6 - x * x - y * y - z * z - w * w
|
||||
local index = band(Perms[ix + Perms[iy + Perms[iz + Perms[iw]]]], 0x1F)
|
||||
|
||||
return max(0, (t * t) * (t * t)) * (Grads4[index][0] * x + Grads4[index][1] * y + Grads4[index][2] * z + Grads4[index][3] * w)
|
||||
end
|
||||
|
||||
-- A lookup table to traverse the simplex around a given point in 4D.
|
||||
-- Details can be found where this table is used, in the 4D noise method.
|
||||
local Simplex = ffi.new("uint8_t[64][4]",
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 3, 2 }, {}, { 0, 2, 3, 1 }, {}, {}, {}, { 1, 2, 3 },
|
||||
{ 0, 2, 1, 3 }, {}, { 0, 3, 1, 2 }, { 0, 3, 2, 1 }, {}, {}, {}, { 1, 3, 2 },
|
||||
{}, {}, {}, {}, {}, {}, {}, {},
|
||||
{ 1, 2, 0, 3 }, {}, { 1, 3, 0, 2 }, {}, {}, {}, { 2, 3, 0, 1 }, { 2, 3, 1 },
|
||||
{ 1, 0, 2, 3 }, { 1, 0, 3, 2 }, {}, {}, {}, { 2, 0, 3, 1 }, {}, { 2, 1, 3 },
|
||||
{}, {}, {}, {}, {}, {}, {}, {},
|
||||
{ 2, 0, 1, 3 }, {}, {}, {}, { 3, 0, 1, 2 }, { 3, 0, 2, 1 }, {}, { 3, 1, 2 },
|
||||
{ 2, 1, 0, 3 }, {}, {}, {}, { 3, 1, 0, 2 }, {}, { 3, 2, 0, 1 }, { 3, 2, 1 }
|
||||
)
|
||||
|
||||
-- Convert the above indices to masks that can be shifted / anded into offsets --
|
||||
for i = 0, 63 do
|
||||
Simplex[i][0] = lshift(1, Simplex[i][0]) - 1
|
||||
Simplex[i][1] = lshift(1, Simplex[i][1]) - 1
|
||||
Simplex[i][2] = lshift(1, Simplex[i][2]) - 1
|
||||
Simplex[i][3] = lshift(1, Simplex[i][3]) - 1
|
||||
end
|
||||
|
||||
local function simplex_4d(x, y, z, w)
|
||||
--[[
|
||||
4D skew factors:
|
||||
F = (math.sqrt(5) - 1) / 4
|
||||
G = (5 - math.sqrt(5)) / 20
|
||||
G2 = 2 * G
|
||||
G3 = 3 * G
|
||||
G4 = 4 * G - 1
|
||||
]]
|
||||
|
||||
-- Skew the input space to determine which simplex cell we are in.
|
||||
local s = (x + y + z + w) * 0.309016994 -- F
|
||||
local ix, iy, iz, iw = floor(x + s), floor(y + s), floor(z + s), floor(w + s)
|
||||
|
||||
-- Unskew the cell origin back to (x, y, z) space.
|
||||
local t = (ix + iy + iz + iw) * 0.138196601 -- G
|
||||
local x0 = x + t - ix
|
||||
local y0 = y + t - iy
|
||||
local z0 = z + t - iz
|
||||
local w0 = w + t - iw
|
||||
|
||||
-- For the 4D case, the simplex is a 4D shape I won't even try to describe.
|
||||
-- To find out which of the 24 possible simplices we're in, we need to
|
||||
-- determine the magnitude ordering of x0, y0, z0 and w0.
|
||||
-- The method below is a good way of finding the ordering of x,y,z,w and
|
||||
-- then find the correct traversal order for the simplex we<77>re in.
|
||||
-- First, six pair-wise comparisons are performed between each possible pair
|
||||
-- of the four coordinates, and the results are used to add up binary bits
|
||||
-- for an integer index.
|
||||
local c1 = band(rshift(floor(y0 - x0), 26), 32)
|
||||
local c2 = band(rshift(floor(z0 - x0), 27), 16)
|
||||
local c3 = band(rshift(floor(z0 - y0), 28), 8)
|
||||
local c4 = band(rshift(floor(w0 - x0), 29), 4)
|
||||
local c5 = band(rshift(floor(w0 - y0), 30), 2)
|
||||
local c6 = rshift(floor(w0 - z0), 31)
|
||||
|
||||
-- Simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
|
||||
-- Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
|
||||
-- impossible. Only the 24 indices which have non-zero entries make any sense.
|
||||
-- We use a thresholding to set the coordinates in turn from the largest magnitude.
|
||||
local c = c1 + c2 + c3 + c4 + c5 + c6
|
||||
|
||||
-- The number 3 (i.e. bit 2) in the "simplex" array is at the position of the largest coordinate.
|
||||
local i1 = rshift(Simplex[c][0], 2)
|
||||
local j1 = rshift(Simplex[c][1], 2)
|
||||
local k1 = rshift(Simplex[c][2], 2)
|
||||
local l1 = rshift(Simplex[c][3], 2)
|
||||
|
||||
-- The number 2 (i.e. bit 1) in the "simplex" array is at the second largest coordinate.
|
||||
local i2 = band(rshift(Simplex[c][0], 1), 1)
|
||||
local j2 = band(rshift(Simplex[c][1], 1), 1)
|
||||
local k2 = band(rshift(Simplex[c][2], 1), 1)
|
||||
local l2 = band(rshift(Simplex[c][3], 1), 1)
|
||||
|
||||
-- The number 1 (i.e. bit 0) in the "simplex" array is at the second smallest coordinate.
|
||||
local i3 = band(Simplex[c][0], 1)
|
||||
local j3 = band(Simplex[c][1], 1)
|
||||
local k3 = band(Simplex[c][2], 1)
|
||||
local l3 = band(Simplex[c][3], 1)
|
||||
|
||||
-- Work out the hashed gradient indices of the five simplex corners
|
||||
-- Sum up and scale the result to cover the range [-1,1]
|
||||
ix, iy, iz, iw = band(ix, 255), band(iy, 255), band(iz, 255), band(iw, 255)
|
||||
|
||||
local n0 = GetN4(ix, iy, iz, iw, x0, y0, z0, w0)
|
||||
local n1 = GetN4(ix + i1, iy + j1, iz + k1, iw + l1, x0 + 0.138196601 - i1, y0 + 0.138196601 - j1, z0 + 0.138196601 - k1, w0 + 0.138196601 - l1) -- G
|
||||
local n2 = GetN4(ix + i2, iy + j2, iz + k2, iw + l2, x0 + 0.276393202 - i2, y0 + 0.276393202 - j2, z0 + 0.276393202 - k2, w0 + 0.276393202 - l2) -- G2
|
||||
local n3 = GetN4(ix + i3, iy + j3, iz + k3, iw + l3, x0 + 0.414589803 - i3, y0 + 0.414589803 - j3, z0 + 0.414589803 - k3, w0 + 0.414589803 - l3) -- G3
|
||||
local n4 = GetN4(ix + 1, iy + 1, iz + 1, iw + 1, x0 - 0.447213595, y0 - 0.447213595, z0 - 0.447213595, w0 - 0.447213595) -- G4
|
||||
|
||||
return 2.210600293 * (n0 + n1 + n2 + n3 + n4)
|
||||
end
|
||||
|
||||
--- Simplex Noise
|
||||
-- @param x
|
||||
-- @param y
|
||||
-- @param z optional
|
||||
-- @param w optional
|
||||
-- @return Noise value in the range [-1, +1]
|
||||
return function(x, y, z, w)
|
||||
if w then
|
||||
return simplex_4d(x, y, z, w)
|
||||
end
|
||||
if z then
|
||||
return simplex_3d(x, y, z)
|
||||
end
|
||||
if y then
|
||||
return simplex_2d(x, y)
|
||||
end
|
||||
error "Simplex requires at least two arguments"
|
||||
end
|
||||
228
libs/cpml/utils.lua
Normal file
@@ -0,0 +1,228 @@
|
||||
--- Various utility functions
|
||||
-- @module utils
|
||||
|
||||
local modules = (...): gsub('%.[^%.]+$', '') .. "."
|
||||
local vec2 = require(modules .. "vec2")
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local private = require(modules .. "_private_utils")
|
||||
local abs = math.abs
|
||||
local ceil = math.ceil
|
||||
local floor = math.floor
|
||||
local log = math.log
|
||||
local utils = {}
|
||||
|
||||
-- reimplementation of math.frexp, due to its removal from Lua 5.3 :(
|
||||
-- courtesy of airstruck
|
||||
local log2 = log(2)
|
||||
|
||||
local frexp = math.frexp or function(x)
|
||||
if x == 0 then return 0, 0 end
|
||||
local e = floor(log(abs(x)) / log2 + 1)
|
||||
return x / 2 ^ e, e
|
||||
end
|
||||
|
||||
--- Clamps a value within the specified range.
|
||||
-- @param value Input value
|
||||
-- @param min Minimum output value
|
||||
-- @param max Maximum output value
|
||||
-- @return number
|
||||
function utils.clamp(value, min, max)
|
||||
return math.max(math.min(value, max), min)
|
||||
end
|
||||
|
||||
--- Returns `value` if it is equal or greater than |`size`|, or 0.
|
||||
-- @param value
|
||||
-- @param size
|
||||
-- @return number
|
||||
function utils.deadzone(value, size)
|
||||
return abs(value) >= size and value or 0
|
||||
end
|
||||
|
||||
--- Check if value is equal or greater than threshold.
|
||||
-- @param value
|
||||
-- @param threshold
|
||||
-- @return boolean
|
||||
function utils.threshold(value, threshold)
|
||||
-- I know, it barely saves any typing at all.
|
||||
return abs(value) >= threshold
|
||||
end
|
||||
|
||||
--- Check if value is equal or less than threshold.
|
||||
-- @param value
|
||||
-- @param threshold
|
||||
-- @return boolean
|
||||
function utils.tolerance(value, threshold)
|
||||
-- I know, it barely saves any typing at all.
|
||||
return abs(value) <= threshold
|
||||
end
|
||||
|
||||
--- Scales a value from one range to another.
|
||||
-- @param value Input value
|
||||
-- @param min_in Minimum input value
|
||||
-- @param max_in Maximum input value
|
||||
-- @param min_out Minimum output value
|
||||
-- @param max_out Maximum output value
|
||||
-- @return number
|
||||
function utils.map(value, min_in, max_in, min_out, max_out)
|
||||
return ((value) - (min_in)) * ((max_out) - (min_out)) / ((max_in) - (min_in)) + (min_out)
|
||||
end
|
||||
|
||||
--- Linear interpolation.
|
||||
-- Performs linear interpolation between 0 and 1 when `low` < `progress` < `high`.
|
||||
-- @param low value to return when `progress` is 0
|
||||
-- @param high value to return when `progress` is 1
|
||||
-- @param progress (0-1)
|
||||
-- @return number
|
||||
function utils.lerp(low, high, progress)
|
||||
return low * (1 - progress) + high * progress
|
||||
end
|
||||
|
||||
--- Exponential decay
|
||||
-- @param low initial value
|
||||
-- @param high target value
|
||||
-- @param rate portion of the original value remaining per second
|
||||
-- @param dt time delta
|
||||
-- @return number
|
||||
function utils.decay(low, high, rate, dt)
|
||||
return utils.lerp(low, high, 1.0 - math.exp(-rate * dt))
|
||||
end
|
||||
|
||||
--- Hermite interpolation.
|
||||
-- Performs smooth Hermite interpolation between 0 and 1 when `low` < `progress` < `high`.
|
||||
-- @param progress (0-1)
|
||||
-- @param low value to return when `progress` is 0
|
||||
-- @param high value to return when `progress` is 1
|
||||
-- @return number
|
||||
function utils.smoothstep(progress, low, high)
|
||||
local t = utils.clamp((progress - low) / (high - low), 0.0, 1.0)
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
end
|
||||
|
||||
--- Round number at a given precision.
|
||||
-- Truncates `value` at `precision` points after the decimal (whole number if
|
||||
-- left unspecified).
|
||||
-- @param value
|
||||
-- @param precision
|
||||
-- @return number
|
||||
utils.round = private.round
|
||||
|
||||
--- Wrap `value` around if it exceeds `limit`.
|
||||
-- @param value
|
||||
-- @param limit
|
||||
-- @return number
|
||||
function utils.wrap(value, limit)
|
||||
if value < 0 then
|
||||
value = value + utils.round(((-value/limit)+1))*limit
|
||||
end
|
||||
return value % limit
|
||||
end
|
||||
|
||||
--- Check if a value is a power-of-two.
|
||||
-- Returns true if a number is a valid power-of-two, otherwise false.
|
||||
-- @author undef
|
||||
-- @param value
|
||||
-- @return boolean
|
||||
function utils.is_pot(value)
|
||||
-- found here: https://love2d.org/forums/viewtopic.php?p=182219#p182219
|
||||
-- check if a number is a power-of-two
|
||||
return (frexp(value)) == 0.5
|
||||
end
|
||||
|
||||
--- Check if a value is NaN
|
||||
-- Returns true if a number is not a valid number
|
||||
-- @param value
|
||||
-- @return boolean
|
||||
utils.is_nan = private.is_nan
|
||||
|
||||
-- Originally from vec3
|
||||
function utils.project_on(a, b)
|
||||
local s =
|
||||
(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /
|
||||
(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0)
|
||||
|
||||
if a.z and b.z then
|
||||
return vec3(
|
||||
b.x * s,
|
||||
b.y * s,
|
||||
b.z * s
|
||||
)
|
||||
end
|
||||
|
||||
return vec2(
|
||||
b.x * s,
|
||||
b.y * s
|
||||
)
|
||||
end
|
||||
|
||||
-- Originally from vec3
|
||||
function utils.project_from(a, b)
|
||||
local s =
|
||||
(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) /
|
||||
(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0)
|
||||
|
||||
if a.z and b.z then
|
||||
return vec3(
|
||||
b.x * s,
|
||||
b.y * s,
|
||||
b.z * s
|
||||
)
|
||||
end
|
||||
|
||||
return vec2(
|
||||
b.x * s,
|
||||
b.y * s
|
||||
)
|
||||
end
|
||||
|
||||
-- Originally from vec3
|
||||
function utils.mirror_on(a, b)
|
||||
local s =
|
||||
(a.x * b.x + a.y * b.y + a.z or 0 * b.z or 0) /
|
||||
(b.x * b.x + b.y * b.y + b.z or 0 * b.z or 0) * 2
|
||||
|
||||
if a.z and b.z then
|
||||
return vec3(
|
||||
b.x * s - a.x,
|
||||
b.y * s - a.y,
|
||||
b.z * s - a.z
|
||||
)
|
||||
end
|
||||
|
||||
return vec2(
|
||||
b.x * s - a.x,
|
||||
b.y * s - a.y
|
||||
)
|
||||
end
|
||||
|
||||
-- Originally from vec3
|
||||
function utils.reflect(i, n)
|
||||
return i - (n * (2 * n:dot(i)))
|
||||
end
|
||||
|
||||
-- Originally from vec3
|
||||
function utils.refract(i, n, ior)
|
||||
local d = n:dot(i)
|
||||
local k = 1 - ior * ior * (1 - d * d)
|
||||
|
||||
if k >= 0 then
|
||||
return (i * ior) - (n * (ior * d + k ^ 0.5))
|
||||
end
|
||||
|
||||
return vec3()
|
||||
end
|
||||
|
||||
--- Get the sign of a number
|
||||
-- returns 1 for positive values, -1 for negative and 0 for zero.
|
||||
-- @param value
|
||||
-- @return number
|
||||
function utils.sign(n)
|
||||
if n > 0 then
|
||||
return 1
|
||||
elseif n < 0 then
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
return utils
|
||||
444
libs/cpml/vec2.lua
Normal file
@@ -0,0 +1,444 @@
|
||||
--- A 2 component vector.
|
||||
-- @module vec2
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local vec3 = require(modules .. "vec3")
|
||||
local precond = require(modules .. "_private_precond")
|
||||
local private = require(modules .. "_private_utils")
|
||||
local acos = math.acos
|
||||
local atan2 = math.atan2 or math.atan
|
||||
local sqrt = math.sqrt
|
||||
local cos = math.cos
|
||||
local sin = math.sin
|
||||
local vec2 = {}
|
||||
local vec2_mt = {}
|
||||
|
||||
-- Private constructor.
|
||||
local function new(x, y)
|
||||
return setmetatable({
|
||||
x = x or 0,
|
||||
y = y or 0
|
||||
}, vec2_mt)
|
||||
end
|
||||
|
||||
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||
local status, ffi
|
||||
if type(jit) == "table" and jit.status() then
|
||||
status, ffi = pcall(require, "ffi")
|
||||
if status then
|
||||
ffi.cdef "typedef struct { double x, y;} cpml_vec2;"
|
||||
new = ffi.typeof("cpml_vec2")
|
||||
end
|
||||
end
|
||||
|
||||
--- Constants
|
||||
-- @table vec2
|
||||
-- @field unit_x X axis of rotation
|
||||
-- @field unit_y Y axis of rotation
|
||||
-- @field zero Empty vector
|
||||
vec2.unit_x = new(1, 0)
|
||||
vec2.unit_y = new(0, 1)
|
||||
vec2.zero = new(0, 0)
|
||||
|
||||
--- The public constructor.
|
||||
-- @param x Can be of three types: </br>
|
||||
-- number X component
|
||||
-- table {x, y} or {x = x, y = y}
|
||||
-- scalar to fill the vector eg. {x, x}
|
||||
-- @tparam number y Y component
|
||||
-- @treturn vec2 out
|
||||
function vec2.new(x, y)
|
||||
-- number, number
|
||||
if x and y then
|
||||
precond.typeof(x, "number", "new: Wrong argument type for x")
|
||||
precond.typeof(y, "number", "new: Wrong argument type for y")
|
||||
|
||||
return new(x, y)
|
||||
|
||||
-- {x, y} or {x=x, y=y}
|
||||
elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
|
||||
local xx, yy = x.x or x[1], x.y or x[2]
|
||||
precond.typeof(xx, "number", "new: Wrong argument type for x")
|
||||
precond.typeof(yy, "number", "new: Wrong argument type for y")
|
||||
|
||||
return new(xx, yy)
|
||||
|
||||
-- number
|
||||
elseif type(x) == "number" then
|
||||
return new(x, x)
|
||||
else
|
||||
return new()
|
||||
end
|
||||
end
|
||||
|
||||
--- Convert point from polar to cartesian.
|
||||
-- @tparam number radius Radius of the point
|
||||
-- @tparam number theta Angle of the point (in radians)
|
||||
-- @treturn vec2 out
|
||||
function vec2.from_cartesian(radius, theta)
|
||||
return new(radius * cos(theta), radius * sin(theta))
|
||||
end
|
||||
|
||||
--- Clone a vector.
|
||||
-- @tparam vec2 a Vector to be cloned
|
||||
-- @treturn vec2 out
|
||||
function vec2.clone(a)
|
||||
return new(a.x, a.y)
|
||||
end
|
||||
|
||||
--- Add two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn vec2 out
|
||||
function vec2.add(a, b)
|
||||
return new(
|
||||
a.x + b.x,
|
||||
a.y + b.y
|
||||
)
|
||||
end
|
||||
|
||||
--- Subtract one vector from another.
|
||||
-- Order: If a and b are positions, computes the direction and distance from b
|
||||
-- to a.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn vec2 out
|
||||
function vec2.sub(a, b)
|
||||
return new(
|
||||
a.x - b.x,
|
||||
a.y - b.y
|
||||
)
|
||||
end
|
||||
|
||||
--- Multiply a vector by another vector.
|
||||
-- Component-size multiplication not matrix multiplication.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn vec2 out
|
||||
function vec2.mul(a, b)
|
||||
return new(
|
||||
a.x * b.x,
|
||||
a.y * b.y
|
||||
)
|
||||
end
|
||||
|
||||
--- Divide a vector by another vector.
|
||||
-- Component-size inv multiplication. Like a non-uniform scale().
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn vec2 out
|
||||
function vec2.div(a, b)
|
||||
return new(
|
||||
a.x / b.x,
|
||||
a.y / b.y
|
||||
)
|
||||
end
|
||||
|
||||
--- Get the normal of a vector.
|
||||
-- @tparam vec2 a Vector to normalize
|
||||
-- @treturn vec2 out
|
||||
function vec2.normalize(a)
|
||||
if a:is_zero() then
|
||||
return new()
|
||||
end
|
||||
return a:scale(1 / a:len())
|
||||
end
|
||||
|
||||
--- Trim a vector to a given length.
|
||||
-- @tparam vec2 a Vector to be trimmed
|
||||
-- @tparam number len Length to trim the vector to
|
||||
-- @treturn vec2 out
|
||||
function vec2.trim(a, len)
|
||||
return a:normalize():scale(math.min(a:len(), len))
|
||||
end
|
||||
|
||||
--- Get the cross product of two vectors.
|
||||
-- Order: Positive if a is clockwise from b. Magnitude is the area spanned by
|
||||
-- the parallelograms that a and b span.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn number magnitude
|
||||
function vec2.cross(a, b)
|
||||
return a.x * b.y - a.y * b.x
|
||||
end
|
||||
|
||||
--- Get the dot product of two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn number dot
|
||||
function vec2.dot(a, b)
|
||||
return a.x * b.x + a.y * b.y
|
||||
end
|
||||
|
||||
--- Get the length of a vector.
|
||||
-- @tparam vec2 a Vector to get the length of
|
||||
-- @treturn number len
|
||||
function vec2.len(a)
|
||||
return sqrt(a.x * a.x + a.y * a.y)
|
||||
end
|
||||
|
||||
--- Get the squared length of a vector.
|
||||
-- @tparam vec2 a Vector to get the squared length of
|
||||
-- @treturn number len
|
||||
function vec2.len2(a)
|
||||
return a.x * a.x + a.y * a.y
|
||||
end
|
||||
|
||||
--- Get the distance between two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn number dist
|
||||
function vec2.dist(a, b)
|
||||
local dx = a.x - b.x
|
||||
local dy = a.y - b.y
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
--- Get the squared distance between two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn number dist
|
||||
function vec2.dist2(a, b)
|
||||
local dx = a.x - b.x
|
||||
local dy = a.y - b.y
|
||||
return dx * dx + dy * dy
|
||||
end
|
||||
|
||||
--- Scale a vector by a scalar.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam number b Right hand operand
|
||||
-- @treturn vec2 out
|
||||
function vec2.scale(a, b)
|
||||
return new(
|
||||
a.x * b,
|
||||
a.y * b
|
||||
)
|
||||
end
|
||||
|
||||
--- Rotate a vector.
|
||||
-- @tparam vec2 a Vector to rotate
|
||||
-- @tparam number phi Angle to rotate vector by (in radians)
|
||||
-- @treturn vec2 out
|
||||
function vec2.rotate(a, phi)
|
||||
local c = cos(phi)
|
||||
local s = sin(phi)
|
||||
return new(
|
||||
c * a.x - s * a.y,
|
||||
s * a.x + c * a.y
|
||||
)
|
||||
end
|
||||
|
||||
--- Get the perpendicular vector of a vector.
|
||||
-- @tparam vec2 a Vector to get perpendicular axes from
|
||||
-- @treturn vec2 out
|
||||
function vec2.perpendicular(a)
|
||||
return new(-a.y, a.x)
|
||||
end
|
||||
|
||||
--- Signed angle from one vector to another.
|
||||
-- Rotations from +x to +y are positive.
|
||||
-- @tparam vec2 a Vector
|
||||
-- @tparam vec2 b Vector
|
||||
-- @treturn number angle in (-pi, pi]
|
||||
function vec2.angle_to(a, b)
|
||||
if b then
|
||||
local angle = atan2(b.y, b.x) - atan2(a.y, a.x)
|
||||
-- convert to (-pi, pi]
|
||||
if angle > math.pi then
|
||||
angle = angle - 2 * math.pi
|
||||
elseif angle <= -math.pi then
|
||||
angle = angle + 2 * math.pi
|
||||
end
|
||||
return angle
|
||||
end
|
||||
|
||||
return atan2(a.y, a.x)
|
||||
end
|
||||
|
||||
--- Unsigned angle between two vectors.
|
||||
-- Directionless and thus commutative.
|
||||
-- @tparam vec2 a Vector
|
||||
-- @tparam vec2 b Vector
|
||||
-- @treturn number angle in [0, pi]
|
||||
function vec2.angle_between(a, b)
|
||||
if b then
|
||||
if vec2.is_vec2(a) then
|
||||
return acos(a:dot(b) / (a:len() * b:len()))
|
||||
end
|
||||
|
||||
return acos(vec3.dot(a, b) / (vec3.len(a) * vec3.len(b)))
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
--- Lerp between two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @tparam number s Step value
|
||||
-- @treturn vec2 out
|
||||
function vec2.lerp(a, b, s)
|
||||
return a + (b - a) * s
|
||||
end
|
||||
|
||||
--- Unpack a vector into individual components.
|
||||
-- @tparam vec2 a Vector to unpack
|
||||
-- @treturn number x
|
||||
-- @treturn number y
|
||||
function vec2.unpack(a)
|
||||
return a.x, a.y
|
||||
end
|
||||
|
||||
--- Return the component-wise minimum of two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.
|
||||
function vec2.component_min(a, b)
|
||||
return new(math.min(a.x, b.x), math.min(a.y, b.y))
|
||||
end
|
||||
|
||||
--- Return the component-wise maximum of two vectors.
|
||||
-- @tparam vec2 a Left hand operand
|
||||
-- @tparam vec2 b Right hand operand
|
||||
-- @treturn vec2 A vector where each component is the lesser value for that component between the two given vectors.
|
||||
function vec2.component_max(a, b)
|
||||
return new(math.max(a.x, b.x), math.max(a.y, b.y))
|
||||
end
|
||||
|
||||
|
||||
--- Return a boolean showing if a table is or is not a vec2.
|
||||
-- @tparam vec2 a Vector to be tested
|
||||
-- @treturn boolean is_vec2
|
||||
function vec2.is_vec2(a)
|
||||
if type(a) == "cdata" then
|
||||
return ffi.istype("cpml_vec2", a)
|
||||
end
|
||||
|
||||
return
|
||||
type(a) == "table" and
|
||||
type(a.x) == "number" and
|
||||
type(a.y) == "number"
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a zero vec2.
|
||||
-- @tparam vec2 a Vector to be tested
|
||||
-- @treturn boolean is_zero
|
||||
function vec2.is_zero(a)
|
||||
return a.x == 0 and a.y == 0
|
||||
end
|
||||
|
||||
--- Return whether either value is NaN
|
||||
-- @tparam vec2 a Vector to be tested
|
||||
-- @treturn boolean if x or y is nan
|
||||
function vec2.has_nan(a)
|
||||
return private.is_nan(a.x) or
|
||||
private.is_nan(a.y)
|
||||
end
|
||||
|
||||
--- Convert point from cartesian to polar.
|
||||
-- @tparam vec2 a Vector to convert
|
||||
-- @treturn number radius
|
||||
-- @treturn number theta
|
||||
function vec2.to_polar(a)
|
||||
local radius = sqrt(a.x^2 + a.y^2)
|
||||
local theta = atan2(a.y, a.x)
|
||||
theta = theta > 0 and theta or theta + 2 * math.pi
|
||||
return radius, theta
|
||||
end
|
||||
|
||||
-- Round all components to nearest int (or other precision).
|
||||
-- @tparam vec2 a Vector to round.
|
||||
-- @tparam precision Digits after the decimal (integer if unspecified)
|
||||
-- @treturn vec2 Rounded vector
|
||||
function vec2.round(a, precision)
|
||||
return vec2.new(private.round(a.x, precision), private.round(a.y, precision))
|
||||
end
|
||||
|
||||
-- Negate x axis only of vector.
|
||||
-- @tparam vec2 a Vector to x-flip.
|
||||
-- @treturn vec2 x-flipped vector
|
||||
function vec2.flip_x(a)
|
||||
return vec2.new(-a.x, a.y)
|
||||
end
|
||||
|
||||
-- Negate y axis only of vector.
|
||||
-- @tparam vec2 a Vector to y-flip.
|
||||
-- @treturn vec2 y-flipped vector
|
||||
function vec2.flip_y(a)
|
||||
return vec2.new(a.x, -a.y)
|
||||
end
|
||||
|
||||
-- Convert vec2 to vec3.
|
||||
-- @tparam vec2 a Vector to convert.
|
||||
-- @tparam number the new z component, or nil for 0
|
||||
-- @treturn vec3 Converted vector
|
||||
function vec2.to_vec3(a, z)
|
||||
return vec3(a.x, a.y, z or 0)
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam vec2 a Vector to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function vec2.to_string(a)
|
||||
return string.format("(%+0.3f,%+0.3f)", a.x, a.y)
|
||||
end
|
||||
|
||||
vec2_mt.__index = vec2
|
||||
vec2_mt.__tostring = vec2.to_string
|
||||
|
||||
function vec2_mt.__call(_, x, y)
|
||||
return vec2.new(x, y)
|
||||
end
|
||||
|
||||
function vec2_mt.__unm(a)
|
||||
return new(-a.x, -a.y)
|
||||
end
|
||||
|
||||
function vec2_mt.__eq(a, b)
|
||||
if not vec2.is_vec2(a) or not vec2.is_vec2(b) then
|
||||
return false
|
||||
end
|
||||
return a.x == b.x and a.y == b.y
|
||||
end
|
||||
|
||||
function vec2_mt.__add(a, b)
|
||||
precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||
precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
|
||||
return a:add(b)
|
||||
end
|
||||
|
||||
function vec2_mt.__sub(a, b)
|
||||
precond.assert(vec2.is_vec2(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||
precond.assert(vec2.is_vec2(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec2> expected)", type(b))
|
||||
return a:sub(b)
|
||||
end
|
||||
|
||||
function vec2_mt.__mul(a, b)
|
||||
precond.assert(vec2.is_vec2(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||
assert(vec2.is_vec2(b) or type(b) == "number", "__mul: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
|
||||
|
||||
if vec2.is_vec2(b) then
|
||||
return a:mul(b)
|
||||
end
|
||||
|
||||
return a:scale(b)
|
||||
end
|
||||
|
||||
function vec2_mt.__div(a, b)
|
||||
precond.assert(vec2.is_vec2(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec2> expected)", type(a))
|
||||
assert(vec2.is_vec2(b) or type(b) == "number", "__div: Wrong argument type for right hand operand. (<cpml.vec2> or <number> expected)")
|
||||
|
||||
if vec2.is_vec2(b) then
|
||||
return a:div(b)
|
||||
end
|
||||
|
||||
return a:scale(1 / b)
|
||||
end
|
||||
|
||||
if status then
|
||||
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||
ffi.metatype(new, vec2_mt)
|
||||
end, function() end)
|
||||
end
|
||||
|
||||
return setmetatable({}, vec2_mt)
|
||||
434
libs/cpml/vec3.lua
Normal file
@@ -0,0 +1,434 @@
|
||||
--- A 3 component vector.
|
||||
-- @module vec3
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local precond = require(modules .. "_private_precond")
|
||||
local private = require(modules .. "_private_utils")
|
||||
local sqrt = math.sqrt
|
||||
local cos = math.cos
|
||||
local sin = math.sin
|
||||
local vec3 = {}
|
||||
local vec3_mt = {}
|
||||
|
||||
-- Private constructor.
|
||||
local function new(x, y, z)
|
||||
return setmetatable({
|
||||
x = x or 0,
|
||||
y = y or 0,
|
||||
z = z or 0
|
||||
}, vec3_mt)
|
||||
end
|
||||
|
||||
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
|
||||
local status, ffi
|
||||
if type(jit) == "table" and jit.status() then
|
||||
status, ffi = pcall(require, "ffi")
|
||||
if status then
|
||||
ffi.cdef "typedef struct { double x, y, z;} cpml_vec3;"
|
||||
new = ffi.typeof("cpml_vec3")
|
||||
end
|
||||
end
|
||||
|
||||
--- Constants
|
||||
-- @table vec3
|
||||
-- @field unit_x X axis of rotation
|
||||
-- @field unit_y Y axis of rotation
|
||||
-- @field unit_z Z axis of rotation
|
||||
-- @field zero Empty vector
|
||||
vec3.unit_x = new(1, 0, 0)
|
||||
vec3.unit_y = new(0, 1, 0)
|
||||
vec3.unit_z = new(0, 0, 1)
|
||||
vec3.zero = new(0, 0, 0)
|
||||
|
||||
--- The public constructor.
|
||||
-- @param x Can be of three types: </br>
|
||||
-- number X component
|
||||
-- table {x, y, z} or {x=x, y=y, z=z}
|
||||
-- scalar To fill the vector eg. {x, x, x}
|
||||
-- @tparam number y Y component
|
||||
-- @tparam number z Z component
|
||||
-- @treturn vec3 out
|
||||
function vec3.new(x, y, z)
|
||||
-- number, number, number
|
||||
if x and y and z then
|
||||
precond.typeof(x, "number", "new: Wrong argument type for x")
|
||||
precond.typeof(y, "number", "new: Wrong argument type for y")
|
||||
precond.typeof(z, "number", "new: Wrong argument type for z")
|
||||
|
||||
return new(x, y, z)
|
||||
|
||||
-- {x, y, z} or {x=x, y=y, z=z}
|
||||
elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
|
||||
local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
|
||||
precond.typeof(xx, "number", "new: Wrong argument type for x")
|
||||
precond.typeof(yy, "number", "new: Wrong argument type for y")
|
||||
precond.typeof(zz, "number", "new: Wrong argument type for z")
|
||||
|
||||
return new(xx, yy, zz)
|
||||
|
||||
-- number
|
||||
elseif type(x) == "number" then
|
||||
return new(x, x, x)
|
||||
else
|
||||
return new()
|
||||
end
|
||||
end
|
||||
|
||||
--- Clone a vector.
|
||||
-- @tparam vec3 a Vector to be cloned
|
||||
-- @treturn vec3 out
|
||||
function vec3.clone(a)
|
||||
return new(a.x, a.y, a.z)
|
||||
end
|
||||
|
||||
--- Add two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function vec3.add(a, b)
|
||||
return new(
|
||||
a.x + b.x,
|
||||
a.y + b.y,
|
||||
a.z + b.z
|
||||
)
|
||||
end
|
||||
|
||||
--- Subtract one vector from another.
|
||||
-- Order: If a and b are positions, computes the direction and distance from b
|
||||
-- to a.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function vec3.sub(a, b)
|
||||
return new(
|
||||
a.x - b.x,
|
||||
a.y - b.y,
|
||||
a.z - b.z
|
||||
)
|
||||
end
|
||||
|
||||
--- Multiply a vector by another vector.
|
||||
-- Component-wise multiplication not matrix multiplication.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function vec3.mul(a, b)
|
||||
return new(
|
||||
a.x * b.x,
|
||||
a.y * b.y,
|
||||
a.z * b.z
|
||||
)
|
||||
end
|
||||
|
||||
--- Divide a vector by another.
|
||||
-- Component-wise inv multiplication. Like a non-uniform scale().
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function vec3.div(a, b)
|
||||
return new(
|
||||
a.x / b.x,
|
||||
a.y / b.y,
|
||||
a.z / b.z
|
||||
)
|
||||
end
|
||||
|
||||
--- Scale a vector to unit length (1).
|
||||
-- @tparam vec3 a vector to normalize
|
||||
-- @treturn vec3 out
|
||||
function vec3.normalize(a)
|
||||
if a:is_zero() then
|
||||
return new()
|
||||
end
|
||||
return a:scale(1 / a:len())
|
||||
end
|
||||
|
||||
--- Scale a vector to unit length (1), and return the input length.
|
||||
-- @tparam vec3 a vector to normalize
|
||||
-- @treturn vec3 out
|
||||
-- @treturn number input vector length
|
||||
function vec3.normalize_len(a)
|
||||
if a:is_zero() then
|
||||
return new(), 0
|
||||
end
|
||||
local len = a:len()
|
||||
return a:scale(1 / len), len
|
||||
end
|
||||
|
||||
--- Trim a vector to a given length
|
||||
-- @tparam vec3 a vector to be trimmed
|
||||
-- @tparam number len Length to trim the vector to
|
||||
-- @treturn vec3 out
|
||||
function vec3.trim(a, len)
|
||||
return a:normalize():scale(math.min(a:len(), len))
|
||||
end
|
||||
|
||||
--- Get the cross product of two vectors.
|
||||
-- Resulting direction is right-hand rule normal of plane defined by a and b.
|
||||
-- Magnitude is the area spanned by the parallelograms that a and b span.
|
||||
-- Order: Direction determined by right-hand rule.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function vec3.cross(a, b)
|
||||
return new(
|
||||
a.y * b.z - a.z * b.y,
|
||||
a.z * b.x - a.x * b.z,
|
||||
a.x * b.y - a.y * b.x
|
||||
)
|
||||
end
|
||||
|
||||
--- Get the dot product of two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn number dot
|
||||
function vec3.dot(a, b)
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z
|
||||
end
|
||||
|
||||
--- Get the length of a vector.
|
||||
-- @tparam vec3 a Vector to get the length of
|
||||
-- @treturn number len
|
||||
function vec3.len(a)
|
||||
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
|
||||
end
|
||||
|
||||
--- Get the squared length of a vector.
|
||||
-- @tparam vec3 a Vector to get the squared length of
|
||||
-- @treturn number len
|
||||
function vec3.len2(a)
|
||||
return a.x * a.x + a.y * a.y + a.z * a.z
|
||||
end
|
||||
|
||||
--- Get the distance between two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn number dist
|
||||
function vec3.dist(a, b)
|
||||
local dx = a.x - b.x
|
||||
local dy = a.y - b.y
|
||||
local dz = a.z - b.z
|
||||
return sqrt(dx * dx + dy * dy + dz * dz)
|
||||
end
|
||||
|
||||
--- Get the squared distance between two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn number dist
|
||||
function vec3.dist2(a, b)
|
||||
local dx = a.x - b.x
|
||||
local dy = a.y - b.y
|
||||
local dz = a.z - b.z
|
||||
return dx * dx + dy * dy + dz * dz
|
||||
end
|
||||
|
||||
--- Scale a vector by a scalar.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam number b Right hand operand
|
||||
-- @treturn vec3 out
|
||||
function vec3.scale(a, b)
|
||||
return new(
|
||||
a.x * b,
|
||||
a.y * b,
|
||||
a.z * b
|
||||
)
|
||||
end
|
||||
|
||||
--- Rotate vector about an axis.
|
||||
-- @tparam vec3 a Vector to rotate
|
||||
-- @tparam number phi Angle to rotate vector by (in radians)
|
||||
-- @tparam vec3 axis Axis to rotate by
|
||||
-- @treturn vec3 out
|
||||
function vec3.rotate(a, phi, axis)
|
||||
if not vec3.is_vec3(axis) then
|
||||
return a
|
||||
end
|
||||
|
||||
local u = axis:normalize()
|
||||
local c = cos(phi)
|
||||
local s = sin(phi)
|
||||
|
||||
-- Calculate generalized rotation matrix
|
||||
local m1 = new((c + u.x * u.x * (1 - c)), (u.x * u.y * (1 - c) - u.z * s), (u.x * u.z * (1 - c) + u.y * s))
|
||||
local m2 = new((u.y * u.x * (1 - c) + u.z * s), (c + u.y * u.y * (1 - c)), (u.y * u.z * (1 - c) - u.x * s))
|
||||
local m3 = new((u.z * u.x * (1 - c) - u.y * s), (u.z * u.y * (1 - c) + u.x * s), (c + u.z * u.z * (1 - c)) )
|
||||
|
||||
return new(
|
||||
a:dot(m1),
|
||||
a:dot(m2),
|
||||
a:dot(m3)
|
||||
)
|
||||
end
|
||||
|
||||
--- Get the perpendicular vector of a vector.
|
||||
-- @tparam vec3 a Vector to get perpendicular axes from
|
||||
-- @treturn vec3 out
|
||||
function vec3.perpendicular(a)
|
||||
return new(-a.y, a.x, 0)
|
||||
end
|
||||
|
||||
--- Lerp between two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @tparam number s Step value
|
||||
-- @treturn vec3 out
|
||||
function vec3.lerp(a, b, s)
|
||||
return a + (b - a) * s
|
||||
end
|
||||
|
||||
-- Round all components to nearest int (or other precision).
|
||||
-- @tparam vec3 a Vector to round.
|
||||
-- @tparam precision Digits after the decimal (round numebr if unspecified)
|
||||
-- @treturn vec3 Rounded vector
|
||||
function vec3.round(a, precision)
|
||||
return vec3.new(private.round(a.x, precision), private.round(a.y, precision), private.round(a.z, precision))
|
||||
end
|
||||
|
||||
--- Unpack a vector into individual components.
|
||||
-- @tparam vec3 a Vector to unpack
|
||||
-- @treturn number x
|
||||
-- @treturn number y
|
||||
-- @treturn number z
|
||||
function vec3.unpack(a)
|
||||
return a.x, a.y, a.z
|
||||
end
|
||||
|
||||
--- Return the component-wise minimum of two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
|
||||
function vec3.component_min(a, b)
|
||||
return new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z))
|
||||
end
|
||||
|
||||
--- Return the component-wise maximum of two vectors.
|
||||
-- @tparam vec3 a Left hand operand
|
||||
-- @tparam vec3 b Right hand operand
|
||||
-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
|
||||
function vec3.component_max(a, b)
|
||||
return new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
|
||||
end
|
||||
|
||||
-- Negate x axis only of vector.
|
||||
-- @tparam vec3 a Vector to x-flip.
|
||||
-- @treturn vec3 x-flipped vector
|
||||
function vec3.flip_x(a)
|
||||
return vec3.new(-a.x, a.y, a.z)
|
||||
end
|
||||
|
||||
-- Negate y axis only of vector.
|
||||
-- @tparam vec3 a Vector to y-flip.
|
||||
-- @treturn vec3 y-flipped vector
|
||||
function vec3.flip_y(a)
|
||||
return vec3.new(a.x, -a.y, a.z)
|
||||
end
|
||||
|
||||
-- Negate z axis only of vector.
|
||||
-- @tparam vec3 a Vector to z-flip.
|
||||
-- @treturn vec3 z-flipped vector
|
||||
function vec3.flip_z(a)
|
||||
return vec3.new(a.x, a.y, -a.z)
|
||||
end
|
||||
|
||||
function vec3.angle_to(a, b)
|
||||
local v = a:normalize():dot(b:normalize())
|
||||
return math.acos(v)
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a vec3.
|
||||
-- @tparam vec3 a Vector to be tested
|
||||
-- @treturn boolean is_vec3
|
||||
function vec3.is_vec3(a)
|
||||
if type(a) == "cdata" then
|
||||
return ffi.istype("cpml_vec3", a)
|
||||
end
|
||||
|
||||
return
|
||||
type(a) == "table" and
|
||||
type(a.x) == "number" and
|
||||
type(a.y) == "number" and
|
||||
type(a.z) == "number"
|
||||
end
|
||||
|
||||
--- Return a boolean showing if a table is or is not a zero vec3.
|
||||
-- @tparam vec3 a Vector to be tested
|
||||
-- @treturn boolean is_zero
|
||||
function vec3.is_zero(a)
|
||||
return a.x == 0 and a.y == 0 and a.z == 0
|
||||
end
|
||||
|
||||
--- Return whether any component is NaN
|
||||
-- @tparam vec3 a Vector to be tested
|
||||
-- @treturn boolean if x,y, or z are nan
|
||||
function vec3.has_nan(a)
|
||||
return private.is_nan(a.x) or
|
||||
private.is_nan(a.y) or
|
||||
private.is_nan(a.z)
|
||||
end
|
||||
|
||||
--- Return a formatted string.
|
||||
-- @tparam vec3 a Vector to be turned into a string
|
||||
-- @treturn string formatted
|
||||
function vec3.to_string(a)
|
||||
return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z)
|
||||
end
|
||||
|
||||
vec3_mt.__index = vec3
|
||||
vec3_mt.__tostring = vec3.to_string
|
||||
|
||||
function vec3_mt.__call(_, x, y, z)
|
||||
return vec3.new(x, y, z)
|
||||
end
|
||||
|
||||
function vec3_mt.__unm(a)
|
||||
return new(-a.x, -a.y, -a.z)
|
||||
end
|
||||
|
||||
function vec3_mt.__eq(a, b)
|
||||
if not vec3.is_vec3(a) or not vec3.is_vec3(b) then
|
||||
return false
|
||||
end
|
||||
return a.x == b.x and a.y == b.y and a.z == b.z
|
||||
end
|
||||
|
||||
function vec3_mt.__add(a, b)
|
||||
precond.assert(vec3.is_vec3(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||
precond.assert(vec3.is_vec3(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
|
||||
return a:add(b)
|
||||
end
|
||||
|
||||
function vec3_mt.__sub(a, b)
|
||||
precond.assert(vec3.is_vec3(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||
precond.assert(vec3.is_vec3(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
|
||||
return a:sub(b)
|
||||
end
|
||||
|
||||
function vec3_mt.__mul(a, b)
|
||||
precond.assert(vec3.is_vec3(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||
precond.assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
|
||||
|
||||
if vec3.is_vec3(b) then
|
||||
return a:mul(b)
|
||||
end
|
||||
|
||||
return a:scale(b)
|
||||
end
|
||||
|
||||
function vec3_mt.__div(a, b)
|
||||
precond.assert(vec3.is_vec3(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
|
||||
precond.assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
|
||||
|
||||
if vec3.is_vec3(b) then
|
||||
return a:div(b)
|
||||
end
|
||||
|
||||
return a:scale(1 / b)
|
||||
end
|
||||
|
||||
if status then
|
||||
xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
|
||||
ffi.metatype(new, vec3_mt)
|
||||
end, function() end)
|
||||
end
|
||||
|
||||
return setmetatable({}, vec3_mt)
|
||||
1048
libs/discordGameSDK.lua
Normal file
13
libs/discordGameSDK/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Discord Game SDK
|
||||
|
||||
> The SDK is currently under extensive development and is subject to change. Suggestions
|
||||
> about the current API are welcome.
|
||||
|
||||
### Setup
|
||||
|
||||
- Create an application on the Discord [developer site](https://discordapp.com/developers/applications/me).
|
||||
- Set a redirect URL. If you don't have one right now, just use <http://127.0.0.1>.
|
||||
- Enable Rich Presence for the application. This enables whitelist access for the SDK.
|
||||
- When you are ready to test with more people, add them to the whitelist.
|
||||
- Copy the **Client ID**.
|
||||
- Use this `CLIENT_ID` when initializing the SDK.
|
||||
646
libs/discordGameSDK/c/discord_game_sdk.h
Normal file
@@ -0,0 +1,646 @@
|
||||
#ifndef _DISCORD_GAME_SDK_H_
|
||||
#define _DISCORD_GAME_SDK_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#define DISCORD_VERSION 2
|
||||
#define DISCORD_APPLICATION_MANAGER_VERSION 1
|
||||
#define DISCORD_USER_MANAGER_VERSION 1
|
||||
#define DISCORD_IMAGE_MANAGER_VERSION 1
|
||||
#define DISCORD_ACTIVITY_MANAGER_VERSION 1
|
||||
#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1
|
||||
#define DISCORD_LOBBY_MANAGER_VERSION 1
|
||||
#define DISCORD_NETWORK_MANAGER_VERSION 1
|
||||
#define DISCORD_OVERLAY_MANAGER_VERSION 1
|
||||
#define DISCORD_STORAGE_MANAGER_VERSION 1
|
||||
#define DISCORD_STORE_MANAGER_VERSION 1
|
||||
#define DISCORD_VOICE_MANAGER_VERSION 1
|
||||
#define DISCORD_ACHIEVEMENT_MANAGER_VERSION 1
|
||||
|
||||
enum EDiscordResult {
|
||||
DiscordResult_Ok = 0,
|
||||
DiscordResult_ServiceUnavailable = 1,
|
||||
DiscordResult_InvalidVersion = 2,
|
||||
DiscordResult_LockFailed = 3,
|
||||
DiscordResult_InternalError = 4,
|
||||
DiscordResult_InvalidPayload = 5,
|
||||
DiscordResult_InvalidCommand = 6,
|
||||
DiscordResult_InvalidPermissions = 7,
|
||||
DiscordResult_NotFetched = 8,
|
||||
DiscordResult_NotFound = 9,
|
||||
DiscordResult_Conflict = 10,
|
||||
DiscordResult_InvalidSecret = 11,
|
||||
DiscordResult_InvalidJoinSecret = 12,
|
||||
DiscordResult_NoEligibleActivity = 13,
|
||||
DiscordResult_InvalidInvite = 14,
|
||||
DiscordResult_NotAuthenticated = 15,
|
||||
DiscordResult_InvalidAccessToken = 16,
|
||||
DiscordResult_ApplicationMismatch = 17,
|
||||
DiscordResult_InvalidDataUrl = 18,
|
||||
DiscordResult_InvalidBase64 = 19,
|
||||
DiscordResult_NotFiltered = 20,
|
||||
DiscordResult_LobbyFull = 21,
|
||||
DiscordResult_InvalidLobbySecret = 22,
|
||||
DiscordResult_InvalidFilename = 23,
|
||||
DiscordResult_InvalidFileSize = 24,
|
||||
DiscordResult_InvalidEntitlement = 25,
|
||||
DiscordResult_NotInstalled = 26,
|
||||
DiscordResult_NotRunning = 27,
|
||||
DiscordResult_InsufficientBuffer = 28,
|
||||
DiscordResult_PurchaseCanceled = 29,
|
||||
DiscordResult_InvalidGuild = 30,
|
||||
DiscordResult_InvalidEvent = 31,
|
||||
DiscordResult_InvalidChannel = 32,
|
||||
DiscordResult_InvalidOrigin = 33,
|
||||
DiscordResult_RateLimited = 34,
|
||||
DiscordResult_OAuth2Error = 35,
|
||||
DiscordResult_SelectChannelTimeout = 36,
|
||||
DiscordResult_GetGuildTimeout = 37,
|
||||
DiscordResult_SelectVoiceForceRequired = 38,
|
||||
DiscordResult_CaptureShortcutAlreadyListening = 39,
|
||||
DiscordResult_UnauthorizedForAchievement = 40,
|
||||
DiscordResult_InvalidGiftCode = 41,
|
||||
DiscordResult_PurchaseError = 42,
|
||||
DiscordResult_TransactionAborted = 43,
|
||||
};
|
||||
|
||||
enum EDiscordCreateFlags {
|
||||
DiscordCreateFlags_Default = 0,
|
||||
DiscordCreateFlags_NoRequireDiscord = 1,
|
||||
};
|
||||
|
||||
enum EDiscordLogLevel {
|
||||
DiscordLogLevel_Error = 1,
|
||||
DiscordLogLevel_Warn,
|
||||
DiscordLogLevel_Info,
|
||||
DiscordLogLevel_Debug,
|
||||
};
|
||||
|
||||
enum EDiscordUserFlag {
|
||||
DiscordUserFlag_Partner = 2,
|
||||
DiscordUserFlag_HypeSquadEvents = 4,
|
||||
DiscordUserFlag_HypeSquadHouse1 = 64,
|
||||
DiscordUserFlag_HypeSquadHouse2 = 128,
|
||||
DiscordUserFlag_HypeSquadHouse3 = 256,
|
||||
};
|
||||
|
||||
enum EDiscordPremiumType {
|
||||
DiscordPremiumType_None = 0,
|
||||
DiscordPremiumType_Tier1 = 1,
|
||||
DiscordPremiumType_Tier2 = 2,
|
||||
};
|
||||
|
||||
enum EDiscordImageType {
|
||||
DiscordImageType_User,
|
||||
};
|
||||
|
||||
enum EDiscordActivityType {
|
||||
DiscordActivityType_Playing,
|
||||
DiscordActivityType_Streaming,
|
||||
DiscordActivityType_Listening,
|
||||
DiscordActivityType_Watching,
|
||||
};
|
||||
|
||||
enum EDiscordActivityActionType {
|
||||
DiscordActivityActionType_Join = 1,
|
||||
DiscordActivityActionType_Spectate,
|
||||
};
|
||||
|
||||
enum EDiscordActivityJoinRequestReply {
|
||||
DiscordActivityJoinRequestReply_No,
|
||||
DiscordActivityJoinRequestReply_Yes,
|
||||
DiscordActivityJoinRequestReply_Ignore,
|
||||
};
|
||||
|
||||
enum EDiscordStatus {
|
||||
DiscordStatus_Offline = 0,
|
||||
DiscordStatus_Online = 1,
|
||||
DiscordStatus_Idle = 2,
|
||||
DiscordStatus_DoNotDisturb = 3,
|
||||
};
|
||||
|
||||
enum EDiscordRelationshipType {
|
||||
DiscordRelationshipType_None,
|
||||
DiscordRelationshipType_Friend,
|
||||
DiscordRelationshipType_Blocked,
|
||||
DiscordRelationshipType_PendingIncoming,
|
||||
DiscordRelationshipType_PendingOutgoing,
|
||||
DiscordRelationshipType_Implicit,
|
||||
};
|
||||
|
||||
enum EDiscordLobbyType {
|
||||
DiscordLobbyType_Private = 1,
|
||||
DiscordLobbyType_Public,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchComparison {
|
||||
DiscordLobbySearchComparison_LessThanOrEqual = -2,
|
||||
DiscordLobbySearchComparison_LessThan,
|
||||
DiscordLobbySearchComparison_Equal,
|
||||
DiscordLobbySearchComparison_GreaterThan,
|
||||
DiscordLobbySearchComparison_GreaterThanOrEqual,
|
||||
DiscordLobbySearchComparison_NotEqual,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchCast {
|
||||
DiscordLobbySearchCast_String = 1,
|
||||
DiscordLobbySearchCast_Number,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchDistance {
|
||||
DiscordLobbySearchDistance_Local,
|
||||
DiscordLobbySearchDistance_Default,
|
||||
DiscordLobbySearchDistance_Extended,
|
||||
DiscordLobbySearchDistance_Global,
|
||||
};
|
||||
|
||||
enum EDiscordEntitlementType {
|
||||
DiscordEntitlementType_Purchase = 1,
|
||||
DiscordEntitlementType_PremiumSubscription,
|
||||
DiscordEntitlementType_DeveloperGift,
|
||||
DiscordEntitlementType_TestModePurchase,
|
||||
DiscordEntitlementType_FreePurchase,
|
||||
DiscordEntitlementType_UserGift,
|
||||
DiscordEntitlementType_PremiumPurchase,
|
||||
};
|
||||
|
||||
enum EDiscordSkuType {
|
||||
DiscordSkuType_Application = 1,
|
||||
DiscordSkuType_DLC,
|
||||
DiscordSkuType_Consumable,
|
||||
DiscordSkuType_Bundle,
|
||||
};
|
||||
|
||||
enum EDiscordInputModeType {
|
||||
DiscordInputModeType_VoiceActivity = 0,
|
||||
DiscordInputModeType_PushToTalk,
|
||||
};
|
||||
|
||||
typedef int64_t DiscordClientId;
|
||||
typedef int32_t DiscordVersion;
|
||||
typedef int64_t DiscordSnowflake;
|
||||
typedef int64_t DiscordTimestamp;
|
||||
typedef DiscordSnowflake DiscordUserId;
|
||||
typedef char DiscordLocale[128];
|
||||
typedef char DiscordBranch[4096];
|
||||
typedef DiscordSnowflake DiscordLobbyId;
|
||||
typedef char DiscordLobbySecret[128];
|
||||
typedef char DiscordMetadataKey[256];
|
||||
typedef char DiscordMetadataValue[4096];
|
||||
typedef uint64_t DiscordNetworkPeerId;
|
||||
typedef uint8_t DiscordNetworkChannelId;
|
||||
typedef char DiscordPath[4096];
|
||||
typedef char DiscordDateTime[64];
|
||||
|
||||
struct DiscordUser {
|
||||
DiscordUserId id;
|
||||
char username[256];
|
||||
char discriminator[8];
|
||||
char avatar[128];
|
||||
bool bot;
|
||||
};
|
||||
|
||||
struct DiscordOAuth2Token {
|
||||
char access_token[128];
|
||||
char scopes[1024];
|
||||
DiscordTimestamp expires;
|
||||
};
|
||||
|
||||
struct DiscordImageHandle {
|
||||
enum EDiscordImageType type;
|
||||
int64_t id;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct DiscordImageDimensions {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct DiscordActivityTimestamps {
|
||||
DiscordTimestamp start;
|
||||
DiscordTimestamp end;
|
||||
};
|
||||
|
||||
struct DiscordActivityAssets {
|
||||
char large_image[128];
|
||||
char large_text[128];
|
||||
char small_image[128];
|
||||
char small_text[128];
|
||||
};
|
||||
|
||||
struct DiscordPartySize {
|
||||
int32_t current_size;
|
||||
int32_t max_size;
|
||||
};
|
||||
|
||||
struct DiscordActivityParty {
|
||||
char id[128];
|
||||
struct DiscordPartySize size;
|
||||
};
|
||||
|
||||
struct DiscordActivitySecrets {
|
||||
char match[128];
|
||||
char join[128];
|
||||
char spectate[128];
|
||||
};
|
||||
|
||||
struct DiscordActivity {
|
||||
enum EDiscordActivityType type;
|
||||
int64_t application_id;
|
||||
char name[128];
|
||||
char state[128];
|
||||
char details[128];
|
||||
struct DiscordActivityTimestamps timestamps;
|
||||
struct DiscordActivityAssets assets;
|
||||
struct DiscordActivityParty party;
|
||||
struct DiscordActivitySecrets secrets;
|
||||
bool instance;
|
||||
};
|
||||
|
||||
struct DiscordPresence {
|
||||
enum EDiscordStatus status;
|
||||
struct DiscordActivity activity;
|
||||
};
|
||||
|
||||
struct DiscordRelationship {
|
||||
enum EDiscordRelationshipType type;
|
||||
struct DiscordUser user;
|
||||
struct DiscordPresence presence;
|
||||
};
|
||||
|
||||
struct DiscordLobby {
|
||||
DiscordLobbyId id;
|
||||
enum EDiscordLobbyType type;
|
||||
DiscordUserId owner_id;
|
||||
DiscordLobbySecret secret;
|
||||
uint32_t capacity;
|
||||
bool locked;
|
||||
};
|
||||
|
||||
struct DiscordFileStat {
|
||||
char filename[260];
|
||||
uint64_t size;
|
||||
uint64_t last_modified;
|
||||
};
|
||||
|
||||
struct DiscordEntitlement {
|
||||
DiscordSnowflake id;
|
||||
enum EDiscordEntitlementType type;
|
||||
DiscordSnowflake sku_id;
|
||||
};
|
||||
|
||||
struct DiscordSkuPrice {
|
||||
uint32_t amount;
|
||||
char currency[16];
|
||||
};
|
||||
|
||||
struct DiscordSku {
|
||||
DiscordSnowflake id;
|
||||
enum EDiscordSkuType type;
|
||||
char name[256];
|
||||
struct DiscordSkuPrice price;
|
||||
};
|
||||
|
||||
struct DiscordInputMode {
|
||||
enum EDiscordInputModeType type;
|
||||
char shortcut[256];
|
||||
};
|
||||
|
||||
struct DiscordUserAchievement {
|
||||
DiscordSnowflake user_id;
|
||||
DiscordSnowflake achievement_id;
|
||||
uint8_t percent_complete;
|
||||
DiscordDateTime unlocked_at;
|
||||
};
|
||||
|
||||
struct IDiscordLobbyTransaction {
|
||||
enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction, enum EDiscordLobbyType type);
|
||||
enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction, DiscordUserId owner_id);
|
||||
enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction, uint32_t capacity);
|
||||
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction, DiscordMetadataKey key, DiscordMetadataValue value);
|
||||
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction, DiscordMetadataKey key);
|
||||
enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction, bool locked);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyMemberTransaction {
|
||||
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyMemberTransaction* lobby_member_transaction, DiscordMetadataKey key, DiscordMetadataValue value);
|
||||
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyMemberTransaction* lobby_member_transaction, DiscordMetadataKey key);
|
||||
};
|
||||
|
||||
struct IDiscordLobbySearchQuery {
|
||||
enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query, DiscordMetadataKey key, enum EDiscordLobbySearchComparison comparison, enum EDiscordLobbySearchCast cast, DiscordMetadataValue value);
|
||||
enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query, DiscordMetadataKey key, enum EDiscordLobbySearchCast cast, DiscordMetadataValue value);
|
||||
enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query, uint32_t limit);
|
||||
enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query, enum EDiscordLobbySearchDistance distance);
|
||||
};
|
||||
|
||||
typedef void* IDiscordApplicationEvents;
|
||||
|
||||
struct IDiscordApplicationManager {
|
||||
void (*validate_or_exit)(struct IDiscordApplicationManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale);
|
||||
void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch);
|
||||
void (*get_oauth2_token)(struct IDiscordApplicationManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordOAuth2Token* oauth2_token));
|
||||
void (*get_ticket)(struct IDiscordApplicationManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, const char* data));
|
||||
};
|
||||
|
||||
struct IDiscordUserEvents {
|
||||
void (*on_current_user_update)(void* event_data);
|
||||
};
|
||||
|
||||
struct IDiscordUserManager {
|
||||
enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager, struct DiscordUser* current_user);
|
||||
void (*get_user)(struct IDiscordUserManager* manager, DiscordUserId user_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordUser* user));
|
||||
enum EDiscordResult (*get_current_user_premium_type)(struct IDiscordUserManager* manager, enum EDiscordPremiumType* premium_type);
|
||||
enum EDiscordResult (*current_user_has_flag)(struct IDiscordUserManager* manager, enum EDiscordUserFlag flag, bool* has_flag);
|
||||
};
|
||||
|
||||
typedef void* IDiscordImageEvents;
|
||||
|
||||
struct IDiscordImageManager {
|
||||
void (*fetch)(struct IDiscordImageManager* manager, struct DiscordImageHandle handle, bool refresh, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordImageHandle handle_result));
|
||||
enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager, struct DiscordImageHandle handle, struct DiscordImageDimensions* dimensions);
|
||||
enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager, struct DiscordImageHandle handle, uint8_t* data, uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordActivityEvents {
|
||||
void (*on_activity_join)(void* event_data, const char* secret);
|
||||
void (*on_activity_spectate)(void* event_data, const char* secret);
|
||||
void (*on_activity_join_request)(void* event_data, struct DiscordUser* user);
|
||||
void (*on_activity_invite)(void* event_data, enum EDiscordActivityActionType type, struct DiscordUser* user, struct DiscordActivity* activity);
|
||||
};
|
||||
|
||||
struct IDiscordActivityManager {
|
||||
enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager, const char* command);
|
||||
enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager, uint32_t steam_id);
|
||||
void (*update_activity)(struct IDiscordActivityManager* manager, struct DiscordActivity* activity, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*clear_activity)(struct IDiscordActivityManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_request_reply)(struct IDiscordActivityManager* manager, DiscordUserId user_id, enum EDiscordActivityJoinRequestReply reply, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_invite)(struct IDiscordActivityManager* manager, DiscordUserId user_id, enum EDiscordActivityActionType type, const char* content, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*accept_invite)(struct IDiscordActivityManager* manager, DiscordUserId user_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
struct IDiscordRelationshipEvents {
|
||||
void (*on_refresh)(void* event_data);
|
||||
void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship);
|
||||
};
|
||||
|
||||
struct IDiscordRelationshipManager {
|
||||
void (*filter)(struct IDiscordRelationshipManager* manager, void* filter_data, bool (*filter)(void* filter_data, struct DiscordRelationship* relationship));
|
||||
enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager, DiscordUserId user_id, struct DiscordRelationship* relationship);
|
||||
enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager, uint32_t index, struct DiscordRelationship* relationship);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyEvents {
|
||||
void (*on_lobby_update)(void* event_data, int64_t lobby_id);
|
||||
void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason);
|
||||
void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_lobby_message)(void* event_data, int64_t lobby_id, int64_t user_id, uint8_t* data, uint32_t data_length);
|
||||
void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking);
|
||||
void (*on_network_message)(void* event_data, int64_t lobby_id, int64_t user_id, uint8_t channel_id, uint8_t* data, uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyManager {
|
||||
enum EDiscordResult (*get_lobby_create_transaction)(struct IDiscordLobbyManager* manager, struct IDiscordLobbyTransaction** transaction);
|
||||
enum EDiscordResult (*get_lobby_update_transaction)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, struct IDiscordLobbyTransaction** transaction);
|
||||
enum EDiscordResult (*get_member_update_transaction)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, struct IDiscordLobbyMemberTransaction** transaction);
|
||||
void (*create_lobby)(struct IDiscordLobbyManager* manager, struct IDiscordLobbyTransaction* transaction, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordLobby* lobby));
|
||||
void (*update_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, struct IDiscordLobbyTransaction* transaction, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*delete_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*connect_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordLobbySecret secret, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordLobby* lobby));
|
||||
void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager, DiscordLobbySecret activity_secret, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, struct DiscordLobby* lobby));
|
||||
void (*disconnect_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, struct DiscordLobby* lobby);
|
||||
enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordLobbySecret* secret);
|
||||
enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordMetadataKey key, DiscordMetadataValue* value);
|
||||
enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t index, DiscordMetadataKey* key);
|
||||
enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t* count);
|
||||
enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t* count);
|
||||
enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, int32_t index, DiscordUserId* user_id);
|
||||
enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, struct DiscordUser* user);
|
||||
enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, DiscordMetadataKey key, DiscordMetadataValue* value);
|
||||
enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, int32_t index, DiscordMetadataKey* key);
|
||||
enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, int32_t* count);
|
||||
void (*update_member)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, struct IDiscordLobbyMemberTransaction* transaction, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_lobby_message)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, uint8_t* data, uint32_t data_length, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager, struct IDiscordLobbySearchQuery** query);
|
||||
void (*search)(struct IDiscordLobbyManager* manager, struct IDiscordLobbySearchQuery* query, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager, int32_t index, DiscordLobbyId* lobby_id);
|
||||
void (*connect_voice)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*disconnect_voice)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id);
|
||||
enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id);
|
||||
enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager);
|
||||
enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, uint8_t channel_id, bool reliable);
|
||||
enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager, DiscordLobbyId lobby_id, DiscordUserId user_id, uint8_t channel_id, uint8_t* data, uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordNetworkEvents {
|
||||
void (*on_message)(void* event_data, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id, uint8_t* data, uint32_t data_length);
|
||||
void (*on_route_update)(void* event_data, const char* route_data);
|
||||
};
|
||||
|
||||
struct IDiscordNetworkManager {
|
||||
/**
|
||||
* Get the local peer ID for this process.
|
||||
*/
|
||||
void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id);
|
||||
/**
|
||||
* Send pending network messages.
|
||||
*/
|
||||
enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager);
|
||||
/**
|
||||
* Open a connection to a remote peer.
|
||||
*/
|
||||
enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, const char* route_data);
|
||||
/**
|
||||
* Update the route data for a connected peer.
|
||||
*/
|
||||
enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, const char* route_data);
|
||||
/**
|
||||
* Close the connection to a remote peer.
|
||||
*/
|
||||
enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id);
|
||||
/**
|
||||
* Open a message channel to a connected peer.
|
||||
*/
|
||||
enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id, bool reliable);
|
||||
/**
|
||||
* Close a message channel to a connected peer.
|
||||
*/
|
||||
enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id);
|
||||
/**
|
||||
* Send a message to a connected peer over an opened message channel.
|
||||
*/
|
||||
enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId peer_id, DiscordNetworkChannelId channel_id, uint8_t* data, uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordOverlayEvents {
|
||||
void (*on_toggle)(void* event_data, bool locked);
|
||||
};
|
||||
|
||||
struct IDiscordOverlayManager {
|
||||
void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled);
|
||||
void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked);
|
||||
void (*set_locked)(struct IDiscordOverlayManager* manager, bool locked, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_activity_invite)(struct IDiscordOverlayManager* manager, enum EDiscordActivityActionType type, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_guild_invite)(struct IDiscordOverlayManager* manager, const char* code, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_voice_settings)(struct IDiscordOverlayManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
typedef void* IDiscordStorageEvents;
|
||||
|
||||
struct IDiscordStorageManager {
|
||||
enum EDiscordResult (*read)(struct IDiscordStorageManager* manager, const char* name, uint8_t* data, uint32_t data_length, uint32_t* read);
|
||||
void (*read_async)(struct IDiscordStorageManager* manager, const char* name, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, uint8_t* data, uint32_t data_length));
|
||||
void (*read_async_partial)(struct IDiscordStorageManager* manager, const char* name, uint64_t offset, uint64_t length, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result, uint8_t* data, uint32_t data_length));
|
||||
enum EDiscordResult (*write)(struct IDiscordStorageManager* manager, const char* name, uint8_t* data, uint32_t data_length);
|
||||
void (*write_async)(struct IDiscordStorageManager* manager, const char* name, uint8_t* data, uint32_t data_length, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name);
|
||||
enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager, const char* name, bool* exists);
|
||||
void (*count)(struct IDiscordStorageManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager, const char* name, struct DiscordFileStat* stat);
|
||||
enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager, int32_t index, struct DiscordFileStat* stat);
|
||||
enum EDiscordResult (*get_path)(struct IDiscordStorageManager* manager, DiscordPath* path);
|
||||
};
|
||||
|
||||
struct IDiscordStoreEvents {
|
||||
void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||
void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||
};
|
||||
|
||||
struct IDiscordStoreManager {
|
||||
void (*fetch_skus)(struct IDiscordStoreManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager, DiscordSnowflake sku_id, struct DiscordSku* sku);
|
||||
enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager, int32_t index, struct DiscordSku* sku);
|
||||
void (*fetch_entitlements)(struct IDiscordStoreManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager, DiscordSnowflake entitlement_id, struct DiscordEntitlement* entitlement);
|
||||
enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager, int32_t index, struct DiscordEntitlement* entitlement);
|
||||
enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager, DiscordSnowflake sku_id, bool* has_entitlement);
|
||||
void (*start_purchase)(struct IDiscordStoreManager* manager, DiscordSnowflake sku_id, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
struct IDiscordVoiceEvents {
|
||||
void (*on_settings_update)(void* event_data);
|
||||
};
|
||||
|
||||
struct IDiscordVoiceManager {
|
||||
enum EDiscordResult (*get_input_mode)(struct IDiscordVoiceManager* manager, struct DiscordInputMode* input_mode);
|
||||
void (*set_input_mode)(struct IDiscordVoiceManager* manager, struct DiscordInputMode input_mode, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*is_self_mute)(struct IDiscordVoiceManager* manager, bool* mute);
|
||||
enum EDiscordResult (*set_self_mute)(struct IDiscordVoiceManager* manager, bool mute);
|
||||
enum EDiscordResult (*is_self_deaf)(struct IDiscordVoiceManager* manager, bool* deaf);
|
||||
enum EDiscordResult (*set_self_deaf)(struct IDiscordVoiceManager* manager, bool deaf);
|
||||
enum EDiscordResult (*is_local_mute)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, bool* mute);
|
||||
enum EDiscordResult (*set_local_mute)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, bool mute);
|
||||
enum EDiscordResult (*get_local_volume)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, uint8_t* volume);
|
||||
enum EDiscordResult (*set_local_volume)(struct IDiscordVoiceManager* manager, DiscordSnowflake user_id, uint8_t volume);
|
||||
};
|
||||
|
||||
struct IDiscordAchievementEvents {
|
||||
void (*on_user_achievement_update)(void* event_data, struct DiscordUserAchievement* user_achievement);
|
||||
};
|
||||
|
||||
struct IDiscordAchievementManager {
|
||||
void (*set_user_achievement)(struct IDiscordAchievementManager* manager, DiscordSnowflake achievement_id, uint8_t percent_complete, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*fetch_user_achievements)(struct IDiscordAchievementManager* manager, void* callback_data, void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_user_achievements)(struct IDiscordAchievementManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_user_achievement)(struct IDiscordAchievementManager* manager, DiscordSnowflake user_achievement_id, struct DiscordUserAchievement* user_achievement);
|
||||
enum EDiscordResult (*get_user_achievement_at)(struct IDiscordAchievementManager* manager, int32_t index, struct DiscordUserAchievement* user_achievement);
|
||||
};
|
||||
|
||||
typedef void* IDiscordCoreEvents;
|
||||
|
||||
struct IDiscordCore {
|
||||
void (*destroy)(struct IDiscordCore* core);
|
||||
enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core);
|
||||
void (*set_log_hook)(struct IDiscordCore* core, enum EDiscordLogLevel min_level, void* hook_data, void (*hook)(void* hook_data, enum EDiscordLogLevel level, const char* message));
|
||||
struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordVoiceManager* (*get_voice_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordAchievementManager* (*get_achievement_manager)(struct IDiscordCore* core);
|
||||
};
|
||||
|
||||
struct DiscordCreateParams {
|
||||
DiscordClientId client_id;
|
||||
uint64_t flags;
|
||||
IDiscordCoreEvents* events;
|
||||
void* event_data;
|
||||
IDiscordApplicationEvents* application_events;
|
||||
DiscordVersion application_version;
|
||||
struct IDiscordUserEvents* user_events;
|
||||
DiscordVersion user_version;
|
||||
IDiscordImageEvents* image_events;
|
||||
DiscordVersion image_version;
|
||||
struct IDiscordActivityEvents* activity_events;
|
||||
DiscordVersion activity_version;
|
||||
struct IDiscordRelationshipEvents* relationship_events;
|
||||
DiscordVersion relationship_version;
|
||||
struct IDiscordLobbyEvents* lobby_events;
|
||||
DiscordVersion lobby_version;
|
||||
struct IDiscordNetworkEvents* network_events;
|
||||
DiscordVersion network_version;
|
||||
struct IDiscordOverlayEvents* overlay_events;
|
||||
DiscordVersion overlay_version;
|
||||
IDiscordStorageEvents* storage_events;
|
||||
DiscordVersion storage_version;
|
||||
struct IDiscordStoreEvents* store_events;
|
||||
DiscordVersion store_version;
|
||||
struct IDiscordVoiceEvents* voice_events;
|
||||
DiscordVersion voice_version;
|
||||
struct IDiscordAchievementEvents* achievement_events;
|
||||
DiscordVersion achievement_version;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#else
|
||||
static
|
||||
#endif
|
||||
void DiscordCreateParamsSetDefault(struct DiscordCreateParams* params)
|
||||
{
|
||||
memset(params, 0, sizeof(struct DiscordCreateParams));
|
||||
params->application_version = DISCORD_APPLICATION_MANAGER_VERSION;
|
||||
params->user_version = DISCORD_USER_MANAGER_VERSION;
|
||||
params->image_version = DISCORD_IMAGE_MANAGER_VERSION;
|
||||
params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION;
|
||||
params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION;
|
||||
params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION;
|
||||
params->network_version = DISCORD_NETWORK_MANAGER_VERSION;
|
||||
params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION;
|
||||
params->storage_version = DISCORD_STORAGE_MANAGER_VERSION;
|
||||
params->store_version = DISCORD_STORE_MANAGER_VERSION;
|
||||
params->voice_version = DISCORD_VOICE_MANAGER_VERSION;
|
||||
params->achievement_version = DISCORD_ACHIEVEMENT_MANAGER_VERSION;
|
||||
}
|
||||
|
||||
enum EDiscordResult DiscordCreate(DiscordVersion version, struct DiscordCreateParams* params, struct IDiscordCore** result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
98
libs/discordGameSDK/cpp/achievement_manager.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "achievement_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class AchievementEvents final {
|
||||
public:
|
||||
static void OnUserAchievementUpdate(void* callbackData, DiscordUserAchievement* userAchievement)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->AchievementManager();
|
||||
module.OnUserAchievementUpdate(*reinterpret_cast<UserAchievement const*>(userAchievement));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordAchievementEvents AchievementManager::events_{
|
||||
&AchievementEvents::OnUserAchievementUpdate,
|
||||
};
|
||||
|
||||
void AchievementManager::SetUserAchievement(Snowflake achievementId,
|
||||
std::uint8_t percentComplete,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->set_user_achievement(
|
||||
internal_, achievementId, percentComplete, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void AchievementManager::FetchUserAchievements(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->fetch_user_achievements(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void AchievementManager::CountUserAchievements(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count_user_achievements(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result AchievementManager::GetUserAchievement(Snowflake userAchievementId,
|
||||
UserAchievement* userAchievement)
|
||||
{
|
||||
if (!userAchievement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_user_achievement(
|
||||
internal_, userAchievementId, reinterpret_cast<DiscordUserAchievement*>(userAchievement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result AchievementManager::GetUserAchievementAt(std::int32_t index,
|
||||
UserAchievement* userAchievement)
|
||||
{
|
||||
if (!userAchievement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_user_achievement_at(
|
||||
internal_, index, reinterpret_cast<DiscordUserAchievement*>(userAchievement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
34
libs/discordGameSDK/cpp/achievement_manager.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class AchievementManager final {
|
||||
public:
|
||||
~AchievementManager() = default;
|
||||
|
||||
void SetUserAchievement(Snowflake achievementId,
|
||||
std::uint8_t percentComplete,
|
||||
std::function<void(Result)> callback);
|
||||
void FetchUserAchievements(std::function<void(Result)> callback);
|
||||
void CountUserAchievements(std::int32_t* count);
|
||||
Result GetUserAchievement(Snowflake userAchievementId, UserAchievement* userAchievement);
|
||||
Result GetUserAchievementAt(std::int32_t index, UserAchievement* userAchievement);
|
||||
|
||||
Event<UserAchievement const&> OnUserAchievementUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
AchievementManager() = default;
|
||||
AchievementManager(AchievementManager const& rhs) = delete;
|
||||
AchievementManager& operator=(AchievementManager const& rhs) = delete;
|
||||
AchievementManager(AchievementManager&& rhs) = delete;
|
||||
AchievementManager& operator=(AchievementManager&& rhs) = delete;
|
||||
|
||||
IDiscordAchievementManager* internal_;
|
||||
static IDiscordAchievementEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
177
libs/discordGameSDK/cpp/activity_manager.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "activity_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ActivityEvents final {
|
||||
public:
|
||||
static void OnActivityJoin(void* callbackData, char const* secret)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivityJoin(static_cast<const char*>(secret));
|
||||
}
|
||||
|
||||
static void OnActivitySpectate(void* callbackData, char const* secret)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivitySpectate(static_cast<const char*>(secret));
|
||||
}
|
||||
|
||||
static void OnActivityJoinRequest(void* callbackData, DiscordUser* user)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivityJoinRequest(*reinterpret_cast<User const*>(user));
|
||||
}
|
||||
|
||||
static void OnActivityInvite(void* callbackData,
|
||||
EDiscordActivityActionType type,
|
||||
DiscordUser* user,
|
||||
DiscordActivity* activity)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->ActivityManager();
|
||||
module.OnActivityInvite(static_cast<ActivityActionType>(type),
|
||||
*reinterpret_cast<User const*>(user),
|
||||
*reinterpret_cast<Activity const*>(activity));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordActivityEvents ActivityManager::events_{
|
||||
&ActivityEvents::OnActivityJoin,
|
||||
&ActivityEvents::OnActivitySpectate,
|
||||
&ActivityEvents::OnActivityJoinRequest,
|
||||
&ActivityEvents::OnActivityInvite,
|
||||
};
|
||||
|
||||
Result ActivityManager::RegisterCommand(char const* command)
|
||||
{
|
||||
auto result = internal_->register_command(internal_, const_cast<char*>(command));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result ActivityManager::RegisterSteam(std::uint32_t steamId)
|
||||
{
|
||||
auto result = internal_->register_steam(internal_, steamId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void ActivityManager::UpdateActivity(Activity const& activity, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->update_activity(internal_,
|
||||
reinterpret_cast<DiscordActivity*>(const_cast<Activity*>(&activity)),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::ClearActivity(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->clear_activity(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::SendRequestReply(UserId userId,
|
||||
ActivityJoinRequestReply reply,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->send_request_reply(internal_,
|
||||
userId,
|
||||
static_cast<EDiscordActivityJoinRequestReply>(reply),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::SendInvite(UserId userId,
|
||||
ActivityActionType type,
|
||||
char const* content,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->send_invite(internal_,
|
||||
userId,
|
||||
static_cast<EDiscordActivityActionType>(type),
|
||||
const_cast<char*>(content),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void ActivityManager::AcceptInvite(UserId userId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->accept_invite(internal_, userId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
42
libs/discordGameSDK/cpp/activity_manager.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ActivityManager final {
|
||||
public:
|
||||
~ActivityManager() = default;
|
||||
|
||||
Result RegisterCommand(char const* command);
|
||||
Result RegisterSteam(std::uint32_t steamId);
|
||||
void UpdateActivity(Activity const& activity, std::function<void(Result)> callback);
|
||||
void ClearActivity(std::function<void(Result)> callback);
|
||||
void SendRequestReply(UserId userId,
|
||||
ActivityJoinRequestReply reply,
|
||||
std::function<void(Result)> callback);
|
||||
void SendInvite(UserId userId,
|
||||
ActivityActionType type,
|
||||
char const* content,
|
||||
std::function<void(Result)> callback);
|
||||
void AcceptInvite(UserId userId, std::function<void(Result)> callback);
|
||||
|
||||
Event<char const*> OnActivityJoin;
|
||||
Event<char const*> OnActivitySpectate;
|
||||
Event<User const&> OnActivityJoinRequest;
|
||||
Event<ActivityActionType, User const&, Activity const&> OnActivityInvite;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
ActivityManager() = default;
|
||||
ActivityManager(ActivityManager const& rhs) = delete;
|
||||
ActivityManager& operator=(ActivityManager const& rhs) = delete;
|
||||
ActivityManager(ActivityManager&& rhs) = delete;
|
||||
ActivityManager& operator=(ActivityManager&& rhs) = delete;
|
||||
|
||||
IDiscordActivityManager* internal_;
|
||||
static IDiscordActivityEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
78
libs/discordGameSDK/cpp/application_manager.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "application_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
void ApplicationManager::ValidateOrExit(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->validate_or_exit(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void ApplicationManager::GetCurrentLocale(char locale[128])
|
||||
{
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->get_current_locale(internal_, reinterpret_cast<DiscordLocale*>(locale));
|
||||
}
|
||||
|
||||
void ApplicationManager::GetCurrentBranch(char branch[4096])
|
||||
{
|
||||
if (!branch) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->get_current_branch(internal_, reinterpret_cast<DiscordBranch*>(branch));
|
||||
}
|
||||
|
||||
void ApplicationManager::GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordOAuth2Token* oauth2Token) -> void {
|
||||
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, OAuth2Token const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<OAuth2Token const*>(oauth2Token));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, OAuth2Token const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, OAuth2Token const&)>(std::move(callback)));
|
||||
internal_->get_oauth2_token(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void ApplicationManager::GetTicket(std::function<void(Result, char const*)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result, char const* data) -> void {
|
||||
std::unique_ptr<std::function<void(Result, char const*)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, char const*)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), static_cast<const char*>(data));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, char const*)>> cb{};
|
||||
cb.reset(new std::function<void(Result, char const*)>(std::move(callback)));
|
||||
internal_->get_ticket(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
30
libs/discordGameSDK/cpp/application_manager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ApplicationManager final {
|
||||
public:
|
||||
~ApplicationManager() = default;
|
||||
|
||||
void ValidateOrExit(std::function<void(Result)> callback);
|
||||
void GetCurrentLocale(char locale[128]);
|
||||
void GetCurrentBranch(char branch[4096]);
|
||||
void GetOAuth2Token(std::function<void(Result, OAuth2Token const&)> callback);
|
||||
void GetTicket(std::function<void(Result, char const*)> callback);
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
ApplicationManager() = default;
|
||||
ApplicationManager(ApplicationManager const& rhs) = delete;
|
||||
ApplicationManager& operator=(ApplicationManager const& rhs) = delete;
|
||||
ApplicationManager(ApplicationManager&& rhs) = delete;
|
||||
ApplicationManager& operator=(ApplicationManager&& rhs) = delete;
|
||||
|
||||
IDiscordApplicationManager* internal_;
|
||||
static IDiscordApplicationEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
182
libs/discordGameSDK/cpp/core.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
Result Core::Create(ClientId clientId, std::uint64_t flags, Core** instance)
|
||||
{
|
||||
if (!instance) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
(*instance) = new Core();
|
||||
DiscordCreateParams params{};
|
||||
DiscordCreateParamsSetDefault(¶ms);
|
||||
params.client_id = clientId;
|
||||
params.flags = flags;
|
||||
params.events = nullptr;
|
||||
params.event_data = *instance;
|
||||
params.user_events = &UserManager::events_;
|
||||
params.activity_events = &ActivityManager::events_;
|
||||
params.relationship_events = &RelationshipManager::events_;
|
||||
params.lobby_events = &LobbyManager::events_;
|
||||
params.network_events = &NetworkManager::events_;
|
||||
params.overlay_events = &OverlayManager::events_;
|
||||
params.store_events = &StoreManager::events_;
|
||||
params.voice_events = &VoiceManager::events_;
|
||||
params.achievement_events = &AchievementManager::events_;
|
||||
auto result = DiscordCreate(DISCORD_VERSION, ¶ms, &((*instance)->internal_));
|
||||
if (result != DiscordResult_Ok || !(*instance)->internal_) {
|
||||
delete (*instance);
|
||||
(*instance) = nullptr;
|
||||
}
|
||||
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Core::~Core()
|
||||
{
|
||||
if (internal_) {
|
||||
internal_->destroy(internal_);
|
||||
internal_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Result Core::RunCallbacks()
|
||||
{
|
||||
auto result = internal_->run_callbacks(internal_);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void Core::SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook)
|
||||
{
|
||||
setLogHook_.DisconnectAll();
|
||||
setLogHook_.Connect(std::move(hook));
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordLogLevel level, char const* message) -> void {
|
||||
auto cb(reinterpret_cast<decltype(setLogHook_)*>(callbackData));
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<LogLevel>(level), static_cast<const char*>(message));
|
||||
};
|
||||
|
||||
internal_->set_log_hook(
|
||||
internal_, static_cast<EDiscordLogLevel>(minLevel), &setLogHook_, wrapper);
|
||||
}
|
||||
|
||||
discord::ApplicationManager& Core::ApplicationManager()
|
||||
{
|
||||
if (!applicationManager_.internal_) {
|
||||
applicationManager_.internal_ = internal_->get_application_manager(internal_);
|
||||
}
|
||||
|
||||
return applicationManager_;
|
||||
}
|
||||
|
||||
discord::UserManager& Core::UserManager()
|
||||
{
|
||||
if (!userManager_.internal_) {
|
||||
userManager_.internal_ = internal_->get_user_manager(internal_);
|
||||
}
|
||||
|
||||
return userManager_;
|
||||
}
|
||||
|
||||
discord::ImageManager& Core::ImageManager()
|
||||
{
|
||||
if (!imageManager_.internal_) {
|
||||
imageManager_.internal_ = internal_->get_image_manager(internal_);
|
||||
}
|
||||
|
||||
return imageManager_;
|
||||
}
|
||||
|
||||
discord::ActivityManager& Core::ActivityManager()
|
||||
{
|
||||
if (!activityManager_.internal_) {
|
||||
activityManager_.internal_ = internal_->get_activity_manager(internal_);
|
||||
}
|
||||
|
||||
return activityManager_;
|
||||
}
|
||||
|
||||
discord::RelationshipManager& Core::RelationshipManager()
|
||||
{
|
||||
if (!relationshipManager_.internal_) {
|
||||
relationshipManager_.internal_ = internal_->get_relationship_manager(internal_);
|
||||
}
|
||||
|
||||
return relationshipManager_;
|
||||
}
|
||||
|
||||
discord::LobbyManager& Core::LobbyManager()
|
||||
{
|
||||
if (!lobbyManager_.internal_) {
|
||||
lobbyManager_.internal_ = internal_->get_lobby_manager(internal_);
|
||||
}
|
||||
|
||||
return lobbyManager_;
|
||||
}
|
||||
|
||||
discord::NetworkManager& Core::NetworkManager()
|
||||
{
|
||||
if (!networkManager_.internal_) {
|
||||
networkManager_.internal_ = internal_->get_network_manager(internal_);
|
||||
}
|
||||
|
||||
return networkManager_;
|
||||
}
|
||||
|
||||
discord::OverlayManager& Core::OverlayManager()
|
||||
{
|
||||
if (!overlayManager_.internal_) {
|
||||
overlayManager_.internal_ = internal_->get_overlay_manager(internal_);
|
||||
}
|
||||
|
||||
return overlayManager_;
|
||||
}
|
||||
|
||||
discord::StorageManager& Core::StorageManager()
|
||||
{
|
||||
if (!storageManager_.internal_) {
|
||||
storageManager_.internal_ = internal_->get_storage_manager(internal_);
|
||||
}
|
||||
|
||||
return storageManager_;
|
||||
}
|
||||
|
||||
discord::StoreManager& Core::StoreManager()
|
||||
{
|
||||
if (!storeManager_.internal_) {
|
||||
storeManager_.internal_ = internal_->get_store_manager(internal_);
|
||||
}
|
||||
|
||||
return storeManager_;
|
||||
}
|
||||
|
||||
discord::VoiceManager& Core::VoiceManager()
|
||||
{
|
||||
if (!voiceManager_.internal_) {
|
||||
voiceManager_.internal_ = internal_->get_voice_manager(internal_);
|
||||
}
|
||||
|
||||
return voiceManager_;
|
||||
}
|
||||
|
||||
discord::AchievementManager& Core::AchievementManager()
|
||||
{
|
||||
if (!achievementManager_.internal_) {
|
||||
achievementManager_.internal_ = internal_->get_achievement_manager(internal_);
|
||||
}
|
||||
|
||||
return achievementManager_;
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
64
libs/discordGameSDK/cpp/core.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "application_manager.h"
|
||||
#include "user_manager.h"
|
||||
#include "image_manager.h"
|
||||
#include "activity_manager.h"
|
||||
#include "relationship_manager.h"
|
||||
#include "lobby_manager.h"
|
||||
#include "network_manager.h"
|
||||
#include "overlay_manager.h"
|
||||
#include "storage_manager.h"
|
||||
#include "store_manager.h"
|
||||
#include "voice_manager.h"
|
||||
#include "achievement_manager.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class Core final {
|
||||
public:
|
||||
static Result Create(ClientId clientId, std::uint64_t flags, Core** instance);
|
||||
|
||||
~Core();
|
||||
|
||||
Result RunCallbacks();
|
||||
void SetLogHook(LogLevel minLevel, std::function<void(LogLevel, char const*)> hook);
|
||||
|
||||
discord::ApplicationManager& ApplicationManager();
|
||||
discord::UserManager& UserManager();
|
||||
discord::ImageManager& ImageManager();
|
||||
discord::ActivityManager& ActivityManager();
|
||||
discord::RelationshipManager& RelationshipManager();
|
||||
discord::LobbyManager& LobbyManager();
|
||||
discord::NetworkManager& NetworkManager();
|
||||
discord::OverlayManager& OverlayManager();
|
||||
discord::StorageManager& StorageManager();
|
||||
discord::StoreManager& StoreManager();
|
||||
discord::VoiceManager& VoiceManager();
|
||||
discord::AchievementManager& AchievementManager();
|
||||
|
||||
private:
|
||||
Core() = default;
|
||||
Core(Core const& rhs) = delete;
|
||||
Core& operator=(Core const& rhs) = delete;
|
||||
Core(Core&& rhs) = delete;
|
||||
Core& operator=(Core&& rhs) = delete;
|
||||
|
||||
IDiscordCore* internal_;
|
||||
Event<LogLevel, char const*> setLogHook_;
|
||||
discord::ApplicationManager applicationManager_;
|
||||
discord::UserManager userManager_;
|
||||
discord::ImageManager imageManager_;
|
||||
discord::ActivityManager activityManager_;
|
||||
discord::RelationshipManager relationshipManager_;
|
||||
discord::LobbyManager lobbyManager_;
|
||||
discord::NetworkManager networkManager_;
|
||||
discord::OverlayManager overlayManager_;
|
||||
discord::StorageManager storageManager_;
|
||||
discord::StoreManager storeManager_;
|
||||
discord::VoiceManager voiceManager_;
|
||||
discord::AchievementManager achievementManager_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
16
libs/discordGameSDK/cpp/discord.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "core.h"
|
||||
#include "application_manager.h"
|
||||
#include "user_manager.h"
|
||||
#include "image_manager.h"
|
||||
#include "activity_manager.h"
|
||||
#include "relationship_manager.h"
|
||||
#include "lobby_manager.h"
|
||||
#include "network_manager.h"
|
||||
#include "overlay_manager.h"
|
||||
#include "storage_manager.h"
|
||||
#include "store_manager.h"
|
||||
#include "voice_manager.h"
|
||||
#include "achievement_manager.h"
|
||||
59
libs/discordGameSDK/cpp/event.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace discord {
|
||||
|
||||
template <typename... Args>
|
||||
class Event final {
|
||||
public:
|
||||
using Token = int;
|
||||
|
||||
Event() { slots_.reserve(4); }
|
||||
|
||||
Event(Event const&) = default;
|
||||
Event(Event&&) = default;
|
||||
~Event() = default;
|
||||
|
||||
Event& operator=(Event const&) = default;
|
||||
Event& operator=(Event&&) = default;
|
||||
|
||||
template <typename EventHandler>
|
||||
Token Connect(EventHandler slot)
|
||||
{
|
||||
slots_.emplace_back(Slot{nextToken_, std::move(slot)});
|
||||
return nextToken_++;
|
||||
}
|
||||
|
||||
void Disconnect(Token token)
|
||||
{
|
||||
for (auto& slot : slots_) {
|
||||
if (slot.token == token) {
|
||||
slot = slots_.back();
|
||||
slots_.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisconnectAll() { slots_ = {}; }
|
||||
|
||||
void operator()(Args... args)
|
||||
{
|
||||
for (auto const& slot : slots_) {
|
||||
slot.fn(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Slot {
|
||||
Token token;
|
||||
std::function<void(Args...)> fn;
|
||||
};
|
||||
|
||||
Token nextToken_{};
|
||||
std::vector<Slot> slots_{};
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
942
libs/discordGameSDK/cpp/ffi.h
Normal file
@@ -0,0 +1,942 @@
|
||||
#ifndef _DISCORD_GAME_SDK_H_
|
||||
#define _DISCORD_GAME_SDK_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#define DISCORD_VERSION 2
|
||||
#define DISCORD_APPLICATION_MANAGER_VERSION 1
|
||||
#define DISCORD_USER_MANAGER_VERSION 1
|
||||
#define DISCORD_IMAGE_MANAGER_VERSION 1
|
||||
#define DISCORD_ACTIVITY_MANAGER_VERSION 1
|
||||
#define DISCORD_RELATIONSHIP_MANAGER_VERSION 1
|
||||
#define DISCORD_LOBBY_MANAGER_VERSION 1
|
||||
#define DISCORD_NETWORK_MANAGER_VERSION 1
|
||||
#define DISCORD_OVERLAY_MANAGER_VERSION 1
|
||||
#define DISCORD_STORAGE_MANAGER_VERSION 1
|
||||
#define DISCORD_STORE_MANAGER_VERSION 1
|
||||
#define DISCORD_VOICE_MANAGER_VERSION 1
|
||||
#define DISCORD_ACHIEVEMENT_MANAGER_VERSION 1
|
||||
|
||||
enum EDiscordResult {
|
||||
DiscordResult_Ok = 0,
|
||||
DiscordResult_ServiceUnavailable = 1,
|
||||
DiscordResult_InvalidVersion = 2,
|
||||
DiscordResult_LockFailed = 3,
|
||||
DiscordResult_InternalError = 4,
|
||||
DiscordResult_InvalidPayload = 5,
|
||||
DiscordResult_InvalidCommand = 6,
|
||||
DiscordResult_InvalidPermissions = 7,
|
||||
DiscordResult_NotFetched = 8,
|
||||
DiscordResult_NotFound = 9,
|
||||
DiscordResult_Conflict = 10,
|
||||
DiscordResult_InvalidSecret = 11,
|
||||
DiscordResult_InvalidJoinSecret = 12,
|
||||
DiscordResult_NoEligibleActivity = 13,
|
||||
DiscordResult_InvalidInvite = 14,
|
||||
DiscordResult_NotAuthenticated = 15,
|
||||
DiscordResult_InvalidAccessToken = 16,
|
||||
DiscordResult_ApplicationMismatch = 17,
|
||||
DiscordResult_InvalidDataUrl = 18,
|
||||
DiscordResult_InvalidBase64 = 19,
|
||||
DiscordResult_NotFiltered = 20,
|
||||
DiscordResult_LobbyFull = 21,
|
||||
DiscordResult_InvalidLobbySecret = 22,
|
||||
DiscordResult_InvalidFilename = 23,
|
||||
DiscordResult_InvalidFileSize = 24,
|
||||
DiscordResult_InvalidEntitlement = 25,
|
||||
DiscordResult_NotInstalled = 26,
|
||||
DiscordResult_NotRunning = 27,
|
||||
DiscordResult_InsufficientBuffer = 28,
|
||||
DiscordResult_PurchaseCanceled = 29,
|
||||
DiscordResult_InvalidGuild = 30,
|
||||
DiscordResult_InvalidEvent = 31,
|
||||
DiscordResult_InvalidChannel = 32,
|
||||
DiscordResult_InvalidOrigin = 33,
|
||||
DiscordResult_RateLimited = 34,
|
||||
DiscordResult_OAuth2Error = 35,
|
||||
DiscordResult_SelectChannelTimeout = 36,
|
||||
DiscordResult_GetGuildTimeout = 37,
|
||||
DiscordResult_SelectVoiceForceRequired = 38,
|
||||
DiscordResult_CaptureShortcutAlreadyListening = 39,
|
||||
DiscordResult_UnauthorizedForAchievement = 40,
|
||||
DiscordResult_InvalidGiftCode = 41,
|
||||
DiscordResult_PurchaseError = 42,
|
||||
DiscordResult_TransactionAborted = 43,
|
||||
};
|
||||
|
||||
enum EDiscordCreateFlags {
|
||||
DiscordCreateFlags_Default = 0,
|
||||
DiscordCreateFlags_NoRequireDiscord = 1,
|
||||
};
|
||||
|
||||
enum EDiscordLogLevel {
|
||||
DiscordLogLevel_Error = 1,
|
||||
DiscordLogLevel_Warn,
|
||||
DiscordLogLevel_Info,
|
||||
DiscordLogLevel_Debug,
|
||||
};
|
||||
|
||||
enum EDiscordUserFlag {
|
||||
DiscordUserFlag_Partner = 2,
|
||||
DiscordUserFlag_HypeSquadEvents = 4,
|
||||
DiscordUserFlag_HypeSquadHouse1 = 64,
|
||||
DiscordUserFlag_HypeSquadHouse2 = 128,
|
||||
DiscordUserFlag_HypeSquadHouse3 = 256,
|
||||
};
|
||||
|
||||
enum EDiscordPremiumType {
|
||||
DiscordPremiumType_None = 0,
|
||||
DiscordPremiumType_Tier1 = 1,
|
||||
DiscordPremiumType_Tier2 = 2,
|
||||
};
|
||||
|
||||
enum EDiscordImageType {
|
||||
DiscordImageType_User,
|
||||
};
|
||||
|
||||
enum EDiscordActivityType {
|
||||
DiscordActivityType_Playing,
|
||||
DiscordActivityType_Streaming,
|
||||
DiscordActivityType_Listening,
|
||||
DiscordActivityType_Watching,
|
||||
};
|
||||
|
||||
enum EDiscordActivityActionType {
|
||||
DiscordActivityActionType_Join = 1,
|
||||
DiscordActivityActionType_Spectate,
|
||||
};
|
||||
|
||||
enum EDiscordActivityJoinRequestReply {
|
||||
DiscordActivityJoinRequestReply_No,
|
||||
DiscordActivityJoinRequestReply_Yes,
|
||||
DiscordActivityJoinRequestReply_Ignore,
|
||||
};
|
||||
|
||||
enum EDiscordStatus {
|
||||
DiscordStatus_Offline = 0,
|
||||
DiscordStatus_Online = 1,
|
||||
DiscordStatus_Idle = 2,
|
||||
DiscordStatus_DoNotDisturb = 3,
|
||||
};
|
||||
|
||||
enum EDiscordRelationshipType {
|
||||
DiscordRelationshipType_None,
|
||||
DiscordRelationshipType_Friend,
|
||||
DiscordRelationshipType_Blocked,
|
||||
DiscordRelationshipType_PendingIncoming,
|
||||
DiscordRelationshipType_PendingOutgoing,
|
||||
DiscordRelationshipType_Implicit,
|
||||
};
|
||||
|
||||
enum EDiscordLobbyType {
|
||||
DiscordLobbyType_Private = 1,
|
||||
DiscordLobbyType_Public,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchComparison {
|
||||
DiscordLobbySearchComparison_LessThanOrEqual = -2,
|
||||
DiscordLobbySearchComparison_LessThan,
|
||||
DiscordLobbySearchComparison_Equal,
|
||||
DiscordLobbySearchComparison_GreaterThan,
|
||||
DiscordLobbySearchComparison_GreaterThanOrEqual,
|
||||
DiscordLobbySearchComparison_NotEqual,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchCast {
|
||||
DiscordLobbySearchCast_String = 1,
|
||||
DiscordLobbySearchCast_Number,
|
||||
};
|
||||
|
||||
enum EDiscordLobbySearchDistance {
|
||||
DiscordLobbySearchDistance_Local,
|
||||
DiscordLobbySearchDistance_Default,
|
||||
DiscordLobbySearchDistance_Extended,
|
||||
DiscordLobbySearchDistance_Global,
|
||||
};
|
||||
|
||||
enum EDiscordEntitlementType {
|
||||
DiscordEntitlementType_Purchase = 1,
|
||||
DiscordEntitlementType_PremiumSubscription,
|
||||
DiscordEntitlementType_DeveloperGift,
|
||||
DiscordEntitlementType_TestModePurchase,
|
||||
DiscordEntitlementType_FreePurchase,
|
||||
DiscordEntitlementType_UserGift,
|
||||
DiscordEntitlementType_PremiumPurchase,
|
||||
};
|
||||
|
||||
enum EDiscordSkuType {
|
||||
DiscordSkuType_Application = 1,
|
||||
DiscordSkuType_DLC,
|
||||
DiscordSkuType_Consumable,
|
||||
DiscordSkuType_Bundle,
|
||||
};
|
||||
|
||||
enum EDiscordInputModeType {
|
||||
DiscordInputModeType_VoiceActivity = 0,
|
||||
DiscordInputModeType_PushToTalk,
|
||||
};
|
||||
|
||||
typedef int64_t DiscordClientId;
|
||||
typedef int32_t DiscordVersion;
|
||||
typedef int64_t DiscordSnowflake;
|
||||
typedef int64_t DiscordTimestamp;
|
||||
typedef DiscordSnowflake DiscordUserId;
|
||||
typedef char DiscordLocale[128];
|
||||
typedef char DiscordBranch[4096];
|
||||
typedef DiscordSnowflake DiscordLobbyId;
|
||||
typedef char DiscordLobbySecret[128];
|
||||
typedef char DiscordMetadataKey[256];
|
||||
typedef char DiscordMetadataValue[4096];
|
||||
typedef uint64_t DiscordNetworkPeerId;
|
||||
typedef uint8_t DiscordNetworkChannelId;
|
||||
typedef char DiscordPath[4096];
|
||||
typedef char DiscordDateTime[64];
|
||||
|
||||
struct DiscordUser {
|
||||
DiscordUserId id;
|
||||
char username[256];
|
||||
char discriminator[8];
|
||||
char avatar[128];
|
||||
bool bot;
|
||||
};
|
||||
|
||||
struct DiscordOAuth2Token {
|
||||
char access_token[128];
|
||||
char scopes[1024];
|
||||
DiscordTimestamp expires;
|
||||
};
|
||||
|
||||
struct DiscordImageHandle {
|
||||
enum EDiscordImageType type;
|
||||
int64_t id;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct DiscordImageDimensions {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
struct DiscordActivityTimestamps {
|
||||
DiscordTimestamp start;
|
||||
DiscordTimestamp end;
|
||||
};
|
||||
|
||||
struct DiscordActivityAssets {
|
||||
char large_image[128];
|
||||
char large_text[128];
|
||||
char small_image[128];
|
||||
char small_text[128];
|
||||
};
|
||||
|
||||
struct DiscordPartySize {
|
||||
int32_t current_size;
|
||||
int32_t max_size;
|
||||
};
|
||||
|
||||
struct DiscordActivityParty {
|
||||
char id[128];
|
||||
struct DiscordPartySize size;
|
||||
};
|
||||
|
||||
struct DiscordActivitySecrets {
|
||||
char match[128];
|
||||
char join[128];
|
||||
char spectate[128];
|
||||
};
|
||||
|
||||
struct DiscordActivity {
|
||||
enum EDiscordActivityType type;
|
||||
int64_t application_id;
|
||||
char name[128];
|
||||
char state[128];
|
||||
char details[128];
|
||||
struct DiscordActivityTimestamps timestamps;
|
||||
struct DiscordActivityAssets assets;
|
||||
struct DiscordActivityParty party;
|
||||
struct DiscordActivitySecrets secrets;
|
||||
bool instance;
|
||||
};
|
||||
|
||||
struct DiscordPresence {
|
||||
enum EDiscordStatus status;
|
||||
struct DiscordActivity activity;
|
||||
};
|
||||
|
||||
struct DiscordRelationship {
|
||||
enum EDiscordRelationshipType type;
|
||||
struct DiscordUser user;
|
||||
struct DiscordPresence presence;
|
||||
};
|
||||
|
||||
struct DiscordLobby {
|
||||
DiscordLobbyId id;
|
||||
enum EDiscordLobbyType type;
|
||||
DiscordUserId owner_id;
|
||||
DiscordLobbySecret secret;
|
||||
uint32_t capacity;
|
||||
bool locked;
|
||||
};
|
||||
|
||||
struct DiscordFileStat {
|
||||
char filename[260];
|
||||
uint64_t size;
|
||||
uint64_t last_modified;
|
||||
};
|
||||
|
||||
struct DiscordEntitlement {
|
||||
DiscordSnowflake id;
|
||||
enum EDiscordEntitlementType type;
|
||||
DiscordSnowflake sku_id;
|
||||
};
|
||||
|
||||
struct DiscordSkuPrice {
|
||||
uint32_t amount;
|
||||
char currency[16];
|
||||
};
|
||||
|
||||
struct DiscordSku {
|
||||
DiscordSnowflake id;
|
||||
enum EDiscordSkuType type;
|
||||
char name[256];
|
||||
struct DiscordSkuPrice price;
|
||||
};
|
||||
|
||||
struct DiscordInputMode {
|
||||
enum EDiscordInputModeType type;
|
||||
char shortcut[256];
|
||||
};
|
||||
|
||||
struct DiscordUserAchievement {
|
||||
DiscordSnowflake user_id;
|
||||
DiscordSnowflake achievement_id;
|
||||
uint8_t percent_complete;
|
||||
DiscordDateTime unlocked_at;
|
||||
};
|
||||
|
||||
struct IDiscordLobbyTransaction {
|
||||
enum EDiscordResult (*set_type)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
enum EDiscordLobbyType type);
|
||||
enum EDiscordResult (*set_owner)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
DiscordUserId owner_id);
|
||||
enum EDiscordResult (*set_capacity)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
uint32_t capacity);
|
||||
enum EDiscordResult (*set_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*delete_metadata)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
DiscordMetadataKey key);
|
||||
enum EDiscordResult (*set_locked)(struct IDiscordLobbyTransaction* lobby_transaction,
|
||||
bool locked);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyMemberTransaction {
|
||||
enum EDiscordResult (*set_metadata)(
|
||||
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*delete_metadata)(
|
||||
struct IDiscordLobbyMemberTransaction* lobby_member_transaction,
|
||||
DiscordMetadataKey key);
|
||||
};
|
||||
|
||||
struct IDiscordLobbySearchQuery {
|
||||
enum EDiscordResult (*filter)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
DiscordMetadataKey key,
|
||||
enum EDiscordLobbySearchComparison comparison,
|
||||
enum EDiscordLobbySearchCast cast,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*sort)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
DiscordMetadataKey key,
|
||||
enum EDiscordLobbySearchCast cast,
|
||||
DiscordMetadataValue value);
|
||||
enum EDiscordResult (*limit)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
uint32_t limit);
|
||||
enum EDiscordResult (*distance)(struct IDiscordLobbySearchQuery* lobby_search_query,
|
||||
enum EDiscordLobbySearchDistance distance);
|
||||
};
|
||||
|
||||
typedef void* IDiscordApplicationEvents;
|
||||
|
||||
struct IDiscordApplicationManager {
|
||||
void (*validate_or_exit)(struct IDiscordApplicationManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*get_current_locale)(struct IDiscordApplicationManager* manager, DiscordLocale* locale);
|
||||
void (*get_current_branch)(struct IDiscordApplicationManager* manager, DiscordBranch* branch);
|
||||
void (*get_oauth2_token)(struct IDiscordApplicationManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordOAuth2Token* oauth2_token));
|
||||
void (*get_ticket)(struct IDiscordApplicationManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
const char* data));
|
||||
};
|
||||
|
||||
struct IDiscordUserEvents {
|
||||
void (*on_current_user_update)(void* event_data);
|
||||
};
|
||||
|
||||
struct IDiscordUserManager {
|
||||
enum EDiscordResult (*get_current_user)(struct IDiscordUserManager* manager,
|
||||
struct DiscordUser* current_user);
|
||||
void (*get_user)(struct IDiscordUserManager* manager,
|
||||
DiscordUserId user_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordUser* user));
|
||||
enum EDiscordResult (*get_current_user_premium_type)(struct IDiscordUserManager* manager,
|
||||
enum EDiscordPremiumType* premium_type);
|
||||
enum EDiscordResult (*current_user_has_flag)(struct IDiscordUserManager* manager,
|
||||
enum EDiscordUserFlag flag,
|
||||
bool* has_flag);
|
||||
};
|
||||
|
||||
typedef void* IDiscordImageEvents;
|
||||
|
||||
struct IDiscordImageManager {
|
||||
void (*fetch)(struct IDiscordImageManager* manager,
|
||||
struct DiscordImageHandle handle,
|
||||
bool refresh,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordImageHandle handle_result));
|
||||
enum EDiscordResult (*get_dimensions)(struct IDiscordImageManager* manager,
|
||||
struct DiscordImageHandle handle,
|
||||
struct DiscordImageDimensions* dimensions);
|
||||
enum EDiscordResult (*get_data)(struct IDiscordImageManager* manager,
|
||||
struct DiscordImageHandle handle,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordActivityEvents {
|
||||
void (*on_activity_join)(void* event_data, const char* secret);
|
||||
void (*on_activity_spectate)(void* event_data, const char* secret);
|
||||
void (*on_activity_join_request)(void* event_data, struct DiscordUser* user);
|
||||
void (*on_activity_invite)(void* event_data,
|
||||
enum EDiscordActivityActionType type,
|
||||
struct DiscordUser* user,
|
||||
struct DiscordActivity* activity);
|
||||
};
|
||||
|
||||
struct IDiscordActivityManager {
|
||||
enum EDiscordResult (*register_command)(struct IDiscordActivityManager* manager,
|
||||
const char* command);
|
||||
enum EDiscordResult (*register_steam)(struct IDiscordActivityManager* manager,
|
||||
uint32_t steam_id);
|
||||
void (*update_activity)(struct IDiscordActivityManager* manager,
|
||||
struct DiscordActivity* activity,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*clear_activity)(struct IDiscordActivityManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_request_reply)(struct IDiscordActivityManager* manager,
|
||||
DiscordUserId user_id,
|
||||
enum EDiscordActivityJoinRequestReply reply,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_invite)(struct IDiscordActivityManager* manager,
|
||||
DiscordUserId user_id,
|
||||
enum EDiscordActivityActionType type,
|
||||
const char* content,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*accept_invite)(struct IDiscordActivityManager* manager,
|
||||
DiscordUserId user_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
struct IDiscordRelationshipEvents {
|
||||
void (*on_refresh)(void* event_data);
|
||||
void (*on_relationship_update)(void* event_data, struct DiscordRelationship* relationship);
|
||||
};
|
||||
|
||||
struct IDiscordRelationshipManager {
|
||||
void (*filter)(struct IDiscordRelationshipManager* manager,
|
||||
void* filter_data,
|
||||
bool (*filter)(void* filter_data, struct DiscordRelationship* relationship));
|
||||
enum EDiscordResult (*count)(struct IDiscordRelationshipManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get)(struct IDiscordRelationshipManager* manager,
|
||||
DiscordUserId user_id,
|
||||
struct DiscordRelationship* relationship);
|
||||
enum EDiscordResult (*get_at)(struct IDiscordRelationshipManager* manager,
|
||||
uint32_t index,
|
||||
struct DiscordRelationship* relationship);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyEvents {
|
||||
void (*on_lobby_update)(void* event_data, int64_t lobby_id);
|
||||
void (*on_lobby_delete)(void* event_data, int64_t lobby_id, uint32_t reason);
|
||||
void (*on_member_connect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_member_update)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_member_disconnect)(void* event_data, int64_t lobby_id, int64_t user_id);
|
||||
void (*on_lobby_message)(void* event_data,
|
||||
int64_t lobby_id,
|
||||
int64_t user_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
void (*on_speaking)(void* event_data, int64_t lobby_id, int64_t user_id, bool speaking);
|
||||
void (*on_network_message)(void* event_data,
|
||||
int64_t lobby_id,
|
||||
int64_t user_id,
|
||||
uint8_t channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordLobbyManager {
|
||||
enum EDiscordResult (*get_lobby_create_transaction)(
|
||||
struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbyTransaction** transaction);
|
||||
enum EDiscordResult (*get_lobby_update_transaction)(
|
||||
struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
struct IDiscordLobbyTransaction** transaction);
|
||||
enum EDiscordResult (*get_member_update_transaction)(
|
||||
struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
struct IDiscordLobbyMemberTransaction** transaction);
|
||||
void (*create_lobby)(struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbyTransaction* transaction,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordLobby* lobby));
|
||||
void (*update_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
struct IDiscordLobbyTransaction* transaction,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*delete_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*connect_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordLobbySecret secret,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordLobby* lobby));
|
||||
void (*connect_lobby_with_activity_secret)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbySecret activity_secret,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
struct DiscordLobby* lobby));
|
||||
void (*disconnect_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*get_lobby)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
struct DiscordLobby* lobby);
|
||||
enum EDiscordResult (*get_lobby_activity_secret)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordLobbySecret* secret);
|
||||
enum EDiscordResult (*get_lobby_metadata_value)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue* value);
|
||||
enum EDiscordResult (*get_lobby_metadata_key)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t index,
|
||||
DiscordMetadataKey* key);
|
||||
enum EDiscordResult (*lobby_metadata_count)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t* count);
|
||||
enum EDiscordResult (*member_count)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t* count);
|
||||
enum EDiscordResult (*get_member_user_id)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
int32_t index,
|
||||
DiscordUserId* user_id);
|
||||
enum EDiscordResult (*get_member_user)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
struct DiscordUser* user);
|
||||
enum EDiscordResult (*get_member_metadata_value)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
DiscordMetadataKey key,
|
||||
DiscordMetadataValue* value);
|
||||
enum EDiscordResult (*get_member_metadata_key)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
int32_t index,
|
||||
DiscordMetadataKey* key);
|
||||
enum EDiscordResult (*member_metadata_count)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
int32_t* count);
|
||||
void (*update_member)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
struct IDiscordLobbyMemberTransaction* transaction,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*send_lobby_message)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*get_search_query)(struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbySearchQuery** query);
|
||||
void (*search)(struct IDiscordLobbyManager* manager,
|
||||
struct IDiscordLobbySearchQuery* query,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*lobby_count)(struct IDiscordLobbyManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_lobby_id)(struct IDiscordLobbyManager* manager,
|
||||
int32_t index,
|
||||
DiscordLobbyId* lobby_id);
|
||||
void (*connect_voice)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*disconnect_voice)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*connect_network)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id);
|
||||
enum EDiscordResult (*disconnect_network)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id);
|
||||
enum EDiscordResult (*flush_network)(struct IDiscordLobbyManager* manager);
|
||||
enum EDiscordResult (*open_network_channel)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
uint8_t channel_id,
|
||||
bool reliable);
|
||||
enum EDiscordResult (*send_network_message)(struct IDiscordLobbyManager* manager,
|
||||
DiscordLobbyId lobby_id,
|
||||
DiscordUserId user_id,
|
||||
uint8_t channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordNetworkEvents {
|
||||
void (*on_message)(void* event_data,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
void (*on_route_update)(void* event_data, const char* route_data);
|
||||
};
|
||||
|
||||
struct IDiscordNetworkManager {
|
||||
/**
|
||||
* Get the local peer ID for this process.
|
||||
*/
|
||||
void (*get_peer_id)(struct IDiscordNetworkManager* manager, DiscordNetworkPeerId* peer_id);
|
||||
/**
|
||||
* Send pending network messages.
|
||||
*/
|
||||
enum EDiscordResult (*flush)(struct IDiscordNetworkManager* manager);
|
||||
/**
|
||||
* Open a connection to a remote peer.
|
||||
*/
|
||||
enum EDiscordResult (*open_peer)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
const char* route_data);
|
||||
/**
|
||||
* Update the route data for a connected peer.
|
||||
*/
|
||||
enum EDiscordResult (*update_peer)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
const char* route_data);
|
||||
/**
|
||||
* Close the connection to a remote peer.
|
||||
*/
|
||||
enum EDiscordResult (*close_peer)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id);
|
||||
/**
|
||||
* Open a message channel to a connected peer.
|
||||
*/
|
||||
enum EDiscordResult (*open_channel)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id,
|
||||
bool reliable);
|
||||
/**
|
||||
* Close a message channel to a connected peer.
|
||||
*/
|
||||
enum EDiscordResult (*close_channel)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id);
|
||||
/**
|
||||
* Send a message to a connected peer over an opened message channel.
|
||||
*/
|
||||
enum EDiscordResult (*send_message)(struct IDiscordNetworkManager* manager,
|
||||
DiscordNetworkPeerId peer_id,
|
||||
DiscordNetworkChannelId channel_id,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
};
|
||||
|
||||
struct IDiscordOverlayEvents {
|
||||
void (*on_toggle)(void* event_data, bool locked);
|
||||
};
|
||||
|
||||
struct IDiscordOverlayManager {
|
||||
void (*is_enabled)(struct IDiscordOverlayManager* manager, bool* enabled);
|
||||
void (*is_locked)(struct IDiscordOverlayManager* manager, bool* locked);
|
||||
void (*set_locked)(struct IDiscordOverlayManager* manager,
|
||||
bool locked,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_activity_invite)(struct IDiscordOverlayManager* manager,
|
||||
enum EDiscordActivityActionType type,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_guild_invite)(struct IDiscordOverlayManager* manager,
|
||||
const char* code,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*open_voice_settings)(struct IDiscordOverlayManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
typedef void* IDiscordStorageEvents;
|
||||
|
||||
struct IDiscordStorageManager {
|
||||
enum EDiscordResult (*read)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint8_t* data,
|
||||
uint32_t data_length,
|
||||
uint32_t* read);
|
||||
void (*read_async)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
uint8_t* data,
|
||||
uint32_t data_length));
|
||||
void (*read_async_partial)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint64_t offset,
|
||||
uint64_t length,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result,
|
||||
uint8_t* data,
|
||||
uint32_t data_length));
|
||||
enum EDiscordResult (*write)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint8_t* data,
|
||||
uint32_t data_length);
|
||||
void (*write_async)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
uint8_t* data,
|
||||
uint32_t data_length,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*delete_)(struct IDiscordStorageManager* manager, const char* name);
|
||||
enum EDiscordResult (*exists)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
bool* exists);
|
||||
void (*count)(struct IDiscordStorageManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*stat)(struct IDiscordStorageManager* manager,
|
||||
const char* name,
|
||||
struct DiscordFileStat* stat);
|
||||
enum EDiscordResult (*stat_at)(struct IDiscordStorageManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordFileStat* stat);
|
||||
enum EDiscordResult (*get_path)(struct IDiscordStorageManager* manager, DiscordPath* path);
|
||||
};
|
||||
|
||||
struct IDiscordStoreEvents {
|
||||
void (*on_entitlement_create)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||
void (*on_entitlement_delete)(void* event_data, struct DiscordEntitlement* entitlement);
|
||||
};
|
||||
|
||||
struct IDiscordStoreManager {
|
||||
void (*fetch_skus)(struct IDiscordStoreManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_skus)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_sku)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake sku_id,
|
||||
struct DiscordSku* sku);
|
||||
enum EDiscordResult (*get_sku_at)(struct IDiscordStoreManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordSku* sku);
|
||||
void (*fetch_entitlements)(struct IDiscordStoreManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*count_entitlements)(struct IDiscordStoreManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_entitlement)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake entitlement_id,
|
||||
struct DiscordEntitlement* entitlement);
|
||||
enum EDiscordResult (*get_entitlement_at)(struct IDiscordStoreManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordEntitlement* entitlement);
|
||||
enum EDiscordResult (*has_sku_entitlement)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake sku_id,
|
||||
bool* has_entitlement);
|
||||
void (*start_purchase)(struct IDiscordStoreManager* manager,
|
||||
DiscordSnowflake sku_id,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
};
|
||||
|
||||
struct IDiscordVoiceEvents {
|
||||
void (*on_settings_update)(void* event_data);
|
||||
};
|
||||
|
||||
struct IDiscordVoiceManager {
|
||||
enum EDiscordResult (*get_input_mode)(struct IDiscordVoiceManager* manager,
|
||||
struct DiscordInputMode* input_mode);
|
||||
void (*set_input_mode)(struct IDiscordVoiceManager* manager,
|
||||
struct DiscordInputMode input_mode,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
enum EDiscordResult (*is_self_mute)(struct IDiscordVoiceManager* manager, bool* mute);
|
||||
enum EDiscordResult (*set_self_mute)(struct IDiscordVoiceManager* manager, bool mute);
|
||||
enum EDiscordResult (*is_self_deaf)(struct IDiscordVoiceManager* manager, bool* deaf);
|
||||
enum EDiscordResult (*set_self_deaf)(struct IDiscordVoiceManager* manager, bool deaf);
|
||||
enum EDiscordResult (*is_local_mute)(struct IDiscordVoiceManager* manager,
|
||||
DiscordSnowflake user_id,
|
||||
bool* mute);
|
||||
enum EDiscordResult (*set_local_mute)(struct IDiscordVoiceManager* manager,
|
||||
DiscordSnowflake user_id,
|
||||
bool mute);
|
||||
enum EDiscordResult (*get_local_volume)(struct IDiscordVoiceManager* manager,
|
||||
DiscordSnowflake user_id,
|
||||
uint8_t* volume);
|
||||
enum EDiscordResult (*set_local_volume)(struct IDiscordVoiceManager* manager,
|
||||
DiscordSnowflake user_id,
|
||||
uint8_t volume);
|
||||
};
|
||||
|
||||
struct IDiscordAchievementEvents {
|
||||
void (*on_user_achievement_update)(void* event_data,
|
||||
struct DiscordUserAchievement* user_achievement);
|
||||
};
|
||||
|
||||
struct IDiscordAchievementManager {
|
||||
void (*set_user_achievement)(struct IDiscordAchievementManager* manager,
|
||||
DiscordSnowflake achievement_id,
|
||||
uint8_t percent_complete,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data, enum EDiscordResult result));
|
||||
void (*fetch_user_achievements)(struct IDiscordAchievementManager* manager,
|
||||
void* callback_data,
|
||||
void (*callback)(void* callback_data,
|
||||
enum EDiscordResult result));
|
||||
void (*count_user_achievements)(struct IDiscordAchievementManager* manager, int32_t* count);
|
||||
enum EDiscordResult (*get_user_achievement)(struct IDiscordAchievementManager* manager,
|
||||
DiscordSnowflake user_achievement_id,
|
||||
struct DiscordUserAchievement* user_achievement);
|
||||
enum EDiscordResult (*get_user_achievement_at)(struct IDiscordAchievementManager* manager,
|
||||
int32_t index,
|
||||
struct DiscordUserAchievement* user_achievement);
|
||||
};
|
||||
|
||||
typedef void* IDiscordCoreEvents;
|
||||
|
||||
struct IDiscordCore {
|
||||
void (*destroy)(struct IDiscordCore* core);
|
||||
enum EDiscordResult (*run_callbacks)(struct IDiscordCore* core);
|
||||
void (*set_log_hook)(struct IDiscordCore* core,
|
||||
enum EDiscordLogLevel min_level,
|
||||
void* hook_data,
|
||||
void (*hook)(void* hook_data,
|
||||
enum EDiscordLogLevel level,
|
||||
const char* message));
|
||||
struct IDiscordApplicationManager* (*get_application_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordUserManager* (*get_user_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordImageManager* (*get_image_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordActivityManager* (*get_activity_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordRelationshipManager* (*get_relationship_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordLobbyManager* (*get_lobby_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordNetworkManager* (*get_network_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordOverlayManager* (*get_overlay_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordStorageManager* (*get_storage_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordStoreManager* (*get_store_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordVoiceManager* (*get_voice_manager)(struct IDiscordCore* core);
|
||||
struct IDiscordAchievementManager* (*get_achievement_manager)(struct IDiscordCore* core);
|
||||
};
|
||||
|
||||
struct DiscordCreateParams {
|
||||
DiscordClientId client_id;
|
||||
uint64_t flags;
|
||||
IDiscordCoreEvents* events;
|
||||
void* event_data;
|
||||
IDiscordApplicationEvents* application_events;
|
||||
DiscordVersion application_version;
|
||||
struct IDiscordUserEvents* user_events;
|
||||
DiscordVersion user_version;
|
||||
IDiscordImageEvents* image_events;
|
||||
DiscordVersion image_version;
|
||||
struct IDiscordActivityEvents* activity_events;
|
||||
DiscordVersion activity_version;
|
||||
struct IDiscordRelationshipEvents* relationship_events;
|
||||
DiscordVersion relationship_version;
|
||||
struct IDiscordLobbyEvents* lobby_events;
|
||||
DiscordVersion lobby_version;
|
||||
struct IDiscordNetworkEvents* network_events;
|
||||
DiscordVersion network_version;
|
||||
struct IDiscordOverlayEvents* overlay_events;
|
||||
DiscordVersion overlay_version;
|
||||
IDiscordStorageEvents* storage_events;
|
||||
DiscordVersion storage_version;
|
||||
struct IDiscordStoreEvents* store_events;
|
||||
DiscordVersion store_version;
|
||||
struct IDiscordVoiceEvents* voice_events;
|
||||
DiscordVersion voice_version;
|
||||
struct IDiscordAchievementEvents* achievement_events;
|
||||
DiscordVersion achievement_version;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#else
|
||||
static
|
||||
#endif
|
||||
void
|
||||
DiscordCreateParamsSetDefault(struct DiscordCreateParams* params)
|
||||
{
|
||||
memset(params, 0, sizeof(struct DiscordCreateParams));
|
||||
params->application_version = DISCORD_APPLICATION_MANAGER_VERSION;
|
||||
params->user_version = DISCORD_USER_MANAGER_VERSION;
|
||||
params->image_version = DISCORD_IMAGE_MANAGER_VERSION;
|
||||
params->activity_version = DISCORD_ACTIVITY_MANAGER_VERSION;
|
||||
params->relationship_version = DISCORD_RELATIONSHIP_MANAGER_VERSION;
|
||||
params->lobby_version = DISCORD_LOBBY_MANAGER_VERSION;
|
||||
params->network_version = DISCORD_NETWORK_MANAGER_VERSION;
|
||||
params->overlay_version = DISCORD_OVERLAY_MANAGER_VERSION;
|
||||
params->storage_version = DISCORD_STORAGE_MANAGER_VERSION;
|
||||
params->store_version = DISCORD_STORE_MANAGER_VERSION;
|
||||
params->voice_version = DISCORD_VOICE_MANAGER_VERSION;
|
||||
params->achievement_version = DISCORD_ACHIEVEMENT_MANAGER_VERSION;
|
||||
}
|
||||
|
||||
enum EDiscordResult DiscordCreate(DiscordVersion version,
|
||||
struct DiscordCreateParams* params,
|
||||
struct IDiscordCore** result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
57
libs/discordGameSDK/cpp/image_manager.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "image_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
void ImageManager::Fetch(ImageHandle handle,
|
||||
bool refresh,
|
||||
std::function<void(Result, ImageHandle)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordImageHandle handleResult) -> void {
|
||||
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, ImageHandle)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<ImageHandle const*>(&handleResult));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, ImageHandle)>> cb{};
|
||||
cb.reset(new std::function<void(Result, ImageHandle)>(std::move(callback)));
|
||||
internal_->fetch(internal_,
|
||||
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||
(refresh ? 1 : 0),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
Result ImageManager::GetDimensions(ImageHandle handle, ImageDimensions* dimensions)
|
||||
{
|
||||
if (!dimensions) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_dimensions(internal_,
|
||||
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||
reinterpret_cast<DiscordImageDimensions*>(dimensions));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result ImageManager::GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->get_data(internal_,
|
||||
*reinterpret_cast<DiscordImageHandle const*>(&handle),
|
||||
reinterpret_cast<uint8_t*>(data),
|
||||
dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
28
libs/discordGameSDK/cpp/image_manager.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class ImageManager final {
|
||||
public:
|
||||
~ImageManager() = default;
|
||||
|
||||
void Fetch(ImageHandle handle, bool refresh, std::function<void(Result, ImageHandle)> callback);
|
||||
Result GetDimensions(ImageHandle handle, ImageDimensions* dimensions);
|
||||
Result GetData(ImageHandle handle, std::uint8_t* data, std::uint32_t dataLength);
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
ImageManager() = default;
|
||||
ImageManager(ImageManager const& rhs) = delete;
|
||||
ImageManager& operator=(ImageManager const& rhs) = delete;
|
||||
ImageManager(ImageManager&& rhs) = delete;
|
||||
ImageManager& operator=(ImageManager&& rhs) = delete;
|
||||
|
||||
IDiscordImageManager* internal_;
|
||||
static IDiscordImageEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
547
libs/discordGameSDK/cpp/lobby_manager.cpp
Normal file
@@ -0,0 +1,547 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "lobby_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class LobbyEvents final {
|
||||
public:
|
||||
static void OnLobbyUpdate(void* callbackData, int64_t lobbyId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnLobbyUpdate(lobbyId);
|
||||
}
|
||||
|
||||
static void OnLobbyDelete(void* callbackData, int64_t lobbyId, uint32_t reason)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnLobbyDelete(lobbyId, reason);
|
||||
}
|
||||
|
||||
static void OnMemberConnect(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnMemberConnect(lobbyId, userId);
|
||||
}
|
||||
|
||||
static void OnMemberUpdate(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnMemberUpdate(lobbyId, userId);
|
||||
}
|
||||
|
||||
static void OnMemberDisconnect(void* callbackData, int64_t lobbyId, int64_t userId)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnMemberDisconnect(lobbyId, userId);
|
||||
}
|
||||
|
||||
static void OnLobbyMessage(void* callbackData,
|
||||
int64_t lobbyId,
|
||||
int64_t userId,
|
||||
uint8_t* data,
|
||||
uint32_t dataLength)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnLobbyMessage(lobbyId, userId, data, dataLength);
|
||||
}
|
||||
|
||||
static void OnSpeaking(void* callbackData, int64_t lobbyId, int64_t userId, bool speaking)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnSpeaking(lobbyId, userId, (speaking != 0));
|
||||
}
|
||||
|
||||
static void OnNetworkMessage(void* callbackData,
|
||||
int64_t lobbyId,
|
||||
int64_t userId,
|
||||
uint8_t channelId,
|
||||
uint8_t* data,
|
||||
uint32_t dataLength)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->LobbyManager();
|
||||
module.OnNetworkMessage(lobbyId, userId, channelId, data, dataLength);
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordLobbyEvents LobbyManager::events_{
|
||||
&LobbyEvents::OnLobbyUpdate,
|
||||
&LobbyEvents::OnLobbyDelete,
|
||||
&LobbyEvents::OnMemberConnect,
|
||||
&LobbyEvents::OnMemberUpdate,
|
||||
&LobbyEvents::OnMemberDisconnect,
|
||||
&LobbyEvents::OnLobbyMessage,
|
||||
&LobbyEvents::OnSpeaking,
|
||||
&LobbyEvents::OnNetworkMessage,
|
||||
};
|
||||
|
||||
Result LobbyManager::GetLobbyCreateTransaction(LobbyTransaction* transaction)
|
||||
{
|
||||
if (!transaction) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_create_transaction(internal_, transaction->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction)
|
||||
{
|
||||
if (!transaction) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_lobby_update_transaction(internal_, lobbyId, transaction->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberUpdateTransaction(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction* transaction)
|
||||
{
|
||||
if (!transaction) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_update_transaction(internal_, lobbyId, userId, transaction->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::CreateLobby(LobbyTransaction const& transaction,
|
||||
std::function<void(Result, Lobby const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||
internal_->create_lobby(
|
||||
internal_, const_cast<LobbyTransaction&>(transaction).Internal(), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::UpdateLobby(LobbyId lobbyId,
|
||||
LobbyTransaction const& transaction,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->update_lobby(internal_,
|
||||
lobbyId,
|
||||
const_cast<LobbyTransaction&>(transaction).Internal(),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->delete_lobby(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::ConnectLobby(LobbyId lobbyId,
|
||||
LobbySecret secret,
|
||||
std::function<void(Result, Lobby const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||
internal_->connect_lobby(internal_, lobbyId, const_cast<char*>(secret), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::ConnectLobbyWithActivitySecret(
|
||||
LobbySecret activitySecret,
|
||||
std::function<void(Result, Lobby const&)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, DiscordLobby* lobby) -> void {
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, Lobby const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<Lobby const*>(lobby));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, Lobby const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, Lobby const&)>(std::move(callback)));
|
||||
internal_->connect_lobby_with_activity_secret(
|
||||
internal_, const_cast<char*>(activitySecret), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->disconnect_lobby(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobby(LobbyId lobbyId, Lobby* lobby)
|
||||
{
|
||||
if (!lobby) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby(internal_, lobbyId, reinterpret_cast<DiscordLobby*>(lobby));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyActivitySecret(LobbyId lobbyId, char secret[128])
|
||||
{
|
||||
if (!secret) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_activity_secret(
|
||||
internal_, lobbyId, reinterpret_cast<DiscordLobbySecret*>(secret));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096])
|
||||
{
|
||||
if (!value) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_metadata_value(
|
||||
internal_, lobbyId, const_cast<char*>(key), reinterpret_cast<DiscordMetadataValue*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256])
|
||||
{
|
||||
if (!key) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_metadata_key(
|
||||
internal_, lobbyId, index, reinterpret_cast<DiscordMetadataKey*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->lobby_metadata_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::MemberCount(LobbyId lobbyId, std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->member_count(internal_, lobbyId, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId)
|
||||
{
|
||||
if (!userId) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_user_id(internal_, lobbyId, index, reinterpret_cast<int64_t*>(userId));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberUser(LobbyId lobbyId, UserId userId, User* user)
|
||||
{
|
||||
if (!user) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_user(internal_, lobbyId, userId, reinterpret_cast<DiscordUser*>(user));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberMetadataValue(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
MetadataKey key,
|
||||
char value[4096])
|
||||
{
|
||||
if (!value) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_member_metadata_value(internal_,
|
||||
lobbyId,
|
||||
userId,
|
||||
const_cast<char*>(key),
|
||||
reinterpret_cast<DiscordMetadataValue*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetMemberMetadataKey(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
std::int32_t index,
|
||||
char key[256])
|
||||
{
|
||||
if (!key) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_member_metadata_key(
|
||||
internal_, lobbyId, userId, index, reinterpret_cast<DiscordMetadataKey*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->member_metadata_count(
|
||||
internal_, lobbyId, userId, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::UpdateMember(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction const& transaction,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->update_member(internal_,
|
||||
lobbyId,
|
||||
userId,
|
||||
const_cast<LobbyMemberTransaction&>(transaction).Internal(),
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::SendLobbyMessage(LobbyId lobbyId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->send_lobby_message(
|
||||
internal_, lobbyId, reinterpret_cast<uint8_t*>(data), dataLength, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result LobbyManager::GetSearchQuery(LobbySearchQuery* query)
|
||||
{
|
||||
if (!query) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_search_query(internal_, query->Receive());
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::Search(LobbySearchQuery const& query, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->search(
|
||||
internal_, const_cast<LobbySearchQuery&>(query).Internal(), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::LobbyCount(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->lobby_count(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result LobbyManager::GetLobbyId(std::int32_t index, LobbyId* lobbyId)
|
||||
{
|
||||
if (!lobbyId) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_lobby_id(internal_, index, reinterpret_cast<int64_t*>(lobbyId));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void LobbyManager::ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->connect_voice(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void LobbyManager::DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->disconnect_voice(internal_, lobbyId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result LobbyManager::ConnectNetwork(LobbyId lobbyId)
|
||||
{
|
||||
auto result = internal_->connect_network(internal_, lobbyId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::DisconnectNetwork(LobbyId lobbyId)
|
||||
{
|
||||
auto result = internal_->disconnect_network(internal_, lobbyId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::FlushNetwork()
|
||||
{
|
||||
auto result = internal_->flush_network(internal_);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable)
|
||||
{
|
||||
auto result =
|
||||
internal_->open_network_channel(internal_, lobbyId, channelId, (reliable ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyManager::SendNetworkMessage(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
std::uint8_t channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->send_network_message(
|
||||
internal_, lobbyId, userId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
88
libs/discordGameSDK/cpp/lobby_manager.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class LobbyManager final {
|
||||
public:
|
||||
~LobbyManager() = default;
|
||||
|
||||
Result GetLobbyCreateTransaction(LobbyTransaction* transaction);
|
||||
Result GetLobbyUpdateTransaction(LobbyId lobbyId, LobbyTransaction* transaction);
|
||||
Result GetMemberUpdateTransaction(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction* transaction);
|
||||
void CreateLobby(LobbyTransaction const& transaction,
|
||||
std::function<void(Result, Lobby const&)> callback);
|
||||
void UpdateLobby(LobbyId lobbyId,
|
||||
LobbyTransaction const& transaction,
|
||||
std::function<void(Result)> callback);
|
||||
void DeleteLobby(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
void ConnectLobby(LobbyId lobbyId,
|
||||
LobbySecret secret,
|
||||
std::function<void(Result, Lobby const&)> callback);
|
||||
void ConnectLobbyWithActivitySecret(LobbySecret activitySecret,
|
||||
std::function<void(Result, Lobby const&)> callback);
|
||||
void DisconnectLobby(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
Result GetLobby(LobbyId lobbyId, Lobby* lobby);
|
||||
Result GetLobbyActivitySecret(LobbyId lobbyId, char secret[128]);
|
||||
Result GetLobbyMetadataValue(LobbyId lobbyId, MetadataKey key, char value[4096]);
|
||||
Result GetLobbyMetadataKey(LobbyId lobbyId, std::int32_t index, char key[256]);
|
||||
Result LobbyMetadataCount(LobbyId lobbyId, std::int32_t* count);
|
||||
Result MemberCount(LobbyId lobbyId, std::int32_t* count);
|
||||
Result GetMemberUserId(LobbyId lobbyId, std::int32_t index, UserId* userId);
|
||||
Result GetMemberUser(LobbyId lobbyId, UserId userId, User* user);
|
||||
Result GetMemberMetadataValue(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
MetadataKey key,
|
||||
char value[4096]);
|
||||
Result GetMemberMetadataKey(LobbyId lobbyId, UserId userId, std::int32_t index, char key[256]);
|
||||
Result MemberMetadataCount(LobbyId lobbyId, UserId userId, std::int32_t* count);
|
||||
void UpdateMember(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
LobbyMemberTransaction const& transaction,
|
||||
std::function<void(Result)> callback);
|
||||
void SendLobbyMessage(LobbyId lobbyId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback);
|
||||
Result GetSearchQuery(LobbySearchQuery* query);
|
||||
void Search(LobbySearchQuery const& query, std::function<void(Result)> callback);
|
||||
void LobbyCount(std::int32_t* count);
|
||||
Result GetLobbyId(std::int32_t index, LobbyId* lobbyId);
|
||||
void ConnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
void DisconnectVoice(LobbyId lobbyId, std::function<void(Result)> callback);
|
||||
Result ConnectNetwork(LobbyId lobbyId);
|
||||
Result DisconnectNetwork(LobbyId lobbyId);
|
||||
Result FlushNetwork();
|
||||
Result OpenNetworkChannel(LobbyId lobbyId, std::uint8_t channelId, bool reliable);
|
||||
Result SendNetworkMessage(LobbyId lobbyId,
|
||||
UserId userId,
|
||||
std::uint8_t channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength);
|
||||
|
||||
Event<std::int64_t> OnLobbyUpdate;
|
||||
Event<std::int64_t, std::uint32_t> OnLobbyDelete;
|
||||
Event<std::int64_t, std::int64_t> OnMemberConnect;
|
||||
Event<std::int64_t, std::int64_t> OnMemberUpdate;
|
||||
Event<std::int64_t, std::int64_t> OnMemberDisconnect;
|
||||
Event<std::int64_t, std::int64_t, std::uint8_t*, std::uint32_t> OnLobbyMessage;
|
||||
Event<std::int64_t, std::int64_t, bool> OnSpeaking;
|
||||
Event<std::int64_t, std::int64_t, std::uint8_t, std::uint8_t*, std::uint32_t> OnNetworkMessage;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
LobbyManager() = default;
|
||||
LobbyManager(LobbyManager const& rhs) = delete;
|
||||
LobbyManager& operator=(LobbyManager const& rhs) = delete;
|
||||
LobbyManager(LobbyManager&& rhs) = delete;
|
||||
LobbyManager& operator=(LobbyManager&& rhs) = delete;
|
||||
|
||||
IDiscordLobbyManager* internal_;
|
||||
static IDiscordLobbyEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
103
libs/discordGameSDK/cpp/network_manager.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "network_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class NetworkEvents final {
|
||||
public:
|
||||
static void OnMessage(void* callbackData,
|
||||
DiscordNetworkPeerId peerId,
|
||||
DiscordNetworkChannelId channelId,
|
||||
uint8_t* data,
|
||||
uint32_t dataLength)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->NetworkManager();
|
||||
module.OnMessage(peerId, channelId, data, dataLength);
|
||||
}
|
||||
|
||||
static void OnRouteUpdate(void* callbackData, char const* routeData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->NetworkManager();
|
||||
module.OnRouteUpdate(static_cast<const char*>(routeData));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordNetworkEvents NetworkManager::events_{
|
||||
&NetworkEvents::OnMessage,
|
||||
&NetworkEvents::OnRouteUpdate,
|
||||
};
|
||||
|
||||
void NetworkManager::GetPeerId(NetworkPeerId* peerId)
|
||||
{
|
||||
if (!peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->get_peer_id(internal_, reinterpret_cast<uint64_t*>(peerId));
|
||||
}
|
||||
|
||||
Result NetworkManager::Flush()
|
||||
{
|
||||
auto result = internal_->flush(internal_);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::OpenPeer(NetworkPeerId peerId, char const* routeData)
|
||||
{
|
||||
auto result = internal_->open_peer(internal_, peerId, const_cast<char*>(routeData));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::UpdatePeer(NetworkPeerId peerId, char const* routeData)
|
||||
{
|
||||
auto result = internal_->update_peer(internal_, peerId, const_cast<char*>(routeData));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::ClosePeer(NetworkPeerId peerId)
|
||||
{
|
||||
auto result = internal_->close_peer(internal_, peerId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable)
|
||||
{
|
||||
auto result = internal_->open_channel(internal_, peerId, channelId, (reliable ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId)
|
||||
{
|
||||
auto result = internal_->close_channel(internal_, peerId, channelId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result NetworkManager::SendMessage(NetworkPeerId peerId,
|
||||
NetworkChannelId channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->send_message(
|
||||
internal_, peerId, channelId, reinterpret_cast<uint8_t*>(data), dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
63
libs/discordGameSDK/cpp/network_manager.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class NetworkManager final {
|
||||
public:
|
||||
~NetworkManager() = default;
|
||||
|
||||
/**
|
||||
* Get the local peer ID for this process.
|
||||
*/
|
||||
void GetPeerId(NetworkPeerId* peerId);
|
||||
/**
|
||||
* Send pending network messages.
|
||||
*/
|
||||
Result Flush();
|
||||
/**
|
||||
* Open a connection to a remote peer.
|
||||
*/
|
||||
Result OpenPeer(NetworkPeerId peerId, char const* routeData);
|
||||
/**
|
||||
* Update the route data for a connected peer.
|
||||
*/
|
||||
Result UpdatePeer(NetworkPeerId peerId, char const* routeData);
|
||||
/**
|
||||
* Close the connection to a remote peer.
|
||||
*/
|
||||
Result ClosePeer(NetworkPeerId peerId);
|
||||
/**
|
||||
* Open a message channel to a connected peer.
|
||||
*/
|
||||
Result OpenChannel(NetworkPeerId peerId, NetworkChannelId channelId, bool reliable);
|
||||
/**
|
||||
* Close a message channel to a connected peer.
|
||||
*/
|
||||
Result CloseChannel(NetworkPeerId peerId, NetworkChannelId channelId);
|
||||
/**
|
||||
* Send a message to a connected peer over an opened message channel.
|
||||
*/
|
||||
Result SendMessage(NetworkPeerId peerId,
|
||||
NetworkChannelId channelId,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength);
|
||||
|
||||
Event<NetworkPeerId, NetworkChannelId, std::uint8_t*, std::uint32_t> OnMessage;
|
||||
Event<char const*> OnRouteUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
NetworkManager() = default;
|
||||
NetworkManager(NetworkManager const& rhs) = delete;
|
||||
NetworkManager& operator=(NetworkManager const& rhs) = delete;
|
||||
NetworkManager(NetworkManager&& rhs) = delete;
|
||||
NetworkManager& operator=(NetworkManager&& rhs) = delete;
|
||||
|
||||
IDiscordNetworkManager* internal_;
|
||||
static IDiscordNetworkEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
112
libs/discordGameSDK/cpp/overlay_manager.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "overlay_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class OverlayEvents final {
|
||||
public:
|
||||
static void OnToggle(void* callbackData, bool locked)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->OverlayManager();
|
||||
module.OnToggle((locked != 0));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordOverlayEvents OverlayManager::events_{
|
||||
&OverlayEvents::OnToggle,
|
||||
};
|
||||
|
||||
void OverlayManager::IsEnabled(bool* enabled)
|
||||
{
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->is_enabled(internal_, reinterpret_cast<bool*>(enabled));
|
||||
}
|
||||
|
||||
void OverlayManager::IsLocked(bool* locked)
|
||||
{
|
||||
if (!locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->is_locked(internal_, reinterpret_cast<bool*>(locked));
|
||||
}
|
||||
|
||||
void OverlayManager::SetLocked(bool locked, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->set_locked(internal_, (locked ? 1 : 0), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void OverlayManager::OpenActivityInvite(ActivityActionType type,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->open_activity_invite(
|
||||
internal_, static_cast<EDiscordActivityActionType>(type), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void OverlayManager::OpenGuildInvite(char const* code, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->open_guild_invite(internal_, const_cast<char*>(code), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void OverlayManager::OpenVoiceSettings(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->open_voice_settings(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
33
libs/discordGameSDK/cpp/overlay_manager.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class OverlayManager final {
|
||||
public:
|
||||
~OverlayManager() = default;
|
||||
|
||||
void IsEnabled(bool* enabled);
|
||||
void IsLocked(bool* locked);
|
||||
void SetLocked(bool locked, std::function<void(Result)> callback);
|
||||
void OpenActivityInvite(ActivityActionType type, std::function<void(Result)> callback);
|
||||
void OpenGuildInvite(char const* code, std::function<void(Result)> callback);
|
||||
void OpenVoiceSettings(std::function<void(Result)> callback);
|
||||
|
||||
Event<bool> OnToggle;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
OverlayManager() = default;
|
||||
OverlayManager(OverlayManager const& rhs) = delete;
|
||||
OverlayManager& operator=(OverlayManager const& rhs) = delete;
|
||||
OverlayManager(OverlayManager&& rhs) = delete;
|
||||
OverlayManager& operator=(OverlayManager&& rhs) = delete;
|
||||
|
||||
IDiscordOverlayManager* internal_;
|
||||
static IDiscordOverlayEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
90
libs/discordGameSDK/cpp/relationship_manager.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "relationship_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class RelationshipEvents final {
|
||||
public:
|
||||
static void OnRefresh(void* callbackData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->RelationshipManager();
|
||||
module.OnRefresh();
|
||||
}
|
||||
|
||||
static void OnRelationshipUpdate(void* callbackData, DiscordRelationship* relationship)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->RelationshipManager();
|
||||
module.OnRelationshipUpdate(*reinterpret_cast<Relationship const*>(relationship));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordRelationshipEvents RelationshipManager::events_{
|
||||
&RelationshipEvents::OnRefresh,
|
||||
&RelationshipEvents::OnRelationshipUpdate,
|
||||
};
|
||||
|
||||
void RelationshipManager::Filter(std::function<bool(Relationship const&)> filter)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, DiscordRelationship* relationship) -> bool {
|
||||
auto cb(reinterpret_cast<std::function<bool(Relationship const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return {};
|
||||
}
|
||||
return (*cb)(*reinterpret_cast<Relationship const*>(relationship));
|
||||
};
|
||||
std::unique_ptr<std::function<bool(Relationship const&)>> cb{};
|
||||
cb.reset(new std::function<bool(Relationship const&)>(std::move(filter)));
|
||||
internal_->filter(internal_, cb.get(), wrapper);
|
||||
}
|
||||
|
||||
Result RelationshipManager::Count(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->count(internal_, reinterpret_cast<int32_t*>(count));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result RelationshipManager::Get(UserId userId, Relationship* relationship)
|
||||
{
|
||||
if (!relationship) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get(internal_, userId, reinterpret_cast<DiscordRelationship*>(relationship));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result RelationshipManager::GetAt(std::uint32_t index, Relationship* relationship)
|
||||
{
|
||||
if (!relationship) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_at(internal_, index, reinterpret_cast<DiscordRelationship*>(relationship));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
32
libs/discordGameSDK/cpp/relationship_manager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class RelationshipManager final {
|
||||
public:
|
||||
~RelationshipManager() = default;
|
||||
|
||||
void Filter(std::function<bool(Relationship const&)> filter);
|
||||
Result Count(std::int32_t* count);
|
||||
Result Get(UserId userId, Relationship* relationship);
|
||||
Result GetAt(std::uint32_t index, Relationship* relationship);
|
||||
|
||||
Event<> OnRefresh;
|
||||
Event<Relationship const&> OnRelationshipUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
RelationshipManager() = default;
|
||||
RelationshipManager(RelationshipManager const& rhs) = delete;
|
||||
RelationshipManager& operator=(RelationshipManager const& rhs) = delete;
|
||||
RelationshipManager(RelationshipManager&& rhs) = delete;
|
||||
RelationshipManager& operator=(RelationshipManager&& rhs) = delete;
|
||||
|
||||
IDiscordRelationshipManager* internal_;
|
||||
static IDiscordRelationshipEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
158
libs/discordGameSDK/cpp/storage_manager.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "storage_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
Result StorageManager::Read(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::uint32_t* read)
|
||||
{
|
||||
if (!read) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->read(internal_,
|
||||
const_cast<char*>(name),
|
||||
reinterpret_cast<uint8_t*>(data),
|
||||
dataLength,
|
||||
reinterpret_cast<uint32_t*>(read));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StorageManager::ReadAsync(char const* name,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
|
||||
callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), data, dataLength);
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
|
||||
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
|
||||
internal_->read_async(internal_, const_cast<char*>(name), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void StorageManager::ReadAsyncPartial(
|
||||
char const* name,
|
||||
std::uint64_t offset,
|
||||
std::uint64_t length,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback)
|
||||
{
|
||||
static auto wrapper =
|
||||
[](void* callbackData, EDiscordResult result, uint8_t* data, uint32_t dataLength) -> void {
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, std::uint8_t*, std::uint32_t)>*>(
|
||||
callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), data, dataLength);
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, std::uint8_t*, std::uint32_t)>> cb{};
|
||||
cb.reset(new std::function<void(Result, std::uint8_t*, std::uint32_t)>(std::move(callback)));
|
||||
internal_->read_async_partial(
|
||||
internal_, const_cast<char*>(name), offset, length, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result StorageManager::Write(char const* name, std::uint8_t* data, std::uint32_t dataLength)
|
||||
{
|
||||
auto result = internal_->write(
|
||||
internal_, const_cast<char*>(name), reinterpret_cast<uint8_t*>(data), dataLength);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StorageManager::WriteAsync(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->write_async(internal_,
|
||||
const_cast<char*>(name),
|
||||
reinterpret_cast<uint8_t*>(data),
|
||||
dataLength,
|
||||
cb.release(),
|
||||
wrapper);
|
||||
}
|
||||
|
||||
Result StorageManager::Delete(char const* name)
|
||||
{
|
||||
auto result = internal_->delete_(internal_, const_cast<char*>(name));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StorageManager::Exists(char const* name, bool* exists)
|
||||
{
|
||||
if (!exists) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->exists(internal_, const_cast<char*>(name), reinterpret_cast<bool*>(exists));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StorageManager::Count(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result StorageManager::Stat(char const* name, FileStat* stat)
|
||||
{
|
||||
if (!stat) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->stat(internal_, const_cast<char*>(name), reinterpret_cast<DiscordFileStat*>(stat));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StorageManager::StatAt(std::int32_t index, FileStat* stat)
|
||||
{
|
||||
if (!stat) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->stat_at(internal_, index, reinterpret_cast<DiscordFileStat*>(stat));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StorageManager::GetPath(char path[4096])
|
||||
{
|
||||
if (!path) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_path(internal_, reinterpret_cast<DiscordPath*>(path));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
46
libs/discordGameSDK/cpp/storage_manager.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class StorageManager final {
|
||||
public:
|
||||
~StorageManager() = default;
|
||||
|
||||
Result Read(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::uint32_t* read);
|
||||
void ReadAsync(char const* name,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
|
||||
void ReadAsyncPartial(char const* name,
|
||||
std::uint64_t offset,
|
||||
std::uint64_t length,
|
||||
std::function<void(Result, std::uint8_t*, std::uint32_t)> callback);
|
||||
Result Write(char const* name, std::uint8_t* data, std::uint32_t dataLength);
|
||||
void WriteAsync(char const* name,
|
||||
std::uint8_t* data,
|
||||
std::uint32_t dataLength,
|
||||
std::function<void(Result)> callback);
|
||||
Result Delete(char const* name);
|
||||
Result Exists(char const* name, bool* exists);
|
||||
void Count(std::int32_t* count);
|
||||
Result Stat(char const* name, FileStat* stat);
|
||||
Result StatAt(std::int32_t index, FileStat* stat);
|
||||
Result GetPath(char path[4096]);
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
StorageManager() = default;
|
||||
StorageManager(StorageManager const& rhs) = delete;
|
||||
StorageManager& operator=(StorageManager const& rhs) = delete;
|
||||
StorageManager(StorageManager&& rhs) = delete;
|
||||
StorageManager& operator=(StorageManager&& rhs) = delete;
|
||||
|
||||
IDiscordStorageManager* internal_;
|
||||
static IDiscordStorageEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
160
libs/discordGameSDK/cpp/store_manager.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "store_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class StoreEvents final {
|
||||
public:
|
||||
static void OnEntitlementCreate(void* callbackData, DiscordEntitlement* entitlement)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->StoreManager();
|
||||
module.OnEntitlementCreate(*reinterpret_cast<Entitlement const*>(entitlement));
|
||||
}
|
||||
|
||||
static void OnEntitlementDelete(void* callbackData, DiscordEntitlement* entitlement)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->StoreManager();
|
||||
module.OnEntitlementDelete(*reinterpret_cast<Entitlement const*>(entitlement));
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordStoreEvents StoreManager::events_{
|
||||
&StoreEvents::OnEntitlementCreate,
|
||||
&StoreEvents::OnEntitlementDelete,
|
||||
};
|
||||
|
||||
void StoreManager::FetchSkus(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->fetch_skus(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void StoreManager::CountSkus(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count_skus(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result StoreManager::GetSku(Snowflake skuId, Sku* sku)
|
||||
{
|
||||
if (!sku) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_sku(internal_, skuId, reinterpret_cast<DiscordSku*>(sku));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StoreManager::GetSkuAt(std::int32_t index, Sku* sku)
|
||||
{
|
||||
if (!sku) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_sku_at(internal_, index, reinterpret_cast<DiscordSku*>(sku));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StoreManager::FetchEntitlements(std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->fetch_entitlements(internal_, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
void StoreManager::CountEntitlements(std::int32_t* count)
|
||||
{
|
||||
if (!count) {
|
||||
return;
|
||||
}
|
||||
|
||||
internal_->count_entitlements(internal_, reinterpret_cast<int32_t*>(count));
|
||||
}
|
||||
|
||||
Result StoreManager::GetEntitlement(Snowflake entitlementId, Entitlement* entitlement)
|
||||
{
|
||||
if (!entitlement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_entitlement(
|
||||
internal_, entitlementId, reinterpret_cast<DiscordEntitlement*>(entitlement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StoreManager::GetEntitlementAt(std::int32_t index, Entitlement* entitlement)
|
||||
{
|
||||
if (!entitlement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_entitlement_at(
|
||||
internal_, index, reinterpret_cast<DiscordEntitlement*>(entitlement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result StoreManager::HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement)
|
||||
{
|
||||
if (!hasEntitlement) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->has_sku_entitlement(internal_, skuId, reinterpret_cast<bool*>(hasEntitlement));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void StoreManager::StartPurchase(Snowflake skuId, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->start_purchase(internal_, skuId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
38
libs/discordGameSDK/cpp/store_manager.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class StoreManager final {
|
||||
public:
|
||||
~StoreManager() = default;
|
||||
|
||||
void FetchSkus(std::function<void(Result)> callback);
|
||||
void CountSkus(std::int32_t* count);
|
||||
Result GetSku(Snowflake skuId, Sku* sku);
|
||||
Result GetSkuAt(std::int32_t index, Sku* sku);
|
||||
void FetchEntitlements(std::function<void(Result)> callback);
|
||||
void CountEntitlements(std::int32_t* count);
|
||||
Result GetEntitlement(Snowflake entitlementId, Entitlement* entitlement);
|
||||
Result GetEntitlementAt(std::int32_t index, Entitlement* entitlement);
|
||||
Result HasSkuEntitlement(Snowflake skuId, bool* hasEntitlement);
|
||||
void StartPurchase(Snowflake skuId, std::function<void(Result)> callback);
|
||||
|
||||
Event<Entitlement const&> OnEntitlementCreate;
|
||||
Event<Entitlement const&> OnEntitlementDelete;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
StoreManager() = default;
|
||||
StoreManager(StoreManager const& rhs) = delete;
|
||||
StoreManager& operator=(StoreManager const& rhs) = delete;
|
||||
StoreManager(StoreManager&& rhs) = delete;
|
||||
StoreManager& operator=(StoreManager&& rhs) = delete;
|
||||
|
||||
IDiscordStoreManager* internal_;
|
||||
static IDiscordStoreEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
769
libs/discordGameSDK/cpp/types.cpp
Normal file
@@ -0,0 +1,769 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
void User::SetId(UserId id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
UserId User::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void User::SetUsername(char const* username)
|
||||
{
|
||||
strncpy(internal_.username, username, 256);
|
||||
internal_.username[256 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* User::GetUsername() const
|
||||
{
|
||||
return internal_.username;
|
||||
}
|
||||
|
||||
void User::SetDiscriminator(char const* discriminator)
|
||||
{
|
||||
strncpy(internal_.discriminator, discriminator, 8);
|
||||
internal_.discriminator[8 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* User::GetDiscriminator() const
|
||||
{
|
||||
return internal_.discriminator;
|
||||
}
|
||||
|
||||
void User::SetAvatar(char const* avatar)
|
||||
{
|
||||
strncpy(internal_.avatar, avatar, 128);
|
||||
internal_.avatar[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* User::GetAvatar() const
|
||||
{
|
||||
return internal_.avatar;
|
||||
}
|
||||
|
||||
void User::SetBot(bool bot)
|
||||
{
|
||||
internal_.bot = bot;
|
||||
}
|
||||
|
||||
bool User::GetBot() const
|
||||
{
|
||||
return internal_.bot != 0;
|
||||
}
|
||||
|
||||
void OAuth2Token::SetAccessToken(char const* accessToken)
|
||||
{
|
||||
strncpy(internal_.access_token, accessToken, 128);
|
||||
internal_.access_token[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* OAuth2Token::GetAccessToken() const
|
||||
{
|
||||
return internal_.access_token;
|
||||
}
|
||||
|
||||
void OAuth2Token::SetScopes(char const* scopes)
|
||||
{
|
||||
strncpy(internal_.scopes, scopes, 1024);
|
||||
internal_.scopes[1024 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* OAuth2Token::GetScopes() const
|
||||
{
|
||||
return internal_.scopes;
|
||||
}
|
||||
|
||||
void OAuth2Token::SetExpires(Timestamp expires)
|
||||
{
|
||||
internal_.expires = expires;
|
||||
}
|
||||
|
||||
Timestamp OAuth2Token::GetExpires() const
|
||||
{
|
||||
return internal_.expires;
|
||||
}
|
||||
|
||||
void ImageHandle::SetType(ImageType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordImageType>(type);
|
||||
}
|
||||
|
||||
ImageType ImageHandle::GetType() const
|
||||
{
|
||||
return static_cast<ImageType>(internal_.type);
|
||||
}
|
||||
|
||||
void ImageHandle::SetId(std::int64_t id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
std::int64_t ImageHandle::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void ImageHandle::SetSize(std::uint32_t size)
|
||||
{
|
||||
internal_.size = size;
|
||||
}
|
||||
|
||||
std::uint32_t ImageHandle::GetSize() const
|
||||
{
|
||||
return internal_.size;
|
||||
}
|
||||
|
||||
void ImageDimensions::SetWidth(std::uint32_t width)
|
||||
{
|
||||
internal_.width = width;
|
||||
}
|
||||
|
||||
std::uint32_t ImageDimensions::GetWidth() const
|
||||
{
|
||||
return internal_.width;
|
||||
}
|
||||
|
||||
void ImageDimensions::SetHeight(std::uint32_t height)
|
||||
{
|
||||
internal_.height = height;
|
||||
}
|
||||
|
||||
std::uint32_t ImageDimensions::GetHeight() const
|
||||
{
|
||||
return internal_.height;
|
||||
}
|
||||
|
||||
void ActivityTimestamps::SetStart(Timestamp start)
|
||||
{
|
||||
internal_.start = start;
|
||||
}
|
||||
|
||||
Timestamp ActivityTimestamps::GetStart() const
|
||||
{
|
||||
return internal_.start;
|
||||
}
|
||||
|
||||
void ActivityTimestamps::SetEnd(Timestamp end)
|
||||
{
|
||||
internal_.end = end;
|
||||
}
|
||||
|
||||
Timestamp ActivityTimestamps::GetEnd() const
|
||||
{
|
||||
return internal_.end;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetLargeImage(char const* largeImage)
|
||||
{
|
||||
strncpy(internal_.large_image, largeImage, 128);
|
||||
internal_.large_image[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetLargeImage() const
|
||||
{
|
||||
return internal_.large_image;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetLargeText(char const* largeText)
|
||||
{
|
||||
strncpy(internal_.large_text, largeText, 128);
|
||||
internal_.large_text[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetLargeText() const
|
||||
{
|
||||
return internal_.large_text;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetSmallImage(char const* smallImage)
|
||||
{
|
||||
strncpy(internal_.small_image, smallImage, 128);
|
||||
internal_.small_image[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetSmallImage() const
|
||||
{
|
||||
return internal_.small_image;
|
||||
}
|
||||
|
||||
void ActivityAssets::SetSmallText(char const* smallText)
|
||||
{
|
||||
strncpy(internal_.small_text, smallText, 128);
|
||||
internal_.small_text[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityAssets::GetSmallText() const
|
||||
{
|
||||
return internal_.small_text;
|
||||
}
|
||||
|
||||
void PartySize::SetCurrentSize(std::int32_t currentSize)
|
||||
{
|
||||
internal_.current_size = currentSize;
|
||||
}
|
||||
|
||||
std::int32_t PartySize::GetCurrentSize() const
|
||||
{
|
||||
return internal_.current_size;
|
||||
}
|
||||
|
||||
void PartySize::SetMaxSize(std::int32_t maxSize)
|
||||
{
|
||||
internal_.max_size = maxSize;
|
||||
}
|
||||
|
||||
std::int32_t PartySize::GetMaxSize() const
|
||||
{
|
||||
return internal_.max_size;
|
||||
}
|
||||
|
||||
void ActivityParty::SetId(char const* id)
|
||||
{
|
||||
strncpy(internal_.id, id, 128);
|
||||
internal_.id[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivityParty::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
PartySize& ActivityParty::GetSize()
|
||||
{
|
||||
return reinterpret_cast<PartySize&>(internal_.size);
|
||||
}
|
||||
|
||||
PartySize const& ActivityParty::GetSize() const
|
||||
{
|
||||
return reinterpret_cast<PartySize const&>(internal_.size);
|
||||
}
|
||||
|
||||
void ActivitySecrets::SetMatch(char const* match)
|
||||
{
|
||||
strncpy(internal_.match, match, 128);
|
||||
internal_.match[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivitySecrets::GetMatch() const
|
||||
{
|
||||
return internal_.match;
|
||||
}
|
||||
|
||||
void ActivitySecrets::SetJoin(char const* join)
|
||||
{
|
||||
strncpy(internal_.join, join, 128);
|
||||
internal_.join[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivitySecrets::GetJoin() const
|
||||
{
|
||||
return internal_.join;
|
||||
}
|
||||
|
||||
void ActivitySecrets::SetSpectate(char const* spectate)
|
||||
{
|
||||
strncpy(internal_.spectate, spectate, 128);
|
||||
internal_.spectate[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* ActivitySecrets::GetSpectate() const
|
||||
{
|
||||
return internal_.spectate;
|
||||
}
|
||||
|
||||
void Activity::SetType(ActivityType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordActivityType>(type);
|
||||
}
|
||||
|
||||
ActivityType Activity::GetType() const
|
||||
{
|
||||
return static_cast<ActivityType>(internal_.type);
|
||||
}
|
||||
|
||||
void Activity::SetApplicationId(std::int64_t applicationId)
|
||||
{
|
||||
internal_.application_id = applicationId;
|
||||
}
|
||||
|
||||
std::int64_t Activity::GetApplicationId() const
|
||||
{
|
||||
return internal_.application_id;
|
||||
}
|
||||
|
||||
void Activity::SetName(char const* name)
|
||||
{
|
||||
strncpy(internal_.name, name, 128);
|
||||
internal_.name[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Activity::GetName() const
|
||||
{
|
||||
return internal_.name;
|
||||
}
|
||||
|
||||
void Activity::SetState(char const* state)
|
||||
{
|
||||
strncpy(internal_.state, state, 128);
|
||||
internal_.state[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Activity::GetState() const
|
||||
{
|
||||
return internal_.state;
|
||||
}
|
||||
|
||||
void Activity::SetDetails(char const* details)
|
||||
{
|
||||
strncpy(internal_.details, details, 128);
|
||||
internal_.details[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Activity::GetDetails() const
|
||||
{
|
||||
return internal_.details;
|
||||
}
|
||||
|
||||
ActivityTimestamps& Activity::GetTimestamps()
|
||||
{
|
||||
return reinterpret_cast<ActivityTimestamps&>(internal_.timestamps);
|
||||
}
|
||||
|
||||
ActivityTimestamps const& Activity::GetTimestamps() const
|
||||
{
|
||||
return reinterpret_cast<ActivityTimestamps const&>(internal_.timestamps);
|
||||
}
|
||||
|
||||
ActivityAssets& Activity::GetAssets()
|
||||
{
|
||||
return reinterpret_cast<ActivityAssets&>(internal_.assets);
|
||||
}
|
||||
|
||||
ActivityAssets const& Activity::GetAssets() const
|
||||
{
|
||||
return reinterpret_cast<ActivityAssets const&>(internal_.assets);
|
||||
}
|
||||
|
||||
ActivityParty& Activity::GetParty()
|
||||
{
|
||||
return reinterpret_cast<ActivityParty&>(internal_.party);
|
||||
}
|
||||
|
||||
ActivityParty const& Activity::GetParty() const
|
||||
{
|
||||
return reinterpret_cast<ActivityParty const&>(internal_.party);
|
||||
}
|
||||
|
||||
ActivitySecrets& Activity::GetSecrets()
|
||||
{
|
||||
return reinterpret_cast<ActivitySecrets&>(internal_.secrets);
|
||||
}
|
||||
|
||||
ActivitySecrets const& Activity::GetSecrets() const
|
||||
{
|
||||
return reinterpret_cast<ActivitySecrets const&>(internal_.secrets);
|
||||
}
|
||||
|
||||
void Activity::SetInstance(bool instance)
|
||||
{
|
||||
internal_.instance = instance;
|
||||
}
|
||||
|
||||
bool Activity::GetInstance() const
|
||||
{
|
||||
return internal_.instance != 0;
|
||||
}
|
||||
|
||||
void Presence::SetStatus(Status status)
|
||||
{
|
||||
internal_.status = static_cast<EDiscordStatus>(status);
|
||||
}
|
||||
|
||||
Status Presence::GetStatus() const
|
||||
{
|
||||
return static_cast<Status>(internal_.status);
|
||||
}
|
||||
|
||||
Activity& Presence::GetActivity()
|
||||
{
|
||||
return reinterpret_cast<Activity&>(internal_.activity);
|
||||
}
|
||||
|
||||
Activity const& Presence::GetActivity() const
|
||||
{
|
||||
return reinterpret_cast<Activity const&>(internal_.activity);
|
||||
}
|
||||
|
||||
void Relationship::SetType(RelationshipType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordRelationshipType>(type);
|
||||
}
|
||||
|
||||
RelationshipType Relationship::GetType() const
|
||||
{
|
||||
return static_cast<RelationshipType>(internal_.type);
|
||||
}
|
||||
|
||||
User& Relationship::GetUser()
|
||||
{
|
||||
return reinterpret_cast<User&>(internal_.user);
|
||||
}
|
||||
|
||||
User const& Relationship::GetUser() const
|
||||
{
|
||||
return reinterpret_cast<User const&>(internal_.user);
|
||||
}
|
||||
|
||||
Presence& Relationship::GetPresence()
|
||||
{
|
||||
return reinterpret_cast<Presence&>(internal_.presence);
|
||||
}
|
||||
|
||||
Presence const& Relationship::GetPresence() const
|
||||
{
|
||||
return reinterpret_cast<Presence const&>(internal_.presence);
|
||||
}
|
||||
|
||||
void Lobby::SetId(LobbyId id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
LobbyId Lobby::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void Lobby::SetType(LobbyType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordLobbyType>(type);
|
||||
}
|
||||
|
||||
LobbyType Lobby::GetType() const
|
||||
{
|
||||
return static_cast<LobbyType>(internal_.type);
|
||||
}
|
||||
|
||||
void Lobby::SetOwnerId(UserId ownerId)
|
||||
{
|
||||
internal_.owner_id = ownerId;
|
||||
}
|
||||
|
||||
UserId Lobby::GetOwnerId() const
|
||||
{
|
||||
return internal_.owner_id;
|
||||
}
|
||||
|
||||
void Lobby::SetSecret(LobbySecret secret)
|
||||
{
|
||||
strncpy(internal_.secret, secret, 128);
|
||||
internal_.secret[128 - 1] = '\0';
|
||||
}
|
||||
|
||||
LobbySecret Lobby::GetSecret() const
|
||||
{
|
||||
return internal_.secret;
|
||||
}
|
||||
|
||||
void Lobby::SetCapacity(std::uint32_t capacity)
|
||||
{
|
||||
internal_.capacity = capacity;
|
||||
}
|
||||
|
||||
std::uint32_t Lobby::GetCapacity() const
|
||||
{
|
||||
return internal_.capacity;
|
||||
}
|
||||
|
||||
void Lobby::SetLocked(bool locked)
|
||||
{
|
||||
internal_.locked = locked;
|
||||
}
|
||||
|
||||
bool Lobby::GetLocked() const
|
||||
{
|
||||
return internal_.locked != 0;
|
||||
}
|
||||
|
||||
void FileStat::SetFilename(char const* filename)
|
||||
{
|
||||
strncpy(internal_.filename, filename, 260);
|
||||
internal_.filename[260 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* FileStat::GetFilename() const
|
||||
{
|
||||
return internal_.filename;
|
||||
}
|
||||
|
||||
void FileStat::SetSize(std::uint64_t size)
|
||||
{
|
||||
internal_.size = size;
|
||||
}
|
||||
|
||||
std::uint64_t FileStat::GetSize() const
|
||||
{
|
||||
return internal_.size;
|
||||
}
|
||||
|
||||
void FileStat::SetLastModified(std::uint64_t lastModified)
|
||||
{
|
||||
internal_.last_modified = lastModified;
|
||||
}
|
||||
|
||||
std::uint64_t FileStat::GetLastModified() const
|
||||
{
|
||||
return internal_.last_modified;
|
||||
}
|
||||
|
||||
void Entitlement::SetId(Snowflake id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
Snowflake Entitlement::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void Entitlement::SetType(EntitlementType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordEntitlementType>(type);
|
||||
}
|
||||
|
||||
EntitlementType Entitlement::GetType() const
|
||||
{
|
||||
return static_cast<EntitlementType>(internal_.type);
|
||||
}
|
||||
|
||||
void Entitlement::SetSkuId(Snowflake skuId)
|
||||
{
|
||||
internal_.sku_id = skuId;
|
||||
}
|
||||
|
||||
Snowflake Entitlement::GetSkuId() const
|
||||
{
|
||||
return internal_.sku_id;
|
||||
}
|
||||
|
||||
void SkuPrice::SetAmount(std::uint32_t amount)
|
||||
{
|
||||
internal_.amount = amount;
|
||||
}
|
||||
|
||||
std::uint32_t SkuPrice::GetAmount() const
|
||||
{
|
||||
return internal_.amount;
|
||||
}
|
||||
|
||||
void SkuPrice::SetCurrency(char const* currency)
|
||||
{
|
||||
strncpy(internal_.currency, currency, 16);
|
||||
internal_.currency[16 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* SkuPrice::GetCurrency() const
|
||||
{
|
||||
return internal_.currency;
|
||||
}
|
||||
|
||||
void Sku::SetId(Snowflake id)
|
||||
{
|
||||
internal_.id = id;
|
||||
}
|
||||
|
||||
Snowflake Sku::GetId() const
|
||||
{
|
||||
return internal_.id;
|
||||
}
|
||||
|
||||
void Sku::SetType(SkuType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordSkuType>(type);
|
||||
}
|
||||
|
||||
SkuType Sku::GetType() const
|
||||
{
|
||||
return static_cast<SkuType>(internal_.type);
|
||||
}
|
||||
|
||||
void Sku::SetName(char const* name)
|
||||
{
|
||||
strncpy(internal_.name, name, 256);
|
||||
internal_.name[256 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* Sku::GetName() const
|
||||
{
|
||||
return internal_.name;
|
||||
}
|
||||
|
||||
SkuPrice& Sku::GetPrice()
|
||||
{
|
||||
return reinterpret_cast<SkuPrice&>(internal_.price);
|
||||
}
|
||||
|
||||
SkuPrice const& Sku::GetPrice() const
|
||||
{
|
||||
return reinterpret_cast<SkuPrice const&>(internal_.price);
|
||||
}
|
||||
|
||||
void InputMode::SetType(InputModeType type)
|
||||
{
|
||||
internal_.type = static_cast<EDiscordInputModeType>(type);
|
||||
}
|
||||
|
||||
InputModeType InputMode::GetType() const
|
||||
{
|
||||
return static_cast<InputModeType>(internal_.type);
|
||||
}
|
||||
|
||||
void InputMode::SetShortcut(char const* shortcut)
|
||||
{
|
||||
strncpy(internal_.shortcut, shortcut, 256);
|
||||
internal_.shortcut[256 - 1] = '\0';
|
||||
}
|
||||
|
||||
char const* InputMode::GetShortcut() const
|
||||
{
|
||||
return internal_.shortcut;
|
||||
}
|
||||
|
||||
void UserAchievement::SetUserId(Snowflake userId)
|
||||
{
|
||||
internal_.user_id = userId;
|
||||
}
|
||||
|
||||
Snowflake UserAchievement::GetUserId() const
|
||||
{
|
||||
return internal_.user_id;
|
||||
}
|
||||
|
||||
void UserAchievement::SetAchievementId(Snowflake achievementId)
|
||||
{
|
||||
internal_.achievement_id = achievementId;
|
||||
}
|
||||
|
||||
Snowflake UserAchievement::GetAchievementId() const
|
||||
{
|
||||
return internal_.achievement_id;
|
||||
}
|
||||
|
||||
void UserAchievement::SetPercentComplete(std::uint8_t percentComplete)
|
||||
{
|
||||
internal_.percent_complete = percentComplete;
|
||||
}
|
||||
|
||||
std::uint8_t UserAchievement::GetPercentComplete() const
|
||||
{
|
||||
return internal_.percent_complete;
|
||||
}
|
||||
|
||||
void UserAchievement::SetUnlockedAt(DateTime unlockedAt)
|
||||
{
|
||||
strncpy(internal_.unlocked_at, unlockedAt, 64);
|
||||
internal_.unlocked_at[64 - 1] = '\0';
|
||||
}
|
||||
|
||||
DateTime UserAchievement::GetUnlockedAt() const
|
||||
{
|
||||
return internal_.unlocked_at;
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetType(LobbyType type)
|
||||
{
|
||||
auto result = internal_->set_type(internal_, static_cast<EDiscordLobbyType>(type));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetOwner(UserId ownerId)
|
||||
{
|
||||
auto result = internal_->set_owner(internal_, ownerId);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetCapacity(std::uint32_t capacity)
|
||||
{
|
||||
auto result = internal_->set_capacity(internal_, capacity);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetMetadata(MetadataKey key, MetadataValue value)
|
||||
{
|
||||
auto result =
|
||||
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::DeleteMetadata(MetadataKey key)
|
||||
{
|
||||
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyTransaction::SetLocked(bool locked)
|
||||
{
|
||||
auto result = internal_->set_locked(internal_, (locked ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyMemberTransaction::SetMetadata(MetadataKey key, MetadataValue value)
|
||||
{
|
||||
auto result =
|
||||
internal_->set_metadata(internal_, const_cast<char*>(key), const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbyMemberTransaction::DeleteMetadata(MetadataKey key)
|
||||
{
|
||||
auto result = internal_->delete_metadata(internal_, const_cast<char*>(key));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Filter(MetadataKey key,
|
||||
LobbySearchComparison comparison,
|
||||
LobbySearchCast cast,
|
||||
MetadataValue value)
|
||||
{
|
||||
auto result = internal_->filter(internal_,
|
||||
const_cast<char*>(key),
|
||||
static_cast<EDiscordLobbySearchComparison>(comparison),
|
||||
static_cast<EDiscordLobbySearchCast>(cast),
|
||||
const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value)
|
||||
{
|
||||
auto result = internal_->sort(internal_,
|
||||
const_cast<char*>(key),
|
||||
static_cast<EDiscordLobbySearchCast>(cast),
|
||||
const_cast<char*>(value));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Limit(std::uint32_t limit)
|
||||
{
|
||||
auto result = internal_->limit(internal_, limit);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result LobbySearchQuery::Distance(LobbySearchDistance distance)
|
||||
{
|
||||
auto result =
|
||||
internal_->distance(internal_, static_cast<EDiscordLobbySearchDistance>(distance));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
491
libs/discordGameSDK/cpp/types.h
Normal file
@@ -0,0 +1,491 @@
|
||||
#pragma once
|
||||
|
||||
#include "ffi.h"
|
||||
#include "event.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
enum class Result {
|
||||
Ok = 0,
|
||||
ServiceUnavailable = 1,
|
||||
InvalidVersion = 2,
|
||||
LockFailed = 3,
|
||||
InternalError = 4,
|
||||
InvalidPayload = 5,
|
||||
InvalidCommand = 6,
|
||||
InvalidPermissions = 7,
|
||||
NotFetched = 8,
|
||||
NotFound = 9,
|
||||
Conflict = 10,
|
||||
InvalidSecret = 11,
|
||||
InvalidJoinSecret = 12,
|
||||
NoEligibleActivity = 13,
|
||||
InvalidInvite = 14,
|
||||
NotAuthenticated = 15,
|
||||
InvalidAccessToken = 16,
|
||||
ApplicationMismatch = 17,
|
||||
InvalidDataUrl = 18,
|
||||
InvalidBase64 = 19,
|
||||
NotFiltered = 20,
|
||||
LobbyFull = 21,
|
||||
InvalidLobbySecret = 22,
|
||||
InvalidFilename = 23,
|
||||
InvalidFileSize = 24,
|
||||
InvalidEntitlement = 25,
|
||||
NotInstalled = 26,
|
||||
NotRunning = 27,
|
||||
InsufficientBuffer = 28,
|
||||
PurchaseCanceled = 29,
|
||||
InvalidGuild = 30,
|
||||
InvalidEvent = 31,
|
||||
InvalidChannel = 32,
|
||||
InvalidOrigin = 33,
|
||||
RateLimited = 34,
|
||||
OAuth2Error = 35,
|
||||
SelectChannelTimeout = 36,
|
||||
GetGuildTimeout = 37,
|
||||
SelectVoiceForceRequired = 38,
|
||||
CaptureShortcutAlreadyListening = 39,
|
||||
UnauthorizedForAchievement = 40,
|
||||
InvalidGiftCode = 41,
|
||||
PurchaseError = 42,
|
||||
TransactionAborted = 43,
|
||||
};
|
||||
|
||||
enum class CreateFlags {
|
||||
Default = 0,
|
||||
NoRequireDiscord = 1,
|
||||
};
|
||||
|
||||
enum class LogLevel {
|
||||
Error = 1,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
};
|
||||
|
||||
enum class UserFlag {
|
||||
Partner = 2,
|
||||
HypeSquadEvents = 4,
|
||||
HypeSquadHouse1 = 64,
|
||||
HypeSquadHouse2 = 128,
|
||||
HypeSquadHouse3 = 256,
|
||||
};
|
||||
|
||||
enum class PremiumType {
|
||||
None = 0,
|
||||
Tier1 = 1,
|
||||
Tier2 = 2,
|
||||
};
|
||||
|
||||
enum class ImageType {
|
||||
User,
|
||||
};
|
||||
|
||||
enum class ActivityType {
|
||||
Playing,
|
||||
Streaming,
|
||||
Listening,
|
||||
Watching,
|
||||
};
|
||||
|
||||
enum class ActivityActionType {
|
||||
Join = 1,
|
||||
Spectate,
|
||||
};
|
||||
|
||||
enum class ActivityJoinRequestReply {
|
||||
No,
|
||||
Yes,
|
||||
Ignore,
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
Offline = 0,
|
||||
Online = 1,
|
||||
Idle = 2,
|
||||
DoNotDisturb = 3,
|
||||
};
|
||||
|
||||
enum class RelationshipType {
|
||||
None,
|
||||
Friend,
|
||||
Blocked,
|
||||
PendingIncoming,
|
||||
PendingOutgoing,
|
||||
Implicit,
|
||||
};
|
||||
|
||||
enum class LobbyType {
|
||||
Private = 1,
|
||||
Public,
|
||||
};
|
||||
|
||||
enum class LobbySearchComparison {
|
||||
LessThanOrEqual = -2,
|
||||
LessThan,
|
||||
Equal,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
NotEqual,
|
||||
};
|
||||
|
||||
enum class LobbySearchCast {
|
||||
String = 1,
|
||||
Number,
|
||||
};
|
||||
|
||||
enum class LobbySearchDistance {
|
||||
Local,
|
||||
Default,
|
||||
Extended,
|
||||
Global,
|
||||
};
|
||||
|
||||
enum class EntitlementType {
|
||||
Purchase = 1,
|
||||
PremiumSubscription,
|
||||
DeveloperGift,
|
||||
TestModePurchase,
|
||||
FreePurchase,
|
||||
UserGift,
|
||||
PremiumPurchase,
|
||||
};
|
||||
|
||||
enum class SkuType {
|
||||
Application = 1,
|
||||
DLC,
|
||||
Consumable,
|
||||
Bundle,
|
||||
};
|
||||
|
||||
enum class InputModeType {
|
||||
VoiceActivity = 0,
|
||||
PushToTalk,
|
||||
};
|
||||
|
||||
using ClientId = std::int64_t;
|
||||
using Version = std::int32_t;
|
||||
using Snowflake = std::int64_t;
|
||||
using Timestamp = std::int64_t;
|
||||
using UserId = Snowflake;
|
||||
using Locale = char const*;
|
||||
using Branch = char const*;
|
||||
using LobbyId = Snowflake;
|
||||
using LobbySecret = char const*;
|
||||
using MetadataKey = char const*;
|
||||
using MetadataValue = char const*;
|
||||
using NetworkPeerId = std::uint64_t;
|
||||
using NetworkChannelId = std::uint8_t;
|
||||
using Path = char const*;
|
||||
using DateTime = char const*;
|
||||
|
||||
class User final {
|
||||
public:
|
||||
void SetId(UserId id);
|
||||
UserId GetId() const;
|
||||
void SetUsername(char const* username);
|
||||
char const* GetUsername() const;
|
||||
void SetDiscriminator(char const* discriminator);
|
||||
char const* GetDiscriminator() const;
|
||||
void SetAvatar(char const* avatar);
|
||||
char const* GetAvatar() const;
|
||||
void SetBot(bool bot);
|
||||
bool GetBot() const;
|
||||
|
||||
private:
|
||||
DiscordUser internal_;
|
||||
};
|
||||
|
||||
class OAuth2Token final {
|
||||
public:
|
||||
void SetAccessToken(char const* accessToken);
|
||||
char const* GetAccessToken() const;
|
||||
void SetScopes(char const* scopes);
|
||||
char const* GetScopes() const;
|
||||
void SetExpires(Timestamp expires);
|
||||
Timestamp GetExpires() const;
|
||||
|
||||
private:
|
||||
DiscordOAuth2Token internal_;
|
||||
};
|
||||
|
||||
class ImageHandle final {
|
||||
public:
|
||||
void SetType(ImageType type);
|
||||
ImageType GetType() const;
|
||||
void SetId(std::int64_t id);
|
||||
std::int64_t GetId() const;
|
||||
void SetSize(std::uint32_t size);
|
||||
std::uint32_t GetSize() const;
|
||||
|
||||
private:
|
||||
DiscordImageHandle internal_;
|
||||
};
|
||||
|
||||
class ImageDimensions final {
|
||||
public:
|
||||
void SetWidth(std::uint32_t width);
|
||||
std::uint32_t GetWidth() const;
|
||||
void SetHeight(std::uint32_t height);
|
||||
std::uint32_t GetHeight() const;
|
||||
|
||||
private:
|
||||
DiscordImageDimensions internal_;
|
||||
};
|
||||
|
||||
class ActivityTimestamps final {
|
||||
public:
|
||||
void SetStart(Timestamp start);
|
||||
Timestamp GetStart() const;
|
||||
void SetEnd(Timestamp end);
|
||||
Timestamp GetEnd() const;
|
||||
|
||||
private:
|
||||
DiscordActivityTimestamps internal_;
|
||||
};
|
||||
|
||||
class ActivityAssets final {
|
||||
public:
|
||||
void SetLargeImage(char const* largeImage);
|
||||
char const* GetLargeImage() const;
|
||||
void SetLargeText(char const* largeText);
|
||||
char const* GetLargeText() const;
|
||||
void SetSmallImage(char const* smallImage);
|
||||
char const* GetSmallImage() const;
|
||||
void SetSmallText(char const* smallText);
|
||||
char const* GetSmallText() const;
|
||||
|
||||
private:
|
||||
DiscordActivityAssets internal_;
|
||||
};
|
||||
|
||||
class PartySize final {
|
||||
public:
|
||||
void SetCurrentSize(std::int32_t currentSize);
|
||||
std::int32_t GetCurrentSize() const;
|
||||
void SetMaxSize(std::int32_t maxSize);
|
||||
std::int32_t GetMaxSize() const;
|
||||
|
||||
private:
|
||||
DiscordPartySize internal_;
|
||||
};
|
||||
|
||||
class ActivityParty final {
|
||||
public:
|
||||
void SetId(char const* id);
|
||||
char const* GetId() const;
|
||||
PartySize& GetSize();
|
||||
PartySize const& GetSize() const;
|
||||
|
||||
private:
|
||||
DiscordActivityParty internal_;
|
||||
};
|
||||
|
||||
class ActivitySecrets final {
|
||||
public:
|
||||
void SetMatch(char const* match);
|
||||
char const* GetMatch() const;
|
||||
void SetJoin(char const* join);
|
||||
char const* GetJoin() const;
|
||||
void SetSpectate(char const* spectate);
|
||||
char const* GetSpectate() const;
|
||||
|
||||
private:
|
||||
DiscordActivitySecrets internal_;
|
||||
};
|
||||
|
||||
class Activity final {
|
||||
public:
|
||||
void SetType(ActivityType type);
|
||||
ActivityType GetType() const;
|
||||
void SetApplicationId(std::int64_t applicationId);
|
||||
std::int64_t GetApplicationId() const;
|
||||
void SetName(char const* name);
|
||||
char const* GetName() const;
|
||||
void SetState(char const* state);
|
||||
char const* GetState() const;
|
||||
void SetDetails(char const* details);
|
||||
char const* GetDetails() const;
|
||||
ActivityTimestamps& GetTimestamps();
|
||||
ActivityTimestamps const& GetTimestamps() const;
|
||||
ActivityAssets& GetAssets();
|
||||
ActivityAssets const& GetAssets() const;
|
||||
ActivityParty& GetParty();
|
||||
ActivityParty const& GetParty() const;
|
||||
ActivitySecrets& GetSecrets();
|
||||
ActivitySecrets const& GetSecrets() const;
|
||||
void SetInstance(bool instance);
|
||||
bool GetInstance() const;
|
||||
|
||||
private:
|
||||
DiscordActivity internal_;
|
||||
};
|
||||
|
||||
class Presence final {
|
||||
public:
|
||||
void SetStatus(Status status);
|
||||
Status GetStatus() const;
|
||||
Activity& GetActivity();
|
||||
Activity const& GetActivity() const;
|
||||
|
||||
private:
|
||||
DiscordPresence internal_;
|
||||
};
|
||||
|
||||
class Relationship final {
|
||||
public:
|
||||
void SetType(RelationshipType type);
|
||||
RelationshipType GetType() const;
|
||||
User& GetUser();
|
||||
User const& GetUser() const;
|
||||
Presence& GetPresence();
|
||||
Presence const& GetPresence() const;
|
||||
|
||||
private:
|
||||
DiscordRelationship internal_;
|
||||
};
|
||||
|
||||
class Lobby final {
|
||||
public:
|
||||
void SetId(LobbyId id);
|
||||
LobbyId GetId() const;
|
||||
void SetType(LobbyType type);
|
||||
LobbyType GetType() const;
|
||||
void SetOwnerId(UserId ownerId);
|
||||
UserId GetOwnerId() const;
|
||||
void SetSecret(LobbySecret secret);
|
||||
LobbySecret GetSecret() const;
|
||||
void SetCapacity(std::uint32_t capacity);
|
||||
std::uint32_t GetCapacity() const;
|
||||
void SetLocked(bool locked);
|
||||
bool GetLocked() const;
|
||||
|
||||
private:
|
||||
DiscordLobby internal_;
|
||||
};
|
||||
|
||||
class FileStat final {
|
||||
public:
|
||||
void SetFilename(char const* filename);
|
||||
char const* GetFilename() const;
|
||||
void SetSize(std::uint64_t size);
|
||||
std::uint64_t GetSize() const;
|
||||
void SetLastModified(std::uint64_t lastModified);
|
||||
std::uint64_t GetLastModified() const;
|
||||
|
||||
private:
|
||||
DiscordFileStat internal_;
|
||||
};
|
||||
|
||||
class Entitlement final {
|
||||
public:
|
||||
void SetId(Snowflake id);
|
||||
Snowflake GetId() const;
|
||||
void SetType(EntitlementType type);
|
||||
EntitlementType GetType() const;
|
||||
void SetSkuId(Snowflake skuId);
|
||||
Snowflake GetSkuId() const;
|
||||
|
||||
private:
|
||||
DiscordEntitlement internal_;
|
||||
};
|
||||
|
||||
class SkuPrice final {
|
||||
public:
|
||||
void SetAmount(std::uint32_t amount);
|
||||
std::uint32_t GetAmount() const;
|
||||
void SetCurrency(char const* currency);
|
||||
char const* GetCurrency() const;
|
||||
|
||||
private:
|
||||
DiscordSkuPrice internal_;
|
||||
};
|
||||
|
||||
class Sku final {
|
||||
public:
|
||||
void SetId(Snowflake id);
|
||||
Snowflake GetId() const;
|
||||
void SetType(SkuType type);
|
||||
SkuType GetType() const;
|
||||
void SetName(char const* name);
|
||||
char const* GetName() const;
|
||||
SkuPrice& GetPrice();
|
||||
SkuPrice const& GetPrice() const;
|
||||
|
||||
private:
|
||||
DiscordSku internal_;
|
||||
};
|
||||
|
||||
class InputMode final {
|
||||
public:
|
||||
void SetType(InputModeType type);
|
||||
InputModeType GetType() const;
|
||||
void SetShortcut(char const* shortcut);
|
||||
char const* GetShortcut() const;
|
||||
|
||||
private:
|
||||
DiscordInputMode internal_;
|
||||
};
|
||||
|
||||
class UserAchievement final {
|
||||
public:
|
||||
void SetUserId(Snowflake userId);
|
||||
Snowflake GetUserId() const;
|
||||
void SetAchievementId(Snowflake achievementId);
|
||||
Snowflake GetAchievementId() const;
|
||||
void SetPercentComplete(std::uint8_t percentComplete);
|
||||
std::uint8_t GetPercentComplete() const;
|
||||
void SetUnlockedAt(DateTime unlockedAt);
|
||||
DateTime GetUnlockedAt() const;
|
||||
|
||||
private:
|
||||
DiscordUserAchievement internal_;
|
||||
};
|
||||
|
||||
class LobbyTransaction final {
|
||||
public:
|
||||
Result SetType(LobbyType type);
|
||||
Result SetOwner(UserId ownerId);
|
||||
Result SetCapacity(std::uint32_t capacity);
|
||||
Result SetMetadata(MetadataKey key, MetadataValue value);
|
||||
Result DeleteMetadata(MetadataKey key);
|
||||
Result SetLocked(bool locked);
|
||||
|
||||
IDiscordLobbyTransaction** Receive() { return &internal_; }
|
||||
IDiscordLobbyTransaction* Internal() { return internal_; }
|
||||
|
||||
private:
|
||||
IDiscordLobbyTransaction* internal_;
|
||||
};
|
||||
|
||||
class LobbyMemberTransaction final {
|
||||
public:
|
||||
Result SetMetadata(MetadataKey key, MetadataValue value);
|
||||
Result DeleteMetadata(MetadataKey key);
|
||||
|
||||
IDiscordLobbyMemberTransaction** Receive() { return &internal_; }
|
||||
IDiscordLobbyMemberTransaction* Internal() { return internal_; }
|
||||
|
||||
private:
|
||||
IDiscordLobbyMemberTransaction* internal_;
|
||||
};
|
||||
|
||||
class LobbySearchQuery final {
|
||||
public:
|
||||
Result Filter(MetadataKey key,
|
||||
LobbySearchComparison comparison,
|
||||
LobbySearchCast cast,
|
||||
MetadataValue value);
|
||||
Result Sort(MetadataKey key, LobbySearchCast cast, MetadataValue value);
|
||||
Result Limit(std::uint32_t limit);
|
||||
Result Distance(LobbySearchDistance distance);
|
||||
|
||||
IDiscordLobbySearchQuery** Receive() { return &internal_; }
|
||||
IDiscordLobbySearchQuery* Internal() { return internal_; }
|
||||
|
||||
private:
|
||||
IDiscordLobbySearchQuery* internal_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
80
libs/discordGameSDK/cpp/user_manager.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "user_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class UserEvents final {
|
||||
public:
|
||||
static void OnCurrentUserUpdate(void* callbackData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->UserManager();
|
||||
module.OnCurrentUserUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordUserEvents UserManager::events_{
|
||||
&UserEvents::OnCurrentUserUpdate,
|
||||
};
|
||||
|
||||
Result UserManager::GetCurrentUser(User* currentUser)
|
||||
{
|
||||
if (!currentUser) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_current_user(internal_, reinterpret_cast<DiscordUser*>(currentUser));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void UserManager::GetUser(UserId userId, std::function<void(Result, User const&)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result, DiscordUser* user) -> void {
|
||||
std::unique_ptr<std::function<void(Result, User const&)>> cb(
|
||||
reinterpret_cast<std::function<void(Result, User const&)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result), *reinterpret_cast<User const*>(user));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result, User const&)>> cb{};
|
||||
cb.reset(new std::function<void(Result, User const&)>(std::move(callback)));
|
||||
internal_->get_user(internal_, userId, cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result UserManager::GetCurrentUserPremiumType(PremiumType* premiumType)
|
||||
{
|
||||
if (!premiumType) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->get_current_user_premium_type(
|
||||
internal_, reinterpret_cast<EDiscordPremiumType*>(premiumType));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result UserManager::CurrentUserHasFlag(UserFlag flag, bool* hasFlag)
|
||||
{
|
||||
if (!hasFlag) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->current_user_has_flag(
|
||||
internal_, static_cast<EDiscordUserFlag>(flag), reinterpret_cast<bool*>(hasFlag));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
31
libs/discordGameSDK/cpp/user_manager.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class UserManager final {
|
||||
public:
|
||||
~UserManager() = default;
|
||||
|
||||
Result GetCurrentUser(User* currentUser);
|
||||
void GetUser(UserId userId, std::function<void(Result, User const&)> callback);
|
||||
Result GetCurrentUserPremiumType(PremiumType* premiumType);
|
||||
Result CurrentUserHasFlag(UserFlag flag, bool* hasFlag);
|
||||
|
||||
Event<> OnCurrentUserUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
UserManager() = default;
|
||||
UserManager(UserManager const& rhs) = delete;
|
||||
UserManager& operator=(UserManager const& rhs) = delete;
|
||||
UserManager(UserManager&& rhs) = delete;
|
||||
UserManager& operator=(UserManager&& rhs) = delete;
|
||||
|
||||
IDiscordUserManager* internal_;
|
||||
static IDiscordUserEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
124
libs/discordGameSDK/cpp/voice_manager.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "voice_manager.h"
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace discord {
|
||||
|
||||
class VoiceEvents final {
|
||||
public:
|
||||
static void OnSettingsUpdate(void* callbackData)
|
||||
{
|
||||
auto* core = reinterpret_cast<Core*>(callbackData);
|
||||
if (!core) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& module = core->VoiceManager();
|
||||
module.OnSettingsUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
IDiscordVoiceEvents VoiceManager::events_{
|
||||
&VoiceEvents::OnSettingsUpdate,
|
||||
};
|
||||
|
||||
Result VoiceManager::GetInputMode(InputMode* inputMode)
|
||||
{
|
||||
if (!inputMode) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_input_mode(internal_, reinterpret_cast<DiscordInputMode*>(inputMode));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
void VoiceManager::SetInputMode(InputMode inputMode, std::function<void(Result)> callback)
|
||||
{
|
||||
static auto wrapper = [](void* callbackData, EDiscordResult result) -> void {
|
||||
std::unique_ptr<std::function<void(Result)>> cb(
|
||||
reinterpret_cast<std::function<void(Result)>*>(callbackData));
|
||||
if (!cb || !(*cb)) {
|
||||
return;
|
||||
}
|
||||
(*cb)(static_cast<Result>(result));
|
||||
};
|
||||
std::unique_ptr<std::function<void(Result)>> cb{};
|
||||
cb.reset(new std::function<void(Result)>(std::move(callback)));
|
||||
internal_->set_input_mode(
|
||||
internal_, *reinterpret_cast<DiscordInputMode const*>(&inputMode), cb.release(), wrapper);
|
||||
}
|
||||
|
||||
Result VoiceManager::IsSelfMute(bool* mute)
|
||||
{
|
||||
if (!mute) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->is_self_mute(internal_, reinterpret_cast<bool*>(mute));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::SetSelfMute(bool mute)
|
||||
{
|
||||
auto result = internal_->set_self_mute(internal_, (mute ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::IsSelfDeaf(bool* deaf)
|
||||
{
|
||||
if (!deaf) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->is_self_deaf(internal_, reinterpret_cast<bool*>(deaf));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::SetSelfDeaf(bool deaf)
|
||||
{
|
||||
auto result = internal_->set_self_deaf(internal_, (deaf ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::IsLocalMute(Snowflake userId, bool* mute)
|
||||
{
|
||||
if (!mute) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result = internal_->is_local_mute(internal_, userId, reinterpret_cast<bool*>(mute));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::SetLocalMute(Snowflake userId, bool mute)
|
||||
{
|
||||
auto result = internal_->set_local_mute(internal_, userId, (mute ? 1 : 0));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::GetLocalVolume(Snowflake userId, std::uint8_t* volume)
|
||||
{
|
||||
if (!volume) {
|
||||
return Result::InternalError;
|
||||
}
|
||||
|
||||
auto result =
|
||||
internal_->get_local_volume(internal_, userId, reinterpret_cast<uint8_t*>(volume));
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
Result VoiceManager::SetLocalVolume(Snowflake userId, std::uint8_t volume)
|
||||
{
|
||||
auto result = internal_->set_local_volume(internal_, userId, volume);
|
||||
return static_cast<Result>(result);
|
||||
}
|
||||
|
||||
} // namespace discord
|
||||
37
libs/discordGameSDK/cpp/voice_manager.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace discord {
|
||||
|
||||
class VoiceManager final {
|
||||
public:
|
||||
~VoiceManager() = default;
|
||||
|
||||
Result GetInputMode(InputMode* inputMode);
|
||||
void SetInputMode(InputMode inputMode, std::function<void(Result)> callback);
|
||||
Result IsSelfMute(bool* mute);
|
||||
Result SetSelfMute(bool mute);
|
||||
Result IsSelfDeaf(bool* deaf);
|
||||
Result SetSelfDeaf(bool deaf);
|
||||
Result IsLocalMute(Snowflake userId, bool* mute);
|
||||
Result SetLocalMute(Snowflake userId, bool mute);
|
||||
Result GetLocalVolume(Snowflake userId, std::uint8_t* volume);
|
||||
Result SetLocalVolume(Snowflake userId, std::uint8_t volume);
|
||||
|
||||
Event<> OnSettingsUpdate;
|
||||
|
||||
private:
|
||||
friend class Core;
|
||||
|
||||
VoiceManager() = default;
|
||||
VoiceManager(VoiceManager const& rhs) = delete;
|
||||
VoiceManager& operator=(VoiceManager const& rhs) = delete;
|
||||
VoiceManager(VoiceManager&& rhs) = delete;
|
||||
VoiceManager& operator=(VoiceManager&& rhs) = delete;
|
||||
|
||||
IDiscordVoiceManager* internal_;
|
||||
static IDiscordVoiceEvents events_;
|
||||
};
|
||||
|
||||
} // namespace discord
|
||||
12
libs/discordGameSDK/csharp/ActivityManager.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class ActivityManager
|
||||
{
|
||||
public void RegisterCommand()
|
||||
{
|
||||
RegisterCommand(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
libs/discordGameSDK/csharp/Constants.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
static class Constants
|
||||
{
|
||||
public const string DllName = "discord_game_sdk";
|
||||
}
|
||||
}
|
||||
4199
libs/discordGameSDK/csharp/Core.cs
Normal file
53
libs/discordGameSDK/csharp/ImageManager.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial struct ImageHandle
|
||||
{
|
||||
static public ImageHandle User(Int64 id)
|
||||
{
|
||||
return User(id, 128);
|
||||
}
|
||||
|
||||
static public ImageHandle User(Int64 id, UInt32 size)
|
||||
{
|
||||
return new ImageHandle
|
||||
{
|
||||
Type = ImageType.User,
|
||||
Id = id,
|
||||
Size = size,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ImageManager
|
||||
{
|
||||
public void Fetch(ImageHandle handle, FetchHandler callback)
|
||||
{
|
||||
Fetch(handle, false, callback);
|
||||
}
|
||||
|
||||
public byte[] GetData(ImageHandle handle)
|
||||
{
|
||||
var dimensions = GetDimensions(handle);
|
||||
var data = new byte[dimensions.Width * dimensions.Height * 4];
|
||||
GetData(handle, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR || UNITY_STANDALONE
|
||||
public Texture2D GetTexture(ImageHandle handle)
|
||||
{
|
||||
var dimensions = GetDimensions(handle);
|
||||
var texture = new Texture2D((int)dimensions.Width, (int)dimensions.Height, TextureFormat.RGBA32, false, true);
|
||||
texture.LoadRawTextureData(GetData(handle));
|
||||
texture.Apply();
|
||||
return texture;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
26
libs/discordGameSDK/csharp/LobbyManager.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class LobbyManager
|
||||
{
|
||||
public IEnumerable<User> GetMemberUsers(Int64 lobbyID)
|
||||
{
|
||||
var memberCount = MemberCount(lobbyID);
|
||||
var members = new List<User>();
|
||||
for (var i = 0; i < memberCount; i++)
|
||||
{
|
||||
members.Add(GetMemberUser(lobbyID, GetMemberUserId(lobbyID, i)));
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
public void SendLobbyMessage(Int64 lobbyID, string data, SendLobbyMessageHandler handler)
|
||||
{
|
||||
SendLobbyMessage(lobbyID, Encoding.UTF8.GetBytes(data), handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
libs/discordGameSDK/csharp/StorageManager.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class StorageManager
|
||||
{
|
||||
public IEnumerable<FileStat> Files()
|
||||
{
|
||||
var fileCount = Count();
|
||||
var files = new List<FileStat>();
|
||||
for (var i = 0; i < fileCount; i++)
|
||||
{
|
||||
files.Add(StatAt(i));
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
libs/discordGameSDK/csharp/StoreManager.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Discord
|
||||
{
|
||||
public partial class StoreManager
|
||||
{
|
||||
public IEnumerable<Entitlement> GetEntitlements()
|
||||
{
|
||||
var count = CountEntitlements();
|
||||
var entitlements = new List<Entitlement>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
entitlements.Add(GetEntitlementAt(i));
|
||||
}
|
||||
return entitlements;
|
||||
}
|
||||
|
||||
public IEnumerable<Sku> GetSkus()
|
||||
{
|
||||
var count = CountSkus();
|
||||
var skus = new List<Sku>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
skus.Add(GetSkuAt(i));
|
||||
}
|
||||
return skus;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
libs/discordGameSDK/examples/c/main.c
Normal file
@@ -0,0 +1,155 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "discord_game_sdk.h"
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#define DISCORD_REQUIRE(x) assert(x == DiscordResult_Ok)
|
||||
|
||||
struct Application {
|
||||
struct IDiscordCore* core;
|
||||
struct IDiscordUserManager* users;
|
||||
struct IDiscordAchievementManager* achievements;
|
||||
struct IDiscordActivityManager* activities;
|
||||
struct IDiscordRelationshipManager* relationships;
|
||||
struct IDiscordApplicationManager* application;
|
||||
struct IDiscordLobbyManager* lobbies;
|
||||
DiscordUserId user_id;
|
||||
};
|
||||
|
||||
void UpdateActivityCallback(void* data, enum EDiscordResult result)
|
||||
{
|
||||
DISCORD_REQUIRE(result);
|
||||
}
|
||||
|
||||
int RelationshipPassFilter(void* data, struct DiscordRelationship* relationship)
|
||||
{
|
||||
return (relationship->type == DiscordRelationshipType_Friend);
|
||||
}
|
||||
|
||||
int RelationshipSnowflakeFilter(void* data, struct DiscordRelationship* relationship)
|
||||
{
|
||||
struct Application* app = (struct Application*)data;
|
||||
|
||||
return (relationship->type == DiscordRelationshipType_Friend &&
|
||||
relationship->user.id < app->user_id);
|
||||
}
|
||||
|
||||
void OnRelationshipsRefresh(void* data)
|
||||
{
|
||||
struct Application* app = (struct Application*)data;
|
||||
struct IDiscordRelationshipManager* module = app->relationships;
|
||||
|
||||
module->filter(module, app, RelationshipPassFilter);
|
||||
|
||||
int32_t unfiltered_count = 0;
|
||||
DISCORD_REQUIRE(module->count(module, &unfiltered_count));
|
||||
|
||||
module->filter(module, app, RelationshipSnowflakeFilter);
|
||||
|
||||
int32_t filtered_count = 0;
|
||||
DISCORD_REQUIRE(module->count(module, &filtered_count));
|
||||
|
||||
printf("=== Cool Friends ===\n");
|
||||
for (int32_t i = 0; i < filtered_count; i += 1) {
|
||||
struct DiscordRelationship relationship;
|
||||
DISCORD_REQUIRE(module->get_at(module, i, &relationship));
|
||||
|
||||
printf("%lld %s#%s\n",
|
||||
relationship.user.id,
|
||||
relationship.user.username,
|
||||
relationship.user.discriminator);
|
||||
}
|
||||
printf("(%d friends less cool than you omitted)\n", unfiltered_count - filtered_count);
|
||||
|
||||
struct DiscordActivity activity;
|
||||
memset(&activity, 0, sizeof(activity));
|
||||
sprintf(activity.details, "Cooler than %d friends", unfiltered_count - filtered_count);
|
||||
sprintf(activity.state, "%d friends total", unfiltered_count);
|
||||
|
||||
app->activities->update_activity(app->activities, &activity, app, UpdateActivityCallback);
|
||||
}
|
||||
|
||||
void OnUserUpdated(void* data)
|
||||
{
|
||||
struct Application* app = (struct Application*)data;
|
||||
struct DiscordUser user;
|
||||
app->users->get_current_user(app->users, &user);
|
||||
app->user_id = user.id;
|
||||
}
|
||||
|
||||
void OnOAuth2Token(void* data, enum EDiscordResult result, struct DiscordOAuth2Token* token)
|
||||
{
|
||||
if (result == DiscordResult_Ok) {
|
||||
printf("OAuth2 token: %s\n", token->access_token);
|
||||
}
|
||||
else {
|
||||
printf("GetOAuth2Token failed with %d\n", (int)result);
|
||||
}
|
||||
}
|
||||
|
||||
void OnLobbyConnect(void* data, enum EDiscordResult result, struct DiscordLobby* lobby)
|
||||
{
|
||||
printf("LobbyConnect returned %d\n", (int)result);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
struct Application app;
|
||||
memset(&app, 0, sizeof(app));
|
||||
|
||||
struct IDiscordUserEvents users_events;
|
||||
memset(&users_events, 0, sizeof(users_events));
|
||||
users_events.on_current_user_update = OnUserUpdated;
|
||||
|
||||
struct IDiscordActivityEvents activities_events;
|
||||
memset(&activities_events, 0, sizeof(activities_events));
|
||||
|
||||
struct IDiscordRelationshipEvents relationships_events;
|
||||
memset(&relationships_events, 0, sizeof(relationships_events));
|
||||
relationships_events.on_refresh = OnRelationshipsRefresh;
|
||||
|
||||
struct DiscordCreateParams params;
|
||||
DiscordCreateParamsSetDefault(¶ms);
|
||||
params.client_id = 418559331265675294;
|
||||
params.flags = DiscordCreateFlags_Default;
|
||||
params.event_data = &app;
|
||||
params.activity_events = &activities_events;
|
||||
params.relationship_events = &relationships_events;
|
||||
params.user_events = &users_events;
|
||||
DISCORD_REQUIRE(DiscordCreate(DISCORD_VERSION, ¶ms, &app.core));
|
||||
|
||||
app.users = app.core->get_user_manager(app.core);
|
||||
app.achievements = app.core->get_achievement_manager(app.core);
|
||||
app.activities = app.core->get_activity_manager(app.core);
|
||||
app.application = app.core->get_application_manager(app.core);
|
||||
app.lobbies = app.core->get_lobby_manager(app.core);
|
||||
|
||||
app.lobbies->connect_lobby_with_activity_secret(
|
||||
app.lobbies, "invalid_secret", &app, OnLobbyConnect);
|
||||
|
||||
app.application->get_oauth2_token(app.application, &app, OnOAuth2Token);
|
||||
|
||||
DiscordBranch branch;
|
||||
app.application->get_current_branch(app.application, &branch);
|
||||
printf("Current branch %s\n", branch);
|
||||
|
||||
app.relationships = app.core->get_relationship_manager(app.core);
|
||||
|
||||
for (;;) {
|
||||
DISCORD_REQUIRE(app.core->run_callbacks(app.core));
|
||||
|
||||
#ifdef _WIN32
|
||||
Sleep(16);
|
||||
#else
|
||||
usleep(16 * 1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
297
libs/discordGameSDK/examples/cpp/main.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "discord.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(push, 1)
|
||||
struct BitmapImageHeader {
|
||||
uint32_t const structSize{sizeof(BitmapImageHeader)};
|
||||
int32_t width{0};
|
||||
int32_t height{0};
|
||||
uint16_t const planes{1};
|
||||
uint16_t const bpp{32};
|
||||
uint32_t const pad0{0};
|
||||
uint32_t const pad1{0};
|
||||
uint32_t const hres{2835};
|
||||
uint32_t const vres{2835};
|
||||
uint32_t const pad4{0};
|
||||
uint32_t const pad5{0};
|
||||
|
||||
BitmapImageHeader& operator=(BitmapImageHeader const&) = delete;
|
||||
};
|
||||
|
||||
struct BitmapFileHeader {
|
||||
uint8_t const magic0{'B'};
|
||||
uint8_t const magic1{'M'};
|
||||
uint32_t size{0};
|
||||
uint32_t const pad{0};
|
||||
uint32_t const offset{sizeof(BitmapFileHeader) + sizeof(BitmapImageHeader)};
|
||||
|
||||
BitmapFileHeader& operator=(BitmapFileHeader const&) = delete;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
struct DiscordState {
|
||||
discord::User currentUser;
|
||||
|
||||
std::unique_ptr<discord::Core> core;
|
||||
};
|
||||
|
||||
namespace {
|
||||
volatile bool interrupted{false};
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
DiscordState state{};
|
||||
|
||||
discord::Core* core{};
|
||||
auto result = discord::Core::Create(310270644849737729, DiscordCreateFlags_Default, &core);
|
||||
state.core.reset(core);
|
||||
if (!state.core) {
|
||||
std::cout << "Failed to instantiate discord core! (err " << static_cast<int>(result)
|
||||
<< ")\n";
|
||||
std::exit(-1);
|
||||
}
|
||||
|
||||
state.core->SetLogHook(
|
||||
discord::LogLevel::Debug, [](discord::LogLevel level, const char* message) {
|
||||
std::cerr << "Log(" << static_cast<uint32_t>(level) << "): " << message << "\n";
|
||||
});
|
||||
|
||||
core->UserManager().OnCurrentUserUpdate.Connect([&state]() {
|
||||
state.core->UserManager().GetCurrentUser(&state.currentUser);
|
||||
|
||||
std::cout << "Current user updated: " << state.currentUser.GetUsername() << "#"
|
||||
<< state.currentUser.GetDiscriminator() << "\n";
|
||||
|
||||
state.core->UserManager().GetUser(130050050968518656,
|
||||
[](discord::Result result, discord::User const& user) {
|
||||
if (result == discord::Result::Ok) {
|
||||
std::cout << "Get " << user.GetUsername() << "\n";
|
||||
}
|
||||
else {
|
||||
std::cout << "Failed to get David!\n";
|
||||
}
|
||||
});
|
||||
|
||||
discord::ImageHandle handle{};
|
||||
handle.SetId(state.currentUser.GetId());
|
||||
handle.SetType(discord::ImageType::User);
|
||||
handle.SetSize(256);
|
||||
|
||||
state.core->ImageManager().Fetch(
|
||||
handle, true, [&state](discord::Result res, discord::ImageHandle handle) {
|
||||
if (res == discord::Result::Ok) {
|
||||
discord::ImageDimensions dims{};
|
||||
state.core->ImageManager().GetDimensions(handle, &dims);
|
||||
std::cout << "Fetched " << dims.GetWidth() << "x" << dims.GetHeight()
|
||||
<< " avatar!\n";
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
data.reserve(dims.GetWidth() * dims.GetHeight() * 4);
|
||||
uint8_t* d = data.data();
|
||||
state.core->ImageManager().GetData(handle, d, data.size());
|
||||
|
||||
#if defined(_WIN32)
|
||||
auto fileSize =
|
||||
data.size() + sizeof(BitmapImageHeader) + sizeof(BitmapFileHeader);
|
||||
|
||||
BitmapImageHeader imageHeader;
|
||||
imageHeader.width = static_cast<int32_t>(dims.GetWidth());
|
||||
imageHeader.height = static_cast<int32_t>(dims.GetHeight());
|
||||
|
||||
BitmapFileHeader fileHeader;
|
||||
fileHeader.size = static_cast<uint32_t>(fileSize);
|
||||
|
||||
FILE* fp = fopen("avatar.bmp", "wb");
|
||||
fwrite(&fileHeader, sizeof(BitmapFileHeader), 1, fp);
|
||||
fwrite(&imageHeader, sizeof(BitmapImageHeader), 1, fp);
|
||||
|
||||
for (auto y = 0u; y < dims.GetHeight(); ++y) {
|
||||
auto pixels = reinterpret_cast<uint32_t const*>(data.data());
|
||||
auto invY = dims.GetHeight() - y - 1;
|
||||
fwrite(
|
||||
&pixels[invY * dims.GetWidth()], sizeof(uint32_t) * dims.GetWidth(), 1, fp);
|
||||
}
|
||||
|
||||
fflush(fp);
|
||||
fclose(fp);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
std::cout << "Failed fetching avatar. (err " << static_cast<int>(res) << ")\n";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
state.core->ActivityManager().RegisterCommand("run/command/foo/bar/baz/here.exe");
|
||||
state.core->ActivityManager().RegisterSteam(123123321);
|
||||
|
||||
state.core->ActivityManager().OnActivityJoin.Connect(
|
||||
[](const char* secret) { std::cout << "Join " << secret << "\n"; });
|
||||
state.core->ActivityManager().OnActivitySpectate.Connect(
|
||||
[](const char* secret) { std::cout << "Spectate " << secret << "\n"; });
|
||||
state.core->ActivityManager().OnActivityJoinRequest.Connect([](discord::User const& user) {
|
||||
std::cout << "Join Request " << user.GetUsername() << "\n";
|
||||
});
|
||||
state.core->ActivityManager().OnActivityInvite.Connect(
|
||||
[](discord::ActivityActionType, discord::User const& user, discord::Activity const&) {
|
||||
std::cout << "Invite " << user.GetUsername() << "\n";
|
||||
});
|
||||
|
||||
state.core->LobbyManager().OnLobbyUpdate.Connect(
|
||||
[](std::int64_t lobbyId) { std::cout << "Lobby update " << lobbyId << "\n"; });
|
||||
|
||||
state.core->LobbyManager().OnLobbyDelete.Connect(
|
||||
[](std::int64_t lobbyId, std::uint32_t reason) {
|
||||
std::cout << "Lobby delete " << lobbyId << " (reason: " << reason << ")\n";
|
||||
});
|
||||
|
||||
state.core->LobbyManager().OnMemberConnect.Connect(
|
||||
[](std::int64_t lobbyId, std::int64_t userId) {
|
||||
std::cout << "Lobby member connect " << lobbyId << " userId " << userId << "\n";
|
||||
});
|
||||
|
||||
state.core->LobbyManager().OnMemberUpdate.Connect(
|
||||
[](std::int64_t lobbyId, std::int64_t userId) {
|
||||
std::cout << "Lobby member update " << lobbyId << " userId " << userId << "\n";
|
||||
});
|
||||
|
||||
state.core->LobbyManager().OnMemberDisconnect.Connect(
|
||||
[](std::int64_t lobbyId, std::int64_t userId) {
|
||||
std::cout << "Lobby member disconnect " << lobbyId << " userId " << userId << "\n";
|
||||
});
|
||||
|
||||
state.core->LobbyManager().OnLobbyMessage.Connect([&](std::int64_t lobbyId,
|
||||
std::int64_t userId,
|
||||
std::uint8_t* payload,
|
||||
std::uint32_t payloadLength) {
|
||||
std::vector<uint8_t> buffer{};
|
||||
buffer.resize(payloadLength);
|
||||
memcpy(buffer.data(), payload, payloadLength);
|
||||
std::cout << "Lobby message " << lobbyId << " from " << userId << " of length "
|
||||
<< payloadLength << " bytes.\n";
|
||||
|
||||
char fourtyNinetySix[4096];
|
||||
state.core->LobbyManager().GetLobbyMetadataValue(lobbyId, "foo", fourtyNinetySix);
|
||||
|
||||
std::cout << "Metadata for key foo is " << fourtyNinetySix << "\n";
|
||||
});
|
||||
|
||||
state.core->LobbyManager().OnSpeaking.Connect(
|
||||
[&](std::int64_t, std::int64_t userId, bool speaking) {
|
||||
std::cout << "User " << userId << " is " << (speaking ? "" : "NOT ") << "speaking.\n";
|
||||
});
|
||||
|
||||
discord::Activity activity{};
|
||||
activity.SetDetails("Fruit Tarts");
|
||||
activity.SetState("Pop Snacks");
|
||||
activity.GetAssets().SetSmallImage("the");
|
||||
activity.GetAssets().SetSmallText("i mage");
|
||||
activity.GetAssets().SetLargeImage("the");
|
||||
activity.GetAssets().SetLargeText("u mage");
|
||||
activity.SetType(discord::ActivityType::Playing);
|
||||
state.core->ActivityManager().UpdateActivity(activity, [](discord::Result result) {
|
||||
std::cout << ((result == discord::Result::Ok) ? "Succeeded" : "Failed")
|
||||
<< " updating activity!\n";
|
||||
});
|
||||
|
||||
discord::LobbyTransaction lobby{};
|
||||
state.core->LobbyManager().GetLobbyCreateTransaction(&lobby);
|
||||
lobby.SetCapacity(2);
|
||||
lobby.SetMetadata("foo", "bar");
|
||||
lobby.SetMetadata("baz", "bat");
|
||||
lobby.SetType(discord::LobbyType::Public);
|
||||
state.core->LobbyManager().CreateLobby(
|
||||
lobby, [&state](discord::Result result, discord::Lobby const& lobby) {
|
||||
if (result == discord::Result::Ok) {
|
||||
std::cout << "Created lobby with secret " << lobby.GetSecret() << "\n";
|
||||
std::array<uint8_t, 234> data{};
|
||||
state.core->LobbyManager().SendLobbyMessage(
|
||||
lobby.GetId(),
|
||||
reinterpret_cast<uint8_t*>(data.data()),
|
||||
data.size(),
|
||||
[](discord::Result result) {
|
||||
std::cout << "Sent message. Result: " << static_cast<int>(result) << "\n";
|
||||
});
|
||||
}
|
||||
else {
|
||||
std::cout << "Failed creating lobby. (err " << static_cast<int>(result) << ")\n";
|
||||
}
|
||||
|
||||
discord::LobbySearchQuery query{};
|
||||
state.core->LobbyManager().GetSearchQuery(&query);
|
||||
query.Limit(1);
|
||||
state.core->LobbyManager().Search(query, [&state](discord::Result result) {
|
||||
if (result == discord::Result::Ok) {
|
||||
std::int32_t lobbyCount{};
|
||||
state.core->LobbyManager().LobbyCount(&lobbyCount);
|
||||
std::cout << "Lobby search succeeded with " << lobbyCount << " lobbies.\n";
|
||||
for (auto i = 0; i < lobbyCount; ++i) {
|
||||
discord::LobbyId lobbyId{};
|
||||
state.core->LobbyManager().GetLobbyId(i, &lobbyId);
|
||||
std::cout << " " << lobbyId << "\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::cout << "Lobby search failed. (err " << static_cast<int>(result) << ")\n";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
state.core->RelationshipManager().OnRefresh.Connect([&]() {
|
||||
std::cout << "Relationships refreshed!\n";
|
||||
|
||||
state.core->RelationshipManager().Filter(
|
||||
[](discord::Relationship const& relationship) -> bool {
|
||||
return relationship.GetType() == discord::RelationshipType::Friend;
|
||||
});
|
||||
|
||||
std::int32_t friendCount{0};
|
||||
state.core->RelationshipManager().Count(&friendCount);
|
||||
|
||||
state.core->RelationshipManager().Filter(
|
||||
[&](discord::Relationship const& relationship) -> bool {
|
||||
return relationship.GetType() == discord::RelationshipType::Friend &&
|
||||
relationship.GetUser().GetId() < state.currentUser.GetId();
|
||||
});
|
||||
|
||||
std::int32_t filteredCount{0};
|
||||
state.core->RelationshipManager().Count(&filteredCount);
|
||||
|
||||
discord::Relationship relationship{};
|
||||
for (auto i = 0; i < filteredCount; ++i) {
|
||||
state.core->RelationshipManager().GetAt(i, &relationship);
|
||||
std::cout << relationship.GetUser().GetId() << " "
|
||||
<< relationship.GetUser().GetUsername() << "#"
|
||||
<< relationship.GetUser().GetDiscriminator() << "\n";
|
||||
}
|
||||
});
|
||||
|
||||
state.core->RelationshipManager().OnRelationshipUpdate.Connect(
|
||||
[](discord::Relationship const& relationship) {
|
||||
std::cout << "Relationship with " << relationship.GetUser().GetUsername()
|
||||
<< " updated!\n";
|
||||
});
|
||||
|
||||
std::signal(SIGINT, [](int) { interrupted = true; });
|
||||
|
||||
do {
|
||||
state.core->RunCallbacks();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
} while (!interrupted);
|
||||
|
||||
return 0;
|
||||
}
|
||||
412
libs/discordGameSDK/examples/csharp/Program.cs
Normal file
@@ -0,0 +1,412 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
class Program
|
||||
{
|
||||
// Request user's avatar data. Sizes can be powers of 2 between 16 and 2048
|
||||
static void FetchAvatar(Discord.ImageManager imageManager, Int64 userID)
|
||||
{
|
||||
imageManager.Fetch(Discord.ImageHandle.User(userID), (result, handle) =>
|
||||
{
|
||||
{
|
||||
if (result == Discord.Result.Ok)
|
||||
{
|
||||
// You can also use GetTexture2D within Unity.
|
||||
// These return raw RGBA.
|
||||
var data = imageManager.GetData(handle);
|
||||
Console.WriteLine("image updated {0} {1}", handle.Id, data.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("image error {0}", handle.Id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update user's activity for your game.
|
||||
// Party and secrets are vital.
|
||||
// Read https://discordapp.com/developers/docs/rich-presence/how-to for more details.
|
||||
static void UpdateActivity(Discord.Discord discord, Discord.Lobby lobby)
|
||||
{
|
||||
var activityManager = discord.GetActivityManager();
|
||||
var lobbyManager = discord.GetLobbyManager();
|
||||
|
||||
var activity = new Discord.Activity
|
||||
{
|
||||
State = "olleh",
|
||||
Details = "foo details",
|
||||
Timestamps =
|
||||
{
|
||||
Start = 5,
|
||||
End = 6,
|
||||
},
|
||||
Assets =
|
||||
{
|
||||
LargeImage = "foo largeImageKey",
|
||||
LargeText = "foo largeImageText",
|
||||
SmallImage = "foo smallImageKey",
|
||||
SmallText = "foo smallImageText",
|
||||
},
|
||||
Party = {
|
||||
Id = lobby.Id.ToString(),
|
||||
Size = {
|
||||
CurrentSize = lobbyManager.MemberCount(lobby.Id),
|
||||
MaxSize = (int)lobby.Capacity,
|
||||
},
|
||||
},
|
||||
Secrets = {
|
||||
Join = lobbyManager.GetLobbyActivitySecret(lobby.Id),
|
||||
},
|
||||
Instance = true,
|
||||
};
|
||||
|
||||
activityManager.UpdateActivity(activity, result =>
|
||||
{
|
||||
Console.WriteLine("Update Activity {0}", result);
|
||||
|
||||
// Send an invite to another user for this activity.
|
||||
// Receiver should see an invite in their DM.
|
||||
// Use a relationship user's ID for this.
|
||||
// activityManager
|
||||
// .SendInvite(
|
||||
// 364843917537050624,
|
||||
// Discord.ActivityActionType.Join,
|
||||
// "",
|
||||
// inviteResult =>
|
||||
// {
|
||||
// Console.WriteLine("Invite {0}", inviteResult);
|
||||
// }
|
||||
// );
|
||||
});
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Use your client ID from Discord's developer site.
|
||||
var clientID = Environment.GetEnvironmentVariable("DISCORD_CLIENT_ID");
|
||||
if (clientID == null)
|
||||
{
|
||||
clientID = "418559331265675294";
|
||||
}
|
||||
var discord = new Discord.Discord(Int64.Parse(clientID), (UInt64)Discord.CreateFlags.Default);
|
||||
discord.SetLogHook(Discord.LogLevel.Debug, (level, message) =>
|
||||
{
|
||||
Console.WriteLine("Log[{0}] {1}", level, message);
|
||||
});
|
||||
|
||||
var applicationManager = discord.GetApplicationManager();
|
||||
// Get the current locale. This can be used to determine what text or audio the user wants.
|
||||
Console.WriteLine("Current Locale: {0}", applicationManager.GetCurrentLocale());
|
||||
// Get the current branch. For example alpha or beta.
|
||||
Console.WriteLine("Current Branch: {0}", applicationManager.GetCurrentBranch());
|
||||
// If you want to verify information from your game's server then you can
|
||||
// grab the access token and send it to your server.
|
||||
//
|
||||
// This automatically looks for an environment variable passed by the Discord client,
|
||||
// if it does not exist the Discord client will focus itself for manual authorization.
|
||||
//
|
||||
// By-default the SDK grants the identify and rpc scopes.
|
||||
// Read more at https://discordapp.com/developers/docs/topics/oauth2
|
||||
// applicationManager.GetOAuth2Token((Discord.Result result, ref Discord.OAuth2Token oauth2Token) =>
|
||||
// {
|
||||
// Console.WriteLine("Access Token {0}", oauth2Token.AccessToken);
|
||||
// });
|
||||
|
||||
var activityManager = discord.GetActivityManager();
|
||||
var lobbyManager = discord.GetLobbyManager();
|
||||
// Received when someone accepts a request to join or invite.
|
||||
// Use secrets to receive back the information needed to add the user to the group/party/match
|
||||
activityManager.OnActivityJoin += secret =>
|
||||
{
|
||||
Console.WriteLine("OnJoin {0}", secret);
|
||||
lobbyManager.ConnectLobbyWithActivitySecret(secret, (Discord.Result result, ref Discord.Lobby lobby) =>
|
||||
{
|
||||
Console.WriteLine("Connected to lobby: {0}", lobby.Id);
|
||||
lobbyManager.ConnectNetwork(lobby.Id);
|
||||
lobbyManager.OpenNetworkChannel(lobby.Id, 0, true);
|
||||
foreach (var user in lobbyManager.GetMemberUsers(lobby.Id))
|
||||
{
|
||||
lobbyManager.SendNetworkMessage(lobby.Id, user.Id, 0,
|
||||
Encoding.UTF8.GetBytes(String.Format("Hello, {0}!", user.Username)));
|
||||
}
|
||||
UpdateActivity(discord, lobby);
|
||||
});
|
||||
};
|
||||
// Received when someone accepts a request to spectate
|
||||
activityManager.OnActivitySpectate += secret =>
|
||||
{
|
||||
Console.WriteLine("OnSpectate {0}", secret);
|
||||
};
|
||||
// A join request has been received. Render the request on the UI.
|
||||
activityManager.OnActivityJoinRequest += (ref Discord.User user) =>
|
||||
{
|
||||
Console.WriteLine("OnJoinRequest {0} {1}", user.Id, user.Username);
|
||||
};
|
||||
// An invite has been received. Consider rendering the user / activity on the UI.
|
||||
activityManager.OnActivityInvite += (Discord.ActivityActionType Type, ref Discord.User user, ref Discord.Activity activity2) =>
|
||||
{
|
||||
Console.WriteLine("OnInvite {0} {1} {2}", Type, user.Username, activity2.Name);
|
||||
// activityManager.AcceptInvite(user.Id, result =>
|
||||
// {
|
||||
// Console.WriteLine("AcceptInvite {0}", result);
|
||||
// });
|
||||
};
|
||||
// This is used to register the game in the registry such that Discord can find it.
|
||||
// This is only needed by games acquired from other platforms, like Steam.
|
||||
// activityManager.RegisterCommand();
|
||||
|
||||
var imageManager = discord.GetImageManager();
|
||||
|
||||
var userManager = discord.GetUserManager();
|
||||
// The auth manager fires events as information about the current user changes.
|
||||
// This event will fire once on init.
|
||||
//
|
||||
// GetCurrentUser will error until this fires once.
|
||||
userManager.OnCurrentUserUpdate += () =>
|
||||
{
|
||||
var currentUser = userManager.GetCurrentUser();
|
||||
Console.WriteLine(currentUser.Username);
|
||||
Console.WriteLine(currentUser.Id);
|
||||
};
|
||||
// If you store Discord user ids in a central place like a leaderboard and want to render them.
|
||||
// The users manager can be used to fetch arbitrary Discord users. This only provides basic
|
||||
// information and does not automatically update like relationships.
|
||||
userManager.GetUser(450795363658366976, (Discord.Result result, ref Discord.User user) =>
|
||||
{
|
||||
if (result == Discord.Result.Ok)
|
||||
{
|
||||
Console.WriteLine("user fetched: {0}", user.Username);
|
||||
|
||||
// Request users's avatar data.
|
||||
// This can only be done after a user is successfully fetched.
|
||||
FetchAvatar(imageManager, user.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("user fetch error: {0}", result);
|
||||
}
|
||||
});
|
||||
|
||||
var relationshipManager = discord.GetRelationshipManager();
|
||||
// It is important to assign this handle right away to get the initial relationships refresh.
|
||||
// This callback will only be fired when the whole list is initially loaded or was reset
|
||||
relationshipManager.OnRefresh += () =>
|
||||
{
|
||||
// Filter a user's relationship list to be just friends
|
||||
relationshipManager.Filter((ref Discord.Relationship relationship) => { return relationship.Type == Discord.RelationshipType.Friend; });
|
||||
// Loop over all friends a user has.
|
||||
Console.WriteLine("relationships updated: {0}", relationshipManager.Count());
|
||||
for (var i = 0; i < Math.Min(relationshipManager.Count(), 10); i++)
|
||||
{
|
||||
// Get an individual relationship from the list
|
||||
var r = relationshipManager.GetAt((uint)i);
|
||||
Console.WriteLine("relationships: {0} {1} {2} {3}", r.Type, r.User.Username, r.Presence.Status, r.Presence.Activity.Name);
|
||||
|
||||
// Request relationship's avatar data.
|
||||
FetchAvatar(imageManager, r.User.Id);
|
||||
}
|
||||
};
|
||||
// All following relationship updates are delivered individually.
|
||||
// These are fired when a user gets a new friend, removes a friend, or a relationship's presence changes.
|
||||
relationshipManager.OnRelationshipUpdate += (ref Discord.Relationship r) =>
|
||||
{
|
||||
Console.WriteLine("relationship updated: {0} {1} {2} {3}", r.Type, r.User.Username, r.Presence.Status, r.Presence.Activity.Name);
|
||||
};
|
||||
|
||||
lobbyManager.OnLobbyMessage += (lobbyID, userID, data) =>
|
||||
{
|
||||
Console.WriteLine("lobby message: {0} {1}", lobbyID, Encoding.UTF8.GetString(data));
|
||||
};
|
||||
lobbyManager.OnNetworkMessage += (lobbyId, userId, channelId, data) =>
|
||||
{
|
||||
Console.WriteLine("network message: {0} {1} {2} {3}", lobbyId, userId, channelId, Encoding.UTF8.GetString(data));
|
||||
};
|
||||
lobbyManager.OnSpeaking += (lobbyID, userID, speaking) =>
|
||||
{
|
||||
Console.WriteLine("lobby speaking: {0} {1} {2}", lobbyID, userID, speaking);
|
||||
};
|
||||
// Create a lobby.
|
||||
var transaction = lobbyManager.GetLobbyCreateTransaction();
|
||||
transaction.SetCapacity(6);
|
||||
transaction.SetType(Discord.LobbyType.Public);
|
||||
transaction.SetMetadata("a", "123");
|
||||
transaction.SetMetadata("a", "456");
|
||||
transaction.SetMetadata("b", "111");
|
||||
transaction.SetMetadata("c", "222");
|
||||
|
||||
lobbyManager.CreateLobby(transaction, (Discord.Result result, ref Discord.Lobby lobby) =>
|
||||
{
|
||||
if (result != Discord.Result.Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the lobby's configuration.
|
||||
Console.WriteLine("lobby {0} with capacity {1} and secret {2}", lobby.Id, lobby.Capacity, lobby.Secret);
|
||||
|
||||
// Check lobby metadata.
|
||||
foreach (var key in new string[] { "a", "b", "c" })
|
||||
{
|
||||
Console.WriteLine("{0} = {1}", key, lobbyManager.GetLobbyMetadataValue(lobby.Id, key));
|
||||
}
|
||||
|
||||
// Print all the members of the lobby.
|
||||
foreach (var user in lobbyManager.GetMemberUsers(lobby.Id))
|
||||
{
|
||||
Console.WriteLine("lobby member: {0}", user.Username);
|
||||
}
|
||||
|
||||
// Send everyone a message.
|
||||
lobbyManager.SendLobbyMessage(lobby.Id, "Hello from C#!", (_) =>
|
||||
{
|
||||
Console.WriteLine("sent message");
|
||||
});
|
||||
|
||||
// Update lobby.
|
||||
var lobbyTransaction = lobbyManager.GetLobbyUpdateTransaction(lobby.Id);
|
||||
lobbyTransaction.SetMetadata("d", "e");
|
||||
lobbyTransaction.SetCapacity(16);
|
||||
lobbyManager.UpdateLobby(lobby.Id, lobbyTransaction, (_) =>
|
||||
{
|
||||
Console.WriteLine("lobby has been updated");
|
||||
});
|
||||
|
||||
// Update a member.
|
||||
var lobbyID = lobby.Id;
|
||||
var userID = lobby.OwnerId;
|
||||
var memberTransaction = lobbyManager.GetMemberUpdateTransaction(lobbyID, userID);
|
||||
memberTransaction.SetMetadata("hello", "there");
|
||||
lobbyManager.UpdateMember(lobbyID, userID, memberTransaction, (_) =>
|
||||
{
|
||||
Console.WriteLine("lobby member has been updated: {0}", lobbyManager.GetMemberMetadataValue(lobbyID, userID, "hello"));
|
||||
});
|
||||
|
||||
// Search lobbies.
|
||||
var query = lobbyManager.GetSearchQuery();
|
||||
// Filter by a metadata value.
|
||||
query.Filter("metadata.a", Discord.LobbySearchComparison.GreaterThan, Discord.LobbySearchCast.Number, "455");
|
||||
query.Sort("metadata.a", Discord.LobbySearchCast.Number, "0");
|
||||
// Only return 1 result max.
|
||||
query.Limit(1);
|
||||
lobbyManager.Search(query, (_) =>
|
||||
{
|
||||
Console.WriteLine("search returned {0} lobbies", lobbyManager.LobbyCount());
|
||||
if (lobbyManager.LobbyCount() == 1)
|
||||
{
|
||||
Console.WriteLine("first lobby secret: {0}", lobbyManager.GetLobby(lobbyManager.GetLobbyId(0)).Secret);
|
||||
}
|
||||
});
|
||||
|
||||
// Connect to voice chat.
|
||||
lobbyManager.ConnectVoice(lobby.Id, (_) =>
|
||||
{
|
||||
Console.WriteLine("Connected to voice chat!");
|
||||
});
|
||||
|
||||
// Setup networking.
|
||||
lobbyManager.ConnectNetwork(lobby.Id);
|
||||
lobbyManager.OpenNetworkChannel(lobby.Id, 0, true);
|
||||
|
||||
// Update activity.
|
||||
UpdateActivity(discord, lobby);
|
||||
});
|
||||
|
||||
/*
|
||||
var overlayManager = discord.GetOverlayManager();
|
||||
overlayManager.OnOverlayLocked += locked =>
|
||||
{
|
||||
Console.WriteLine("Overlay Locked: {0}", locked);
|
||||
};
|
||||
overlayManager.SetLocked(false);
|
||||
*/
|
||||
|
||||
var storageManager = discord.GetStorageManager();
|
||||
var contents = new byte[20000];
|
||||
var random = new Random();
|
||||
random.NextBytes(contents);
|
||||
Console.WriteLine("storage path: {0}", storageManager.GetPath());
|
||||
storageManager.WriteAsync("foo", contents, res =>
|
||||
{
|
||||
var files = storageManager.Files();
|
||||
foreach (var file in files)
|
||||
{
|
||||
Console.WriteLine("file: {0} size: {1} last_modified: {2}", file.Filename, file.Size, file.LastModified);
|
||||
}
|
||||
storageManager.ReadAsyncPartial("foo", 400, 50, (result, data) =>
|
||||
{
|
||||
Console.WriteLine("partial contents of foo match {0}", Enumerable.SequenceEqual(data, new ArraySegment<byte>(contents, 400, 50)));
|
||||
});
|
||||
storageManager.ReadAsync("foo", (result, data) =>
|
||||
{
|
||||
Console.WriteLine("length of contents {0} data {1}", contents.Length, data.Length);
|
||||
Console.WriteLine("contents of foo match {0}", Enumerable.SequenceEqual(data, contents));
|
||||
Console.WriteLine("foo exists? {0}", storageManager.Exists("foo"));
|
||||
storageManager.Delete("foo");
|
||||
Console.WriteLine("post-delete foo exists? {0}", storageManager.Exists("foo"));
|
||||
});
|
||||
});
|
||||
|
||||
var storeManager = discord.GetStoreManager();
|
||||
storeManager.OnEntitlementCreate += (ref Discord.Entitlement entitlement) =>
|
||||
{
|
||||
Console.WriteLine("Entitlement Create1: {0}", entitlement.Id);
|
||||
};
|
||||
|
||||
// Start a purchase flow.
|
||||
// storeManager.StartPurchase(487507201519255552, result =>
|
||||
// {
|
||||
// if (result == Discord.Result.Ok)
|
||||
// {
|
||||
// Console.WriteLine("Purchase Complete");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Console.WriteLine("Purchase Canceled");
|
||||
// }
|
||||
// });
|
||||
|
||||
// Get all entitlements.
|
||||
storeManager.FetchEntitlements(result =>
|
||||
{
|
||||
if (result == Discord.Result.Ok)
|
||||
{
|
||||
foreach (var entitlement in storeManager.GetEntitlements())
|
||||
{
|
||||
Console.WriteLine("entitlement: {0} - {1} {2}", entitlement.Id, entitlement.Type, entitlement.SkuId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get all SKUs.
|
||||
storeManager.FetchSkus(result =>
|
||||
{
|
||||
if (result == Discord.Result.Ok)
|
||||
{
|
||||
foreach (var sku in storeManager.GetSkus())
|
||||
{
|
||||
Console.WriteLine("sku: {0} - {1} {2}", sku.Name, sku.Price.Amount, sku.Price.Currency);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Pump the event look to ensure all callbacks continue to get fired.
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
discord.RunCallbacks();
|
||||
lobbyManager.FlushNetwork();
|
||||
Thread.Sleep(1000 / 60);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
discord.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
BIN
libs/discordGameSDK/lib/x86/discord_game_sdk.dll
Normal file
BIN
libs/discordGameSDK/lib/x86/discord_game_sdk.dll.lib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.bundle
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dll.lib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.dylib
Normal file
BIN
libs/discordGameSDK/lib/x86_64/discord_game_sdk.so
Normal file
@@ -24,6 +24,59 @@ if osname == "Linux" then
|
||||
elseif osname == "OS X" then
|
||||
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
|
||||
|
||||
138
libs/simple-slider.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
--[[
|
||||
Copyright (c) 2016 George Prosser
|
||||
|
||||
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.
|
||||
]]
|
||||
|
||||
local slider = {}
|
||||
slider.__index = slider
|
||||
|
||||
function newSlider(x, y, length, value, min, max, setter, style)
|
||||
local s = {}
|
||||
s.value = (value - min) / (max - min)
|
||||
s.min = min
|
||||
s.max = max
|
||||
s.setter = setter
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.length = length
|
||||
|
||||
local p = style or {}
|
||||
s.width = p.width or length * 0.1
|
||||
s.orientation = p.orientation or 'horizontal'
|
||||
s.track = p.track or 'rectangle'
|
||||
s.knob = p.knob or 'rectangle'
|
||||
|
||||
s.grabbed = false
|
||||
s.wasDown = true
|
||||
s.ox = 0
|
||||
s.oy = 0
|
||||
|
||||
return setmetatable(s, slider)
|
||||
end
|
||||
|
||||
function slider:update(mouseX, mouseY, mouseDown)
|
||||
local x = mouseX or love.mouse.getX()
|
||||
local y = mouseY or love.mouse.getY()
|
||||
local down = love.mouse.isDown(1)
|
||||
if mouseDown ~= nil then
|
||||
down = mouseDown
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
local ox = x - knobX
|
||||
local oy = y - knobY
|
||||
|
||||
local dx = ox - self.ox
|
||||
local dy = oy - self.oy
|
||||
|
||||
if down then
|
||||
if self.grabbed then
|
||||
if self.orientation == 'horizontal' then
|
||||
self.value = self.value + dx / self.length
|
||||
elseif self.orientation == 'vertical' then
|
||||
self.value = self.value - dy / self.length
|
||||
end
|
||||
elseif (x > knobX - self.width/2 and x < knobX + self.width/2 and y > knobY - self.width/2 and y < knobY + self.width/2) and not self.wasDown then
|
||||
self.ox = ox
|
||||
self.oy = oy
|
||||
self.grabbed = true
|
||||
end
|
||||
else
|
||||
self.grabbed = false
|
||||
end
|
||||
|
||||
self.value = math.max(0, math.min(1, self.value))
|
||||
|
||||
if self.setter ~= nil then
|
||||
self.setter(self.min + self.value * (self.max - self.min))
|
||||
end
|
||||
|
||||
self.wasDown = down
|
||||
end
|
||||
|
||||
function slider:draw()
|
||||
if self.track == 'rectangle' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width)
|
||||
end
|
||||
elseif self.track == 'line' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.line(self.x - self.length/2, self.y, self.x + self.length/2, self.y)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.line(self.x, self.y - self.length/2, self.x, self.y + self.length/2)
|
||||
end
|
||||
elseif self.track == 'roundrect' then
|
||||
if self.orientation == 'horizontal' then
|
||||
love.graphics.rectangle('line', self.x - self.length/2 - self.width/2, self.y - self.width/2, self.length + self.width, self.width, self.width/2, self.width)
|
||||
elseif self.orientation == 'vertical' then
|
||||
love.graphics.rectangle('line', self.x - self.width/2, self.y - self.length/2 - self.width/2, self.width, self.length + self.width, self.width, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
local knobX = self.x
|
||||
local knobY = self.y
|
||||
if self.orientation == 'horizontal' then
|
||||
knobX = self.x - self.length/2 + self.length * self.value
|
||||
elseif self.orientation == 'vertical' then
|
||||
knobY = self.y + self.length/2 - self.length * self.value
|
||||
end
|
||||
|
||||
if self.knob == 'rectangle' then
|
||||
love.graphics.rectangle('fill', knobX - self.width/2, knobY - self.width/2, self.width, self.width)
|
||||
elseif self.knob == 'circle' then
|
||||
love.graphics.circle('fill', knobX, knobY, self.width/2)
|
||||
end
|
||||
end
|
||||
|
||||
function slider:getValue()
|
||||
return self.min + self.value * (self.max - self.min)
|
||||
end
|
||||
47
load/bgm.lua
@@ -6,34 +6,45 @@ bgm = {
|
||||
}
|
||||
|
||||
local current_bgm = nil
|
||||
local bgm_locked = true
|
||||
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
|
||||
@@ -47,19 +58,21 @@ function fadeoutBGM(time)
|
||||
end
|
||||
|
||||
function resetBGMFadeout(time)
|
||||
current_bgm:setVolume(1)
|
||||
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 / 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
@@ -0,0 +1,2 @@
|
||||
bigint = require "libs.bigint.bigint"
|
||||
number_names = require "libs.bigint.named-powers-of-ten"
|
||||
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
@@ -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,29 +1,116 @@
|
||||
backgrounds = {
|
||||
[0] = love.graphics.newImage("res/backgrounds/0-quantum-foam.png"),
|
||||
love.graphics.newImage("res/backgrounds/100-big-bang.png"),
|
||||
love.graphics.newImage("res/backgrounds/200-spiral-galaxy.png"),
|
||||
love.graphics.newImage("res/backgrounds/300-sun-and-dust.png"),
|
||||
love.graphics.newImage("res/backgrounds/400-earth-and-moon.png"),
|
||||
love.graphics.newImage("res/backgrounds/500-cambrian-explosion.png"),
|
||||
love.graphics.newImage("res/backgrounds/600-dinosaurs.png"),
|
||||
love.graphics.newImage("res/backgrounds/700-asteroid.png"),
|
||||
love.graphics.newImage("res/backgrounds/800-human-fire.png"),
|
||||
love.graphics.newImage("res/backgrounds/900-early-civilization.png"),
|
||||
love.graphics.newImage("res/backgrounds/1000-vikings.png"),
|
||||
love.graphics.newImage("res/backgrounds/1100-crusades.png"),
|
||||
love.graphics.newImage("res/backgrounds/1200-genghis-khan.png"),
|
||||
love.graphics.newImage("res/backgrounds/1300-black-death.png"),
|
||||
love.graphics.newImage("res/backgrounds/1400-columbus-discovery.png"),
|
||||
love.graphics.newImage("res/backgrounds/1500-aztecas.png"),
|
||||
love.graphics.newImage("res/backgrounds/1600-telescope.png"),
|
||||
love.graphics.newImage("res/backgrounds/1700-american-revolution.png"),
|
||||
love.graphics.newImage("res/backgrounds/1800-railways.png"),
|
||||
love.graphics.newImage("res/backgrounds/1900-world-wide-web.png"),
|
||||
title = love.graphics.newImage("res/backgrounds/title_v0.1.png"),
|
||||
input_config = love.graphics.newImage("res/backgrounds/options-pcb.png"),
|
||||
game_config = love.graphics.newImage("res/backgrounds/options-gears.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"),
|
||||
@@ -33,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"),
|
||||
@@ -45,9 +134,31 @@ 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"),
|
||||
},
|
||||
["gem"] = {
|
||||
R = love.graphics.newImage("res/img/gem1.png"),
|
||||
O = love.graphics.newImage("res/img/gem3.png"),
|
||||
Y = love.graphics.newImage("res/img/gem7.png"),
|
||||
G = love.graphics.newImage("res/img/gem6.png"),
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +180,7 @@ ColourSchemes = {
|
||||
Z = "R",
|
||||
O = "Y",
|
||||
T = "M",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for name, blockset in pairs(blocks) do
|
||||
@@ -84,4 +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"),
|
||||
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
|
||||
|
||||
125
load/sounds.lua
@@ -1,55 +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"),
|
||||
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 = "res/se/welcomeToCambridge.wav",
|
||||
}
|
||||
|
||||
function playSE(sound, subsound)
|
||||
if subsound == nil then
|
||||
sounds[sound]:setVolume(0.5)
|
||||
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(0.1)
|
||||
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(0.5)
|
||||
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(0.5)
|
||||
if sounds[sound][subsound]:isPlaying() then
|
||||
return
|
||||
end
|
||||
sounds[sound][subsound]:play()
|
||||
end
|
||||
end
|
||||
1
load/version.lua
Normal file
@@ -0,0 +1 @@
|
||||
version = "v0.3.4"
|
||||
270
main.lua
@@ -1,51 +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["side_next"] = false
|
||||
--config["reverse_rotate"] = true
|
||||
--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.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
|
||||
-- aliasing to prevent people using math.random by accident
|
||||
math.random = love.math.random
|
||||
math.randomseed = love.math.setRandomSeed
|
||||
math.randomseed(os.time())
|
||||
|
||||
-- init config
|
||||
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
|
||||
@@ -55,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)
|
||||
@@ -109,18 +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", "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
|
||||
@@ -191,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})
|
||||
@@ -205,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
|
||||
@@ -221,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 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/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
|
||||
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
|
||||
zip dist/cambridge-win32.zip dist/win32/* SOURCES.md LICENSE.md
|
||||
cp cambridge.love dist/other/
|
||||
zip dist/cambridge-other.zip cambridge.love libs/discord-rpc.* SOURCES.md LICENSE.md
|
||||
10
release.bat
@@ -5,17 +5,23 @@ mkdir dist\windows
|
||||
mkdir dist\windows\libs
|
||||
mkdir dist\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 ..\..
|
||||
0
res/backgrounds/0-quantum-foam.png → res/backgrounds/0.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
0
res/backgrounds/100-big-bang.png → res/backgrounds/100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
0
res/backgrounds/1000-vikings.png → res/backgrounds/1000.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
0
res/backgrounds/1100-crusades.png → res/backgrounds/1100.png
Executable file → Normal file
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |