Compare commits
822 commits
mars_speci
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 77d359fa52 | |||
| c5b8eb4f49 | |||
| 810770564e | |||
| bb0c3bb178 | |||
| 056a855a32 | |||
| 9c76feb150 | |||
| 379b1e53a0 | |||
| 940c2bb07e | |||
| cd87a325d0 | |||
| 44d4deca22 | |||
| e3030416ce | |||
| f251b1b07f | |||
| c0a60cbdc8 | |||
| a9094975be | |||
| 99978c3241 | |||
| 5a6975b3f4 | |||
| 0cc148f653 | |||
| e5d356e1fc | |||
| 500646bfe9 | |||
| aeb0f4a799 | |||
| 68aedbf2fa | |||
| 03af0a7099 | |||
| 3a22c74ce5 | |||
| 892662307f | |||
| 0731dd9216 | |||
| 074cbb18ba | |||
| b3cb60371e | |||
| 2d9355f829 | |||
| 5b98171283 | |||
| c097420a50 | |||
| 1044443e9b | |||
| dd8781a1b2 | |||
| 005008b720 | |||
| 2a486c6d8f | |||
| d1a3bf15d4 | |||
| c3f48fdbc0 | |||
| 2f781525d7 | |||
| 339de6b218 | |||
| 0e813c3307 | |||
| 23f153c38d | |||
| f88d158b35 | |||
| 02817be114 | |||
| 69a2a04811 | |||
| 9e4a96f622 | |||
| 253eda65ce | |||
| e3cbbb5b47 | |||
| 2549d248a5 | |||
| 8832700e25 | |||
| a5acb9534b | |||
| a8e08bf6d3 | |||
| 4b314d78c4 | |||
| 62f2adcb82 | |||
| 48d756bdd5 | |||
| 7c398e28ff | |||
| 774620c673 | |||
| 17d857ecf2 | |||
| e40467a8d1 | |||
| b99a4da18f | |||
| e23455356d | |||
| 64ffe27c2a | |||
| 615ee51229 | |||
| 78e9731d8d | |||
| b1ea7bc763 | |||
| 424bf5f533 | |||
| 4a4f680d0a | |||
| 8d25721d35 | |||
| 7c5062e081 | |||
| 7300679f56 | |||
| 8a739bf637 | |||
| 7beba986ae | |||
| a42d025ffb | |||
| be372ee007 | |||
| 754b279b1a | |||
| 08f62bdaf7 | |||
| 3b88908534 | |||
| 3b89037c25 | |||
| 3bb8bd36f3 | |||
| 1b548f1ba3 | |||
| 5c0d4d8d29 | |||
| 0210b61540 | |||
| 56d23480b0 | |||
| 059b2b1539 | |||
| 5e7a9b7697 | |||
| 3e4d4f39ea | |||
| 8784938e97 | |||
| 78ea54ac2d | |||
| 2cce0082cf | |||
| 905e98070c | |||
| d2f35276bf | |||
| fa214ed956 | |||
| c7b1bc0b0a | |||
| b83a3635fc | |||
| b1255f7a14 | |||
| 46f672599d | |||
| d3366b56aa | |||
| 0e000b190d | |||
| 7b1ed4bd95 | |||
| e57b284ffc | |||
| 80daea54fa | |||
| 68ba3698f8 | |||
| ce829f7356 | |||
| 1f57c76036 | |||
| c3c8446431 | |||
| a7cbe69712 | |||
| d826940b4c | |||
| 14b18197bf | |||
| afd942af14 | |||
| 08510e265b | |||
| 24f3df6f35 | |||
| fde307e96f | |||
| cc031470b2 | |||
| a8e32e2d98 | |||
| 08ca6ab21b | |||
| 2f75df3c91 | |||
| 132a521b6a | |||
| 1d8ba361fc | |||
| d5abbb4762 | |||
| 8e89679219 | |||
| 93aa697347 | |||
| c75809aa75 | |||
| 6a8563cce9 | |||
| e4222f35ff | |||
| 73abc03ae2 | |||
| 7e2c2a4c6d | |||
| 6cf5eb008b | |||
| 9f0f07885d | |||
| 083e5841aa | |||
| 0034e6a9f1 | |||
| 944d1374a5 | |||
| 5fcd2a171f | |||
| 472d095e2d | |||
| c8a58d1de4 | |||
| 8099e928dc | |||
| 57f9f7331c | |||
| 6a8a896aba | |||
| e5811b61a0 | |||
| 42260d4d7d | |||
| 4dc8b6a183 | |||
| 2e28b3ffc6 | |||
| d41c53193d | |||
| ef85eec178 | |||
| dab7b6327e | |||
| 195a31fa1c | |||
| 12eed4dda5 | |||
| 4f4e86ce4f | |||
| 4c16594b3f | |||
| 85451a1d6f | |||
| 72694664de | |||
| 28a4d9ccb0 | |||
| 97c9d79a5a | |||
| 3655fc9c6f | |||
| cd31187d3f | |||
| a6268663da | |||
| fa9c5efaf0 | |||
| f75c04d988 | |||
| 4e2aa5f577 | |||
| 64d85dd1c0 | |||
| d4e3cc623a | |||
| cca966b33e | |||
| b01c594cba | |||
| adcf761243 | |||
| 6fc476f1f0 | |||
| 4890761b0a | |||
| 5587c43611 | |||
| 5155175d9b | |||
| 2c3565df61 | |||
| 7c15c1bb0d | |||
| 3940766f7d | |||
| b967af7ddd | |||
| a49be3b253 | |||
| 8a21e53ee1 | |||
| 20f3bef6bd | |||
| 073ac16223 | |||
| c59b28e13f | |||
| 89cbbaeeef | |||
| 6d4abb7c03 | |||
| e7482bb165 | |||
| bfa0491f39 | |||
| 95b6651fc3 | |||
| 9b3f2df2a0 | |||
| 80b4cc938d | |||
| fad1580379 | |||
| ad752f0ea1 | |||
| 3359aaa62d | |||
| daa2b9c49a | |||
| 6971b8545a | |||
| 5138e00c35 | |||
| fbb032fe63 | |||
| f986f0d4b1 | |||
| e6ae945b92 | |||
| ce8547c8d3 | |||
| b5d556d06f | |||
| 8b5c35f89a | |||
| 44672ac1fd | |||
| 95492efea7 | |||
| d66092b86d | |||
| 8617fac987 | |||
| 655dd2778f | |||
| fbf78f1dc4 | |||
| d032f93eb3 | |||
| 2bd4e5790b | |||
| 954068ba3a | |||
| ba8b33d49e | |||
| 7ada90e3d0 | |||
| 4e9a551452 | |||
| c3a859d358 | |||
| aca5048f6f | |||
| 88b8c2354f | |||
| ffc77e5fa3 | |||
| 17a0aef630 | |||
| 02bd1dc2f1 | |||
| 57aebfb42d | |||
| 3dde4d6b55 | |||
| 5042fbbf7e | |||
| 013212e582 | |||
| c16194c95c | |||
| a2c83633ae | |||
| 82f7d5d429 | |||
| 6368ca5e68 | |||
| 0568521879 | |||
| c350c8f911 | |||
| 241f3afde3 | |||
| 9273a6f538 | |||
| 3a24090607 | |||
| dbd479d413 | |||
| 4fde43313c | |||
| ddddfb67ce | |||
| 84905b6f05 | |||
| 9ce8082e4d | |||
| 2fad7e0349 | |||
| 820a184fa7 | |||
| 6089c02e90 | |||
| 5141643531 | |||
| f177f29a60 | |||
| 57027c837e | |||
| 7e96709dc1 | |||
| 47ec94641b | |||
| f69657c826 | |||
| 611dc959da | |||
| ab50b4c323 | |||
| 61626b9179 | |||
| a4d666096e | |||
| 0718282dda | |||
| c1c9e304da | |||
| 9843a0cf06 | |||
| 0cc04cdbb6 | |||
| d5b755bde0 | |||
| 61fd45d40c | |||
| 3ac46bc8dc | |||
| 9f063218bf | |||
| 367f82a01f | |||
| 7151fef7a8 | |||
| 13862d8b47 | |||
| 7346aff9f6 | |||
| 9234a28344 | |||
| ddaf2407a9 | |||
| 3856fdd827 | |||
| 8cc3356eb1 | |||
| d7b68e44a8 | |||
| b32a891183 | |||
| 1ed8b28633 | |||
| ae815ec538 | |||
| 546c0f2a7b | |||
| 018f3ae0b0 | |||
| 325dc01757 | |||
| d2ba791be1 | |||
| 403e24bf4c | |||
| 5df29246a5 | |||
| 058505e9f0 | |||
| 54fb90214f | |||
| 99ca3ec2eb | |||
| e3750707bc | |||
| e55a98c2a0 | |||
| b73a37deea | |||
| bae1fb8c08 | |||
| b8ad66732a | |||
| 17e23e41d4 | |||
| b290bfb063 | |||
| bced20ddc6 | |||
| 37a3d7f827 | |||
| bd202d08ae | |||
| 7218730e7c | |||
| 7537338bdb | |||
| 85031e0d59 | |||
| faacf6dbc0 | |||
| 4b9591885a | |||
| aa32b09fca | |||
| 6d13bfa407 | |||
| b9a6fbf99f | |||
| ea037acbc0 | |||
| 30877401a3 | |||
| 6c5815ff76 | |||
| 488290be4f | |||
| f8c52bcfe2 | |||
| 47b772a432 | |||
| ad36acf238 | |||
| 22a57f91db | |||
| e79266d7e2 | |||
| cbd99d1c4d | |||
| bd1393c505 | |||
| 68d4adcad2 | |||
| 7279a5b6f5 | |||
| 91c2dbca67 | |||
| 2dd9d1e22d | |||
| 44d30a6f8d | |||
| 100e2d6af8 | |||
| 7333bcd922 | |||
| fc18c75557 | |||
| 0bfedccc7b | |||
| c68418e868 | |||
| 283bc9efe5 | |||
| e1201e7913 | |||
| b5117ca8b2 | |||
| b04456066b | |||
| d4f08acf24 | |||
| 443fa3d8ca | |||
| 01b8bd3d59 | |||
| b9b18b800d | |||
| cedc29f39a | |||
| eab13e2ae5 | |||
| 6591dd1893 | |||
| 938aa688cc | |||
| 70b260ec50 | |||
| 933874eddf | |||
| d9935c7476 | |||
| c6f9fbd244 | |||
| 713a479eed | |||
| 1da8e341c5 | |||
| 2423547973 | |||
| 045f399245 | |||
| 1538fa002f | |||
| cae25f4719 | |||
| fffd2c70e6 | |||
| b436e7b2d8 | |||
| 6581143837 | |||
| cb4a8e836f | |||
| c18a8ba2c5 | |||
| 119a323b72 | |||
| 6770ce908f | |||
| b26849e689 | |||
| 83f92ae9cb | |||
| bede9c95f9 | |||
| 663f7bc5f4 | |||
| ff1c144f25 | |||
| a5812c02c8 | |||
| 52969c718a | |||
| 49eafc4806 | |||
| 1cc2c23602 | |||
| d7c9b484fe | |||
| 718d0c7d04 | |||
| 47d3156c57 | |||
| d179ef65a2 | |||
| a26826fc03 | |||
| 3187908435 | |||
| 448b6dc9fa | |||
| 153cc87986 | |||
| 645423ecba | |||
| 24a74b1208 | |||
| 49fe46bce2 | |||
| dbcffa8d4a | |||
| 646ef45acf | |||
| 05af18ba3f | |||
| bd3aa04922 | |||
| 4a17529e39 | |||
| 1ccf82c2c5 | |||
| d48f9c086f | |||
| 9b9af9e3bc | |||
| c7108a0e39 | |||
| 57aceed172 | |||
| ca2d106725 | |||
| 4ff36a61ba | |||
| d1104f9aac | |||
| 84ef3faa68 | |||
| 540d63ce5b | |||
| d09bd76a14 | |||
| e28a78166e | |||
| 740fab84be | |||
| 929e8091dc | |||
| 752593a589 | |||
| 04443b1e3a | |||
| 13a86a76c8 | |||
| 5533826711 | |||
| 92f02d8ac0 | |||
| 88677eb893 | |||
| 802bb1a74d | |||
| e6f5fc17a4 | |||
| 099a09e4d9 | |||
| 64a6713b61 | |||
| 368d1edd71 | |||
| 470b7a0757 | |||
| 4b1d513cd6 | |||
| f5bd285336 | |||
| 46a49ef42d | |||
| 8fcaef4ba1 | |||
| 080893ee96 | |||
| 5e64a4c216 | |||
| a36d25a34b | |||
| 451701726c | |||
| 1ad6bf50a6 | |||
| 880c61f789 | |||
| efbcf48d01 | |||
| c3d964fafd | |||
| 00231ce3f6 | |||
| 3e10b1e294 | |||
| 78a131952b | |||
| 8ff0adb710 | |||
| 0998da6329 | |||
| 7c122c5664 | |||
| 082b8e0493 | |||
| 0a088684d9 | |||
| d943f14185 | |||
| 97ee3c1134 | |||
| 407df9a21d | |||
| 7071f3363d | |||
| 2d8be7560b | |||
| e8b9d18d05 | |||
| 818db07676 | |||
| c0b485ae4b | |||
| 20b584b5e0 | |||
| 4370bf608f | |||
| e34b187235 | |||
| e0281cd13f | |||
| ea71e1878e | |||
| cde91b55a9 | |||
| e52ab0d731 | |||
| 260d584df8 | |||
| 20a114eb41 | |||
| 095b06d5f1 | |||
| 5add6df083 | |||
| 66d6fa5c36 | |||
| 2af6a1000a | |||
| 1144d4a46c | |||
| 0555243990 | |||
| e2fdb777de | |||
| 7611d375dc | |||
| 05b7f2d560 | |||
| 2364c15120 | |||
| 5eef1e13f5 | |||
| 5e61832a9b | |||
| 94ea4cf932 | |||
| a85e72a5b6 | |||
| 028b4e1296 | |||
| 0b1522722c | |||
| bda4a156e7 | |||
| 3b5e649a31 | |||
| 9f86a8d71a | |||
| 5cb6421258 | |||
| b3df337b8f | |||
| 340a5f4254 | |||
| 08b14ded93 | |||
| eb87f1ea20 | |||
| 2a97a467aa | |||
| 6a13a06187 | |||
| 6833531edb | |||
| e1150a98fc | |||
| c7cad03f0a | |||
| 76e43590e9 | |||
| fa87ab4425 | |||
| 1a133f2d3e | |||
| 95b26d890e | |||
| d48bbc91a6 | |||
| bcae6dde4e | |||
| 0b1bd8cf2f | |||
| de9c695a7a | |||
| 30ae586b98 | |||
| 1b949c0b7f | |||
| 1c54dd85a5 | |||
| 6ff3351ab8 | |||
| ac0f45d9f0 | |||
| dca01b7cc4 | |||
| a8fc4ea741 | |||
| 8038da6987 | |||
| a8c89f84bd | |||
| 323b264b41 | |||
| 8d120dd407 | |||
| e31132face | |||
| 352d03ed3f | |||
| de1a454079 | |||
| f9dda53c61 | |||
| 1584301a21 | |||
| 340a04d17c | |||
| 721c88fbee | |||
| eddda6d787 | |||
| f1c0959fba | |||
| 5fa8e4a3a3 | |||
| f743201565 | |||
| 03ae98d8a9 | |||
| 52b18c07a4 | |||
| 51a5bcd3bc | |||
| 9e0babfd2c | |||
| 76c91e50c1 | |||
| 9a800b8f7a | |||
| 9cbb9127b9 | |||
| 831dd7b688 | |||
| 8be3544295 | |||
| 424ed06ecb | |||
| 8c8ede7587 | |||
| 8a437086d6 | |||
| 27a9c9ee7b | |||
| 6e366cea16 | |||
| 85a6ade2cb | |||
| e16ebb8822 | |||
| 85c0c56e03 | |||
| f46b3bdc40 | |||
| 7183a441e5 | |||
| bc63d98d51 | |||
| 8b1157c641 | |||
| 59c803d31c | |||
| 1d20bd5a17 | |||
| 24d1f1e5bf | |||
| 5a1db3097b | |||
| d2604bc5ce | |||
| 31ffd4dc54 | |||
| 809a0ec212 | |||
| 183644d46f | |||
| 8adfe7320b | |||
| 1e74639ff4 | |||
| dbeee752b7 | |||
| a27378a3a7 | |||
| d3fa7fdfea | |||
| 6bab38a545 | |||
| 3701df15f4 | |||
| d7babe6a0f | |||
| b99371dc29 | |||
| c0668e2de2 | |||
| 72508eb445 | |||
| 0b562a26c1 | |||
| 0f621a9f3f | |||
| fee1f647b2 | |||
| 2d2362d85c | |||
| 8a7891d561 | |||
| 9f36dfc0e4 | |||
| c59a28433a | |||
| 82682c0317 | |||
| c1991cbb57 | |||
| e0e0df8128 | |||
| c6805dea21 | |||
| 535627f962 | |||
| d1975dd917 | |||
| 63008d62ff | |||
| 190fd5e51e | |||
| 79e90fb03c | |||
| c35478ac88 | |||
| 2667d1aadb | |||
| 981629cb74 | |||
| 58215fedc1 | |||
| f27a4fa26d | |||
| 16dcbe9603 | |||
| 1036ebae30 | |||
| 67c7ce0878 | |||
| b467af74c9 | |||
| 8b01b6684c | |||
| 80c243ee2f | |||
| 3b00182f63 | |||
| 5eb7cbff92 | |||
| f6377045c9 | |||
| 04243db779 | |||
| 85d7a0ca33 | |||
| 2bff6d6cf3 | |||
| 248bd049db | |||
| 128abc453e | |||
| dd12ad9477 | |||
| 1c22892eec | |||
| 56c14e6111 | |||
| d13c097d61 | |||
| 95e99e4104 | |||
| 5630ae2da3 | |||
| 0019063c4e | |||
| 64672d3f1f | |||
| 564a0001a2 | |||
| 644c9ca5b6 | |||
| 02ecca5ebc | |||
| 4ce09fc6b0 | |||
| de5c0a3371 | |||
| 0836cf00a6 | |||
| d473ef3ea4 | |||
| 7f5d50787d | |||
| 6ee75f8bae | |||
| bd826103d3 | |||
| f5f094bdc6 | |||
| a4a07c0d29 | |||
| 810f64ad76 | |||
| 2e569b2cd3 | |||
| b33ef168ea | |||
| 54b78e6c66 | |||
| 93d5c55c68 | |||
| bf90a8edd9 | |||
| e37bdf6bd2 | |||
| a4e7b318ed | |||
| a1b00d4a70 | |||
| ceb69b9d62 | |||
| b910d16261 | |||
| 42f986ce4a | |||
| ffe7afff1f | |||
| 9e65c02541 | |||
| 2ea0427b0f | |||
| b9ed814cfe | |||
| 33488e67ca | |||
| 3570cb4fea | |||
| 703ba37394 | |||
| 168cca63e3 | |||
| 698bd2a60b | |||
|
|
bc94fe256d | ||
| 4befa1cad7 | |||
| d7c8d8710c | |||
| 1bd3f30be4 | |||
| f70cdf9d8e | |||
|
|
ec18266d9e | ||
| 2e2df09790 | |||
| 95dc09d7a3 | |||
| 18c49f7740 | |||
| 93e5691d8a | |||
| 769b5786ae | |||
| e4fcf82f70 | |||
| 48a85dfd66 | |||
| 1770a83bdc | |||
| 1d35ed2abb | |||
| e80d831d10 | |||
| ab20daf5ce | |||
| f824fd6612 | |||
| b5cea86b46 | |||
| 0791aa1096 | |||
| eb55ad5644 | |||
| 30ed7a781e | |||
| 1ef17e8918 | |||
| cc4b2c645b | |||
| 933734bd2e | |||
| 5895c01c4c | |||
| 6f6a263553 | |||
| aae4fa238f | |||
| 0675585a53 | |||
| 4b223c5ed1 | |||
| dc6335a4ab | |||
| e8ea3e0fd4 | |||
| 2c13bcac15 | |||
| d2afa09adf | |||
| 343cb394b5 | |||
| cddc8a3235 | |||
| 546db9fb98 | |||
| c3e8068668 | |||
| a1f06a57ca | |||
| 327e4b9744 | |||
| aeec694443 | |||
| 1246985763 | |||
| e21692743c | |||
| 9f0179a86a | |||
| b0814989e1 | |||
| 53bfbb0cd0 | |||
| b21332b9a4 | |||
| 4b59aa619d | |||
| 3c4ccbbf54 | |||
| df5e188236 | |||
| 3bfe867cc0 | |||
| 7ae3bbfe96 | |||
| 7ad2c92d31 | |||
| 21e35a988a | |||
| fcd86fb359 | |||
| 063e8e43ba | |||
| c26f626baf | |||
| 1561162cf8 | |||
| 0c2c950a8c | |||
| d930ef6d93 | |||
| 3bb0b53daf | |||
| 0b0be3539f | |||
| d824159c2f | |||
| d68242d59d | |||
| 98300b2f64 | |||
| 0633aacc63 | |||
| ef6ef081d8 | |||
| b52039d47c | |||
| cac70befe5 | |||
| 23f818078f | |||
| c639e533fe | |||
| d27d4cca17 | |||
| 20574e23e3 | |||
| 73711b8ffa | |||
| cc7135a0f4 | |||
| ac436cb155 | |||
| 8b456ce9e9 | |||
| 32da717a55 | |||
| 696ed71090 | |||
| 6d5a01921c | |||
| 11af452b16 | |||
| 84b804adf7 | |||
| d90317576c | |||
| 9b304ead00 | |||
| 15b08aeb47 | |||
| aa61c63609 | |||
| 10ddf4933b | |||
| 43e95e89f3 | |||
| 4cb86d9314 | |||
| ab13864021 | |||
| 5f63fb76f5 | |||
| b7252f8746 | |||
| 0979b55f72 | |||
| 1a06f5071d | |||
| 5e6cd060d7 | |||
| 40c62d621a | |||
| e304b09f9f | |||
| 1f1612f7e6 | |||
| 519c487c4d | |||
| 417c6f0080 | |||
| ed1764ffea | |||
| 7c20a91546 | |||
| a2922708e8 | |||
| 1582b341d3 | |||
| 0e84751ac9 | |||
| ce1809e50d | |||
| 5ce8647b3d | |||
| fdcf0d5b88 | |||
| 8e286a2cc0 | |||
| f6d9b14f38 | |||
| 4c5d8777d6 | |||
| 3ed7f142d7 | |||
| e83df0d965 | |||
| 63f576aee5 | |||
| 441db52e71 | |||
| 437714288b | |||
| 514f4006ef | |||
| f3700dc4b8 | |||
| e51fc4029d | |||
| 292ccadc7e | |||
| 5e93208fcb | |||
| a279fbfcb8 | |||
| 104d4a92a0 | |||
| 56505ebe85 | |||
| 3b5ad89ba0 | |||
| cbd5a15ce8 | |||
| a67e00d601 | |||
| 633e366650 | |||
| 4db40a0276 | |||
| 2209648999 | |||
| 7376109e96 | |||
| 148efb02cc | |||
| df9d40540b | |||
| f9e8b76029 | |||
| c980e72e14 | |||
| 13590ff9e5 | |||
| e19b515404 | |||
| 630ae28301 | |||
| ef95af60ae | |||
| 0d23d8bcab | |||
| 3c22074f8d | |||
| 098c3e84b1 | |||
| 9a5206ebf9 | |||
| b9939ba10e | |||
| 2906e31634 | |||
| 3f5735e10f | |||
| 0b358f6877 | |||
| dd38061d1c | |||
| 1241fdff9c | |||
| bf3f6f61d1 | |||
| ea726c7a93 | |||
| 1b8339ed5d | |||
| 361ef9fd7a | |||
| 874d4bd5c8 | |||
| c928b88062 | |||
| cdbe162521 | |||
| 42e1136123 | |||
| 9e5b6997f6 | |||
| 594dcc9230 | |||
| d60eb6f659 | |||
| 267304ce6a | |||
| 0a64bd0cbb | |||
| b1063f83d9 | |||
| f31e97edde | |||
| 9481997034 | |||
| 9c70c8e19e | |||
| c43aa72334 | |||
| c0c659ee61 | |||
| 8425900fe0 | |||
| 9600bd96d5 | |||
| 5a0469364f | |||
| ee9847a904 | |||
| eb33aa3b91 | |||
| e1d4608e3f | |||
| d6f53ed76f | |||
| 64425e4cc9 | |||
| 2349882abc | |||
| b247566b09 | |||
| bd9e148d9b | |||
| a63bc2eb01 | |||
| 72cbec959d | |||
| d692256580 | |||
| 17bb412140 | |||
| 8e0c27c8c0 | |||
| 5af0c66599 | |||
| 932e64a991 | |||
| 5bf544d032 | |||
| 183e5bb482 | |||
| 79eceaa616 | |||
| 6fe5d72415 | |||
| cf13025cc6 | |||
| c1699fe882 | |||
| 852a170c97 | |||
| 9ab72a7c1a | |||
| 1c9fb28ebf | |||
| f42b91a80e | |||
| e28b7a8e67 | |||
| a1307b84b9 | |||
| 3a93ddce65 | |||
| d5fdb2a7c5 | |||
| 7d55b374e4 | |||
| 3d2c03c1b4 | |||
| 83cb71d2c0 | |||
| 83559aba51 | |||
| a7d331544c | |||
| b00e59ab08 | |||
| 87c612a0eb | |||
| 5f7da5ddc5 | |||
| e959c0ec1d | |||
| 1198653891 | |||
| 3f382651c4 | |||
| d01a8186d8 | |||
| 9f045c18a8 | |||
| 30c35fd768 | |||
| 592fb437e7 | |||
| 8aec37d1de | |||
| 11e46ba1c6 | |||
| 0434a7d904 | |||
| f006ecb364 | |||
| b0bd606727 |
735 changed files with 60053 additions and 9001 deletions
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[submodule "3rdparty/rapidjson"]
|
||||
path = 3rdparty/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson.git
|
||||
[submodule "3rdparty/libbacktrace"]
|
||||
path = 3rdparty/libbacktrace
|
||||
url = https://github.com/ianlancetaylor/libbacktrace.git
|
||||
32
3rdparty/CMakeLists.txt
vendored
Normal file
32
3rdparty/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
file(GLOB_RECURSE RAPIDJSON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/rapidjson/include/*")
|
||||
add_library(rapidjson INTERFACE EXCLUDE_FROM_ALL "${RAPIDJSON_SOURCES}")
|
||||
target_include_directories(rapidjson INTERFACE "${CMAKE_CURRENT_LIST_DIR}/rapidjson/include")
|
||||
|
||||
if(PSEMEK_STACKTRACE)
|
||||
set(LIBBACKTRACE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/libbacktrace")
|
||||
set(LIBBACKTRACE_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/libbacktrace")
|
||||
set(LIBBACKTRACE_INCLUDE_DIR "${LIBBACKTRACE_BUILD_DIR}/include")
|
||||
set(LIBBACKTRACE_LIBRARY "${LIBBACKTRACE_BUILD_DIR}/.libs/libbacktrace.a")
|
||||
|
||||
if(NOT EXISTS "${LIBBACKTRACE_BUILD_DIR}")
|
||||
file(COPY "${LIBBACKTRACE_SOURCE_DIR}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
make_directory("${LIBBACKTRACE_INCLUDE_DIR}")
|
||||
file(COPY "${LIBBACKTRACE_SOURCE_DIR}/backtrace.h" DESTINATION "${LIBBACKTRACE_INCLUDE_DIR}")
|
||||
if(PSEMEK_PACKAGE_TARGET AND WIN32)
|
||||
execute_process(COMMAND "./configure" "--host=x86_64-w64-mingw32" "CC=${CMAKE_C_COMPILER}" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" COMMAND_ECHO STDOUT)
|
||||
elseif(PSEMEK_PACKAGE_TARGET AND ANDROID)
|
||||
execute_process(COMMAND "./configure" "--host=aarch64-none-linux-android34" "CC=${CMAKE_C_COMPILER}" "CFLAGS=--target=aarch64-none-linux-android34" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" COMMAND_ECHO STDOUT)
|
||||
else()
|
||||
execute_process(COMMAND "./configure" "CC=${CMAKE_C_COMPILER}" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" COMMAND_ECHO STDOUT)
|
||||
endif()
|
||||
execute_process(COMMAND "make" WORKING_DIRECTORY "${LIBBACKTRACE_BUILD_DIR}" RESULT_VARIABLE LIBBACKTRACE_BUILD_RESULT COMMAND_ECHO STDOUT)
|
||||
if(NOT (${LIBBACKTRACE_BUILD_RESULT} EQUAL 0))
|
||||
message(FATAL_ERROR "libbacktrace build failed")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(libbacktrace INTERFACE)
|
||||
target_include_directories(libbacktrace INTERFACE "${LIBBACKTRACE_INCLUDE_DIR}")
|
||||
target_link_libraries(libbacktrace INTERFACE "${LIBBACKTRACE_LIBRARY}")
|
||||
endif()
|
||||
|
||||
1
3rdparty/libbacktrace
vendored
Submodule
1
3rdparty/libbacktrace
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 9ae4f4ae4481b1e69d38ed810980d33103544613
|
||||
1
3rdparty/rapidjson
vendored
Submodule
1
3rdparty/rapidjson
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 80b6d1c83402a5785c486603c5611923159d0894
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.8)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(psemek)
|
||||
|
||||
cmake_policy(SET CMP0077 NEW)
|
||||
|
|
@ -18,6 +18,11 @@ if(PSEMEK_ROBUST_PREDICATES)
|
|||
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_ROBUST_PREDICATES=1")
|
||||
endif()
|
||||
|
||||
option(PSEMEK_USE_FREETYPE "Include Freetype fonts support" OFF)
|
||||
if(PSEMEK_USE_FREETYPE)
|
||||
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_USE_FREETYPE=1")
|
||||
endif()
|
||||
|
||||
set(PSEMEK_CXX_FLAGS)
|
||||
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"))
|
||||
list(APPEND PSEMEK_CXX_FLAGS -Wall -Werror -Wextra -pedantic -Wno-narrowing -Wno-sign-compare)
|
||||
|
|
@ -25,27 +30,59 @@ endif()
|
|||
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
|
||||
# gcc gives way too many false positives on -Wmaybe-uninitialized
|
||||
list(APPEND PSEMEK_CXX_FLAGS -Wno-maybe-uninitialized)
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13)
|
||||
# gcc-13 gives false positive -Wdangling-reference in ecs::accessor::get()
|
||||
list(APPEND PSEMEK_CXX_FLAGS -Wno-dangling-reference)
|
||||
endif()
|
||||
endif()
|
||||
if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))
|
||||
# -Wdtor-name gives false-positive on audio::engine::impl::~impl
|
||||
list(APPEND PSEMEK_CXX_FLAGS -Wno-dtor-name)
|
||||
endif()
|
||||
|
||||
option(PSEMEK_ASAN "Turn on Address Sanitizer" OFF)
|
||||
if(PSEMEK_ASAN)
|
||||
list(APPEND CMAKE_CXX_FLAGS -fsanitize=address)
|
||||
set(PSEMEK_BACKEND "SDL2" CACHE STRING "Application backend (OFF/SDL2/ANDROID)")
|
||||
option(PSEMEK_LEGACY_UI "Use legacy UI library" OFF)
|
||||
|
||||
message(STATUS "Using backend ${PSEMEK_BACKEND}")
|
||||
|
||||
if(PSEMEK_BACKEND STREQUAL "ANDROID")
|
||||
set(PSEMEK_GL_API gles32)
|
||||
set(PSEMEK_GL_LIBRARIES GLESv3 EGL)
|
||||
set(PSEMEK_PACKAGE_AS_LIBRARY ON CACHE INTERNAL "Build applications as shared libraries")
|
||||
list(APPEND PSEMEK_CXX_FLAGS -fPIC)
|
||||
else()
|
||||
find_package(Threads REQUIRED)
|
||||
set(OpenGL_GL_PREFERENCE LEGACY)
|
||||
find_package(OpenGL REQUIRED)
|
||||
set(PSEMEK_GL_API gl33)
|
||||
set(PSEMEK_GL_LIBRARIES OpenGL::GL)
|
||||
set(PSEMEK_PACKAGE_AS_LIBRARY OFF CACHE INTERNAL "Build applications as shared libraries")
|
||||
endif()
|
||||
|
||||
option(PSEMEK_UBSAN "Turn on UB Sanitizer" OFF)
|
||||
if(PSEMEK_UBSAN)
|
||||
list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined)
|
||||
if(NOT DEFINED PSEMEK_GRAPHICS_API)
|
||||
set(PSEMEK_GRAPHICS_API OPENGL)
|
||||
endif()
|
||||
|
||||
if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU)
|
||||
find_package(wgpu-native 27.0.2.0 REQUIRED)
|
||||
endif()
|
||||
|
||||
message(STATUS "Using graphics API ${PSEMEK_GRAPHICS_API}")
|
||||
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_GRAPHICS_API_${PSEMEK_GRAPHICS_API}=1")
|
||||
|
||||
option(PSEMEK_STACKTRACE "Use Boost.Stacktrace for stacktraces in exceptions" ON)
|
||||
|
||||
if(PSEMEK_STACKTRACE)
|
||||
list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_STACKTRACE=1")
|
||||
endif()
|
||||
|
||||
add_subdirectory(3rdparty)
|
||||
|
||||
get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY)
|
||||
|
||||
add_subdirectory(package)
|
||||
|
||||
if(PSEMEK_PACKAGE_MODE)
|
||||
if(PSEMEK_PACKAGE_MODE AND PSEMEK_PACKAGE_TARGET AND NOT PSEMEK_PACKAGE_AS_LIBRARY)
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
29
LICENSE.txt
Normal file
29
LICENSE.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
MIT NON-AI NON-NFT License
|
||||
|
||||
Copyright (c) 2025, Nikita Lisitsa
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the 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.
|
||||
|
||||
In addition, the following restrictions apply:
|
||||
|
||||
1. The Software, any modifications made to it, any artifacts or media generated by it, and any parts thereof, including but not limited to images,
|
||||
video, and code snippets, may not be used for the purpose of training or improving machine learning algorithms, or be included in any dataset used
|
||||
for training or improving machine learning algorithms, including but not limited to artificial intelligence, natural language processing, large
|
||||
language models, or data mining. This condition applies to any derivatives, modifications, or updates based on the Software code. Any usage of the
|
||||
Software in an AI-training dataset is considered a breach of this License.
|
||||
|
||||
2. The Software, any modifications made to it, any artifacts or media generated by it, and any parts thereof, including but not limited to images,
|
||||
video, and code snippets, may not be owned via a blockchain-backed ownership token, or as a means of generating blockchain-backed ownership tokens,
|
||||
including but not limited to non-fungible tokens. Any usage of the Software to generate non-fungible tokens is considered a breach of this License.
|
||||
|
||||
3. Any person or organization found to be in violation of these restrictions will be subject to legal action and may be held liable for any damages
|
||||
resulting from such use.
|
||||
|
||||
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.
|
||||
29
cmake/modules/Findwgpu-native.cmake
Normal file
29
cmake/modules/Findwgpu-native.cmake
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
if(wgpu-native_FOUND)
|
||||
set(wgpu-native_FIND_QUIETLY TRUE)
|
||||
endif()
|
||||
|
||||
# Don't search for include files - these are bundled with psemek-wgpu lib
|
||||
find_library(wgpu-native_LIBRARY NAMES libwgpu_native.a wgpu_native.dll wgpu_native PATHS "${WGPU_NATIVE_ROOT}" "${WGPU_NATIVE_ROOT}/lib")
|
||||
find_file(wgpu-native_VERSION_FILE wgpu-native-git-tag PATHS "${WGPU_NATIVE_ROOT}/wgpu-native-meta")
|
||||
|
||||
if(EXISTS "${wgpu-native_VERSION_FILE}")
|
||||
file(READ "${wgpu-native_VERSION_FILE}" wgpu-native_VERSION_NOT_STRIPPED)
|
||||
string(STRIP "${wgpu-native_VERSION_NOT_STRIPPED}" wgpu-native_VERSION_STRIPPED)
|
||||
string(SUBSTRING "${wgpu-native_VERSION_STRIPPED}" 1 -1 wgpu-native_VERSION)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(wgpu-native
|
||||
REQUIRED_VARS wgpu-native_LIBRARY wgpu-native_VERSION_FILE
|
||||
VERSION_VAR wgpu-native_VERSION
|
||||
)
|
||||
|
||||
if(wgpu-native_FOUND AND NOT TARGET wgpu-native)
|
||||
set(wgpu-native_LIBRARIES ${wgpu-native_LIBRARY})
|
||||
add_library(wgpu-native STATIC IMPORTED)
|
||||
set_target_properties(wgpu-native PROPERTIES
|
||||
IMPORTED_LOCATION "${wgpu-native_LIBRARIES}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(wgpu-native_LIBRARY wgpu-native_VERSION_FILE)
|
||||
|
|
@ -2,12 +2,17 @@ find_package(ZLIB REQUIRED)
|
|||
|
||||
file(GLOB PSEMEK_EXAMPLES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
|
||||
|
||||
find_package(OpenMP)
|
||||
|
||||
foreach(example ${PSEMEK_EXAMPLES})
|
||||
get_filename_component(TARGET_NAME "${example}" NAME_WLE)
|
||||
set(TARGET_NAME psemek-example-${TARGET_NAME})
|
||||
psemek_add_executable(${TARGET_NAME} ${example})
|
||||
psemek_add_application(${TARGET_NAME} ${example})
|
||||
if(TARGET ${TARGET_NAME})
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC psemek ZLIB::ZLIB)
|
||||
if(OpenMP_CXX_FOUND)
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC OpenMP::OpenMP_CXX)
|
||||
endif()
|
||||
target_compile_definitions(${TARGET_NAME} PUBLIC -DPSEMEK_EXAMPLES_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/gauss.hpp>
|
||||
#include <psemek/geom/distance.hpp>
|
||||
#include <psemek/geom/interval.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/gauss.hpp>
|
||||
#include <psemek/math/distance.hpp>
|
||||
#include <psemek/math/interval.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/rotation.hpp>
|
||||
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
|
|
@ -35,9 +35,9 @@ namespace
|
|||
|
||||
struct bone
|
||||
{
|
||||
geom::point<float, 2> position;
|
||||
geom::vector<float, 2> direction;
|
||||
geom::vector<float, 2> velocity;
|
||||
math::point<float, 2> position;
|
||||
math::vector<float, 2> direction;
|
||||
math::vector<float, 2> velocity;
|
||||
float angular_velocity;
|
||||
float length;
|
||||
float width;
|
||||
|
|
@ -51,16 +51,16 @@ struct joint
|
|||
float s0, s1;
|
||||
// angle range max should be in [0, 2*pi]
|
||||
// angle range min should be less or equal to angle range max
|
||||
std::optional<geom::interval<float>> angle_range = std::nullopt;
|
||||
std::optional<math::interval<float>> angle_range = std::nullopt;
|
||||
};
|
||||
|
||||
template <std::size_t Bones, std::size_t Constraints>
|
||||
struct constraint
|
||||
{
|
||||
std::size_t bone[Bones];
|
||||
geom::matrix<float, Constraints, 4 * Bones> jacobian;
|
||||
geom::vector<float, Constraints> bias;
|
||||
geom::interval<float> range = geom::interval<float>::full();
|
||||
math::matrix<float, Constraints, 4 * Bones> jacobian;
|
||||
math::vector<float, Constraints> bias;
|
||||
math::interval<float> range = math::interval<float>::full();
|
||||
};
|
||||
|
||||
struct system
|
||||
|
|
@ -74,7 +74,7 @@ struct system
|
|||
{
|
||||
std::size_t index;
|
||||
float pos;
|
||||
geom::vector<float, 2> delta;
|
||||
math::vector<float, 2> delta;
|
||||
};
|
||||
|
||||
void advance(float time, std::optional<selection> sel, util::function<std::vector<float>(system const &)> torque_provider);
|
||||
|
|
@ -105,7 +105,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
if (old_torque_.size() != joints.size())
|
||||
old_torque_.assign(joints.size(), 0.f);
|
||||
|
||||
geom::vector<float, 2> const gravity { 0.f, -10.f };
|
||||
math::vector<float, 2> const gravity { 0.f, -10.f };
|
||||
|
||||
for (std::size_t i = 0; i < bones.size(); ++i)
|
||||
{
|
||||
|
|
@ -138,7 +138,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
auto & b = bones[i];
|
||||
|
||||
b.position += b.velocity * dt;
|
||||
b.direction = geom::normalized(b.direction + geom::ort(b.direction) * b.angular_velocity * dt);
|
||||
b.direction = math::normalized(b.direction + math::ort(b.direction) * b.angular_velocity * dt);
|
||||
}
|
||||
|
||||
std::vector<constraint<1, 1>> constraints_1_1;
|
||||
|
|
@ -175,7 +175,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
float const bounce = 0.05f;
|
||||
float const friction = 0.05f;
|
||||
|
||||
auto push_1 = [&](float depth, float s, geom::vector<float, 2> const & v){
|
||||
auto push_1 = [&](float depth, float s, math::vector<float, 2> const & v){
|
||||
constraint<1, 1> & c = constraints_1_1.emplace_back();
|
||||
|
||||
c.bone[0] = i;
|
||||
|
|
@ -209,8 +209,8 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
auto p0 = b.position - b.direction * b.length / 2.f;
|
||||
auto p1 = b.position + b.direction * b.length / 2.f;
|
||||
|
||||
auto v0 = b.velocity - geom::ort(b.direction) * b.angular_velocity * b.length / 2.f;
|
||||
auto v1 = b.velocity + geom::ort(b.direction) * b.angular_velocity * b.length / 2.f;
|
||||
auto v0 = b.velocity - math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
|
||||
auto v1 = b.velocity + math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
|
||||
|
||||
if (p0[1] < b.width / 2.f)
|
||||
{
|
||||
|
|
@ -303,8 +303,8 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
|
||||
if (j.angle_range)
|
||||
{
|
||||
float x = geom::dot(b0.direction, b1.direction);
|
||||
float y = geom::det(b0.direction, b1.direction);
|
||||
float x = math::dot(b0.direction, b1.direction);
|
||||
float y = math::det(b0.direction, b1.direction);
|
||||
|
||||
// a \in [-pi, pi]
|
||||
float a = std::atan2(y, x);
|
||||
|
|
@ -314,10 +314,10 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
// j.angle_range->center() \in [-pi, 2*pi]
|
||||
// delta \in [-3*pi, 2*pi]
|
||||
float delta = a - j.angle_range->center();
|
||||
if (delta > geom::pi)
|
||||
delta -= 2.f * geom::pi;
|
||||
if (delta < -geom::pi)
|
||||
delta += 2.f * geom::pi;
|
||||
if (delta > math::pi)
|
||||
delta -= 2.f * math::pi;
|
||||
if (delta < -math::pi)
|
||||
delta += 2.f * math::pi;
|
||||
// delta in [-pi, pi]
|
||||
|
||||
float const half_length = j.angle_range->length() / 2.f;
|
||||
|
|
@ -380,7 +380,7 @@ void system::step(std::optional<selection> sel, util::function<std::vector<float
|
|||
template <std::size_t B, std::size_t C>
|
||||
void system::solve_constraint(constraint<B, C> const & c)
|
||||
{
|
||||
geom::vector<float, 4 * B> v;
|
||||
math::vector<float, 4 * B> v;
|
||||
auto j_inv_mass = c.jacobian;
|
||||
|
||||
for (std::size_t i = 0; i < B; ++i)
|
||||
|
|
@ -401,11 +401,11 @@ void system::solve_constraint(constraint<B, C> const & c)
|
|||
}
|
||||
}
|
||||
|
||||
geom::vector<float, C> l = *geom::solve(j_inv_mass * geom::transpose(c.jacobian), - c.jacobian * v - c.bias);
|
||||
math::vector<float, C> l = *math::solve(j_inv_mass * math::transpose(c.jacobian), - c.jacobian * v - c.bias);
|
||||
for (std::size_t j = 0; j < C; ++j)
|
||||
l[j] = geom::clamp(l[j], c.range);
|
||||
l[j] = math::clamp(l[j], c.range);
|
||||
|
||||
auto p = geom::transpose(j_inv_mass) * l;
|
||||
auto p = math::transpose(j_inv_mass) * l;
|
||||
|
||||
for (std::size_t i = 0; i < B; ++i)
|
||||
{
|
||||
|
|
@ -413,7 +413,7 @@ void system::solve_constraint(constraint<B, C> const & c)
|
|||
|
||||
b.velocity[0] += p[4 * i + 0];
|
||||
b.velocity[1] += p[4 * i + 1];
|
||||
b.angular_velocity += geom::det(b.direction, geom::vector{p[4 * i + 2], p[4 * i + 3]});
|
||||
b.angular_velocity += math::det(b.direction, math::vector{p[4 * i + 2], p[4 * i + 3]});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -433,15 +433,15 @@ struct controller
|
|||
|
||||
static constexpr float max_output = 20.f;
|
||||
|
||||
geom::matrix<float, outputs, inputs> m1;
|
||||
geom::vector<float, outputs> t1;
|
||||
math::matrix<float, outputs, inputs> m1;
|
||||
math::vector<float, outputs> t1;
|
||||
|
||||
// geom::matrix<float, layer1, inputs> m1;
|
||||
// geom::vector<float, layer1> t1;
|
||||
// geom::matrix<float, layer2, layer1> m2;
|
||||
// geom::vector<float, layer2> t2;
|
||||
// geom::matrix<float, layer3, layer2> m3;
|
||||
// geom::vector<float, layer3> t3;
|
||||
// math::matrix<float, layer1, inputs> m1;
|
||||
// math::vector<float, layer1> t1;
|
||||
// math::matrix<float, layer2, layer1> m2;
|
||||
// math::vector<float, layer2> t2;
|
||||
// math::matrix<float, layer3, layer2> m3;
|
||||
// math::vector<float, layer3> t3;
|
||||
|
||||
//Eigen::VectorXf to_eigen() const;
|
||||
//void from_eigen(Eigen::VectorXf const & v);
|
||||
|
|
@ -456,7 +456,7 @@ struct controller
|
|||
|
||||
void reset();
|
||||
|
||||
geom::vector<float, outputs> apply(system const & s) const;
|
||||
math::vector<float, outputs> apply(system const & s) const;
|
||||
|
||||
void read(std::istream & is);
|
||||
void write(std::ostream & os) const;
|
||||
|
|
@ -464,7 +464,7 @@ struct controller
|
|||
static float activation(float x);
|
||||
|
||||
template <std::size_t N>
|
||||
static geom::vector<float, N> activation(geom::vector<float, N> v);
|
||||
static math::vector<float, N> activation(math::vector<float, N> v);
|
||||
|
||||
template <typename T>
|
||||
static void read(std::istream & is, T & x);
|
||||
|
|
@ -562,14 +562,14 @@ void controller::mutate(RNG && rng, float amplitude)
|
|||
void controller::reset()
|
||||
{}
|
||||
|
||||
geom::vector<float, controller::outputs> controller::apply(system const & sys) const
|
||||
math::vector<float, controller::outputs> controller::apply(system const & sys) const
|
||||
{
|
||||
if (sys.bones.size() * 7 != inputs)
|
||||
throw std::runtime_error(util::to_string("Wrong number of inputs: should be ", sys.bones.size() * 7));
|
||||
if (sys.joints.size() != outputs)
|
||||
throw std::runtime_error(util::to_string("Wrong number of outputs: should be ", sys.joints.size()));
|
||||
|
||||
geom::vector<float, inputs> input;
|
||||
math::vector<float, inputs> input;
|
||||
for (std::size_t i = 0; i < inputs / 7; ++i)
|
||||
{
|
||||
input[7 * i + 0] = sys.bones[i].position[0] - sys.bones[0].position[0];
|
||||
|
|
@ -630,7 +630,7 @@ float controller::activation(float x)
|
|||
}
|
||||
|
||||
template <std::size_t N>
|
||||
geom::vector<float, N> controller::activation(geom::vector<float, N> v)
|
||||
math::vector<float, N> controller::activation(math::vector<float, N> v)
|
||||
{
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
v[i] = activation(v[i]);
|
||||
|
|
@ -640,32 +640,32 @@ geom::vector<float, N> controller::activation(geom::vector<float, N> v)
|
|||
controller lerp(controller const & c1, controller const & c2, float t)
|
||||
{
|
||||
controller c;
|
||||
c.m1 = geom::lerp(c1.m1, c2.m1, t);
|
||||
// c.m2 = geom::lerp(c1.m2, c2.m2, t);
|
||||
// c.m3 = geom::lerp(c1.m3, c2.m3, t);
|
||||
c.t1 = geom::lerp(c1.t1, c2.t1, t);
|
||||
// c.t2 = geom::lerp(c1.t2, c2.t2, t);
|
||||
// c.t3 = geom::lerp(c1.t3, c2.t3, t);
|
||||
c.m1 = math::lerp(c1.m1, c2.m1, t);
|
||||
// c.m2 = math::lerp(c1.m2, c2.m2, t);
|
||||
// c.m3 = math::lerp(c1.m3, c2.m3, t);
|
||||
c.t1 = math::lerp(c1.t1, c2.t1, t);
|
||||
// c.t2 = math::lerp(c1.t2, c2.t2, t);
|
||||
// c.t3 = math::lerp(c1.t3, c2.t3, t);
|
||||
c.generation = std::max(c1.generation, c2.generation) + 1;
|
||||
return c;
|
||||
}
|
||||
|
||||
struct animation_2d_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
animation_2d_app();
|
||||
animation_2d_app(options const &, context const &);
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_mouse_wheel(int delta) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
void on_event(app::mouse_wheel_event const & event) override;
|
||||
|
||||
void on_key_down(SDL_Keycode key) override;
|
||||
void on_event(app::key_event const & event) override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
void update_camera();
|
||||
|
||||
geom::box<float, 2> view_bbox;
|
||||
math::box<float, 2> view_bbox;
|
||||
bool centered = false;
|
||||
|
||||
gfx::painter painter;
|
||||
|
|
@ -688,6 +688,8 @@ struct animation_2d_app
|
|||
test,
|
||||
} mode = mode::train;
|
||||
|
||||
application::context context;
|
||||
|
||||
std::vector<controller> population;
|
||||
std::size_t const population_size = 1024;
|
||||
std::size_t const max_train_frames = 10.f / physics.dt;
|
||||
|
|
@ -698,7 +700,7 @@ struct animation_2d_app
|
|||
std::size_t train_iterations = 0;
|
||||
std::size_t const max_train_iterations = 1024*8;
|
||||
float const initial_variance = 10.f;
|
||||
static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + geom::lerp(0.f, -2.f, t)); };
|
||||
static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + math::lerp(0.f, -2.f, t)); };
|
||||
|
||||
//Eigen::VectorXf mean;
|
||||
//Eigen::MatrixXf covariance;
|
||||
|
|
@ -720,24 +722,24 @@ struct animation_2d_app
|
|||
void do_test();
|
||||
};
|
||||
|
||||
animation_2d_app::animation_2d_app()
|
||||
: app("Animation 2D")
|
||||
animation_2d_app::animation_2d_app(options const &, application::context const & context)
|
||||
: context(context)
|
||||
{
|
||||
view_bbox[1] = {-1.f, 15.f};
|
||||
|
||||
vsync(false);
|
||||
context.vsync(false);
|
||||
}
|
||||
|
||||
void animation_2d_app::on_resize(int width, int height)
|
||||
void animation_2d_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
update_camera();
|
||||
}
|
||||
|
||||
void animation_2d_app::on_mouse_wheel(int delta)
|
||||
void animation_2d_app::on_event(app::mouse_wheel_event const & event)
|
||||
{
|
||||
float p = std::pow(0.8f, delta);
|
||||
float p = std::pow(0.8f, event.delta);
|
||||
view_bbox[1].max *= p;
|
||||
|
||||
update_camera();
|
||||
|
|
@ -745,7 +747,7 @@ void animation_2d_app::on_mouse_wheel(int delta)
|
|||
|
||||
void animation_2d_app::update_camera()
|
||||
{
|
||||
float ratio = static_cast<float>(width()) / height();
|
||||
float ratio = static_cast<float>(state().size[0]) / state().size[1];
|
||||
|
||||
float cx = 0.f;
|
||||
if (centered)
|
||||
|
|
@ -763,25 +765,25 @@ void animation_2d_app::update_camera()
|
|||
cx = ratio * view_bbox[1].length() / 2.f;
|
||||
}
|
||||
|
||||
view_bbox[0] = geom::expand(geom::interval<float>::singleton(cx), ratio * view_bbox[1].length() / 2.f);
|
||||
view_bbox[0] = math::expand(math::interval<float>::singleton(cx), ratio * view_bbox[1].length() / 2.f);
|
||||
}
|
||||
|
||||
void animation_2d_app::on_key_down(SDL_Keycode key)
|
||||
void animation_2d_app::on_event(app::key_event const & event)
|
||||
{
|
||||
if (key == SDLK_c)
|
||||
if (event.down && event.key == app::keycode::C)
|
||||
{
|
||||
centered = !centered;
|
||||
update_camera();
|
||||
}
|
||||
|
||||
if (key == SDLK_p)
|
||||
if (event.down && event.key == app::keycode::P)
|
||||
{
|
||||
testing_control = !testing_control;
|
||||
}
|
||||
|
||||
if (mode == mode::train)
|
||||
{
|
||||
if (key == SDLK_s)
|
||||
if (event.down && event.key == app::keycode::S)
|
||||
{
|
||||
train_iterations = max_train_iterations;
|
||||
}
|
||||
|
|
@ -789,12 +791,12 @@ void animation_2d_app::on_key_down(SDL_Keycode key)
|
|||
if (mode == mode::test)
|
||||
{
|
||||
bool reset = false;
|
||||
if (key == SDLK_LEFT)
|
||||
if (event.down && event.key == app::keycode::LEFT)
|
||||
{
|
||||
reset = true;
|
||||
test_id = (test_id + population.size() - 1) % population.size();
|
||||
}
|
||||
if (key == SDLK_RIGHT)
|
||||
if (event.down && event.key == app::keycode::RIGHT)
|
||||
{
|
||||
reset = true;
|
||||
test_id = (test_id + 1) % population.size();
|
||||
|
|
@ -807,14 +809,14 @@ void animation_2d_app::on_key_down(SDL_Keycode key)
|
|||
|
||||
float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
|
||||
float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
|
||||
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * geom::rad(angle_variation_amplitude);
|
||||
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);
|
||||
|
||||
geom::plane_rotation<float, 2> rot{0, 1, angle};
|
||||
math::plane_rotation<float, 2> rot{0, 1, angle};
|
||||
|
||||
float miny = 0.f;
|
||||
for (auto & b : physics.bones)
|
||||
{
|
||||
b.position = rot(b.position) + geom::vector{shiftx, shifty};
|
||||
b.position = rot(b.position) + math::vector{shiftx, shifty};
|
||||
b.direction = rot(b.direction);
|
||||
miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
|
||||
}
|
||||
|
|
@ -844,7 +846,7 @@ void animation_2d_app::update()
|
|||
|
||||
population[0].reset();
|
||||
|
||||
vsync(true);
|
||||
context.vsync(true);
|
||||
}
|
||||
}
|
||||
else if (mode == mode::test)
|
||||
|
|
@ -869,8 +871,8 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{0.25f, 0.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
|
||||
sys.bones.push_back(bone{{0.75f, 1.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, -1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval<float>{geom::rad(-30.f), geom::rad(30.f)}});
|
||||
sys.joints.push_back(joint{0, 1, -1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -882,10 +884,10 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{ 0.f, h*2.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
||||
sys.bones.push_back(bone{{ s, h*1.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
|
||||
sys.bones.push_back(bone{{ s, h*0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -s, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, s, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -s, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, s, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -894,10 +896,10 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{3, 0, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -906,10 +908,10 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{3, 0, 1.f, -1.f, geom::interval<float>{geom::rad(60.f), geom::rad(120.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -925,7 +927,7 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
|
||||
for (int i = 0; i < 2 * n + 4; ++i)
|
||||
sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, geom::interval<float>{geom::rad(0.f), geom::rad(60.f)}});
|
||||
sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, math::interval<float>{math::rad(0.f), math::rad(60.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -934,7 +936,7 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
for (int i = 0; i < n; ++i)
|
||||
sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
for (int i = 0; i + 1 < n; ++i)
|
||||
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, geom::interval<float>{geom::rad(-30.f), geom::rad(30.f)}});
|
||||
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
|
||||
//*/
|
||||
|
||||
|
||||
|
|
@ -949,11 +951,11 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
}
|
||||
|
||||
for (int i = 0; i + 1 < n; ++i)
|
||||
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, geom::interval<float>{geom::rad(-30.f), geom::rad(30.f)}});
|
||||
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, geom::interval<float>{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{i, n + 2 * i + 1, 0.5f, -1.f, geom::interval<float>{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{i, n + 2 * i + 1, 0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
|
||||
}
|
||||
//*/
|
||||
|
||||
|
|
@ -963,9 +965,9 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{0.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{1.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.bones.push_back(bone{{2.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(-15.f), geom::rad( 15.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(-15.f), math::rad( 15.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -975,10 +977,10 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad(45.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -988,10 +990,10 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -1001,10 +1003,10 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad( 45.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad( 45.f)}});
|
||||
//*/
|
||||
|
||||
/*
|
||||
|
|
@ -1014,17 +1016,17 @@ void animation_2d_app::reset_state(system & sys) const
|
|||
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, geom::interval{geom::rad( 0.f), geom::rad( 45.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, geom::interval{geom::rad(240.f), geom::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, geom::interval{geom::rad(-45.f), geom::rad( 0.f)}});
|
||||
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad( 45.f)}});
|
||||
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
||||
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
||||
//*/
|
||||
|
||||
float const bone_density = 1.f;
|
||||
for (auto & b : sys.bones)
|
||||
{
|
||||
b.position[1] += 0.125f;
|
||||
b.direction = geom::normalized(b.direction);
|
||||
b.direction = math::normalized(b.direction);
|
||||
b.mass = b.length * b.width * bone_density;
|
||||
b.inertia = b.mass * (b.length * b.length + b.width * b.width) / 12.f;
|
||||
}
|
||||
|
|
@ -1043,14 +1045,14 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
|
|||
|
||||
float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
|
||||
float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
|
||||
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * geom::rad(angle_variation_amplitude);
|
||||
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);
|
||||
|
||||
geom::plane_rotation<float, 2> rot{0, 1, angle};
|
||||
math::plane_rotation<float, 2> rot{0, 1, angle};
|
||||
|
||||
float miny = 0.f;
|
||||
for (auto & b : physics.bones)
|
||||
{
|
||||
b.position = rot(b.position) + geom::vector{shiftx, shifty};
|
||||
b.position = rot(b.position) + math::vector{shiftx, shifty};
|
||||
b.direction = rot(b.direction);
|
||||
miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
|
||||
}
|
||||
|
|
@ -1098,7 +1100,7 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
|
|||
auto ctrl = c.apply(physics);
|
||||
for (std::size_t i = 0; i < ctrl.dimension(); ++i)
|
||||
torque[i] = ctrl[i];
|
||||
energy += geom::length(ctrl) * dt;
|
||||
energy += math::length(ctrl) * dt;
|
||||
return torque;
|
||||
});
|
||||
|
||||
|
|
@ -1117,12 +1119,12 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
|
|||
}
|
||||
}
|
||||
|
||||
// penalty += geom::sqr((1.f - physics.bones[2].direction[0]) * dt);
|
||||
// penalty += math::sqr((1.f - physics.bones[2].direction[0]) * dt);
|
||||
// float const target_speed = 2.f;
|
||||
// penalty += geom::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt;
|
||||
// penalty += math::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt;
|
||||
// penalty -= physics.bones[2].velocity[0] * dt / physics.bones;
|
||||
|
||||
auto cm_vel = geom::vector<float, 2>::zero();
|
||||
auto cm_vel = math::vector<float, 2>::zero();
|
||||
float mass = 0.f;
|
||||
for (auto const & b : physics.bones)
|
||||
{
|
||||
|
|
@ -1143,14 +1145,6 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
|
|||
|
||||
if (frame + 1 == max_train_frames)
|
||||
{
|
||||
float mean_pos_x = 0.f;
|
||||
float mass = 0.f;
|
||||
for (auto const & b : physics.bones)
|
||||
{
|
||||
mean_pos_x += b.position[0] * b.mass;
|
||||
mass += b.mass;
|
||||
}
|
||||
mean_pos_x /= mass;
|
||||
cur_score = reward / max_train_variations;
|
||||
}
|
||||
|
||||
|
|
@ -1165,7 +1159,6 @@ float animation_2d_app::eval_score(controller const & c, random::generator rng)
|
|||
return score;
|
||||
}
|
||||
|
||||
|
||||
// Old evolutionary training implementation
|
||||
void animation_2d_app::do_train()
|
||||
{
|
||||
|
|
@ -1367,17 +1360,12 @@ void animation_2d_app::do_train()
|
|||
|
||||
void animation_2d_app::do_test()
|
||||
{
|
||||
std::optional<geom::point<float, 2>> m;
|
||||
if (mouse())
|
||||
{
|
||||
m = view_bbox.corner((*mouse())[0] * 1.f / width(), 1.f - (*mouse())[1] * 1.f / height());
|
||||
}
|
||||
math::point<float, 2> mouse = view_bbox.corner(state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]);
|
||||
|
||||
if (!is_left_button_down())
|
||||
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||
{
|
||||
selected = std::nullopt;
|
||||
|
||||
if (m)
|
||||
{
|
||||
float selected_distance = std::numeric_limits<float>::infinity();
|
||||
|
||||
|
|
@ -1389,20 +1377,20 @@ void animation_2d_app::do_test()
|
|||
auto p1 = b.position + b.direction * b.length / 2.f;
|
||||
auto r = p1 - p0;
|
||||
|
||||
auto d = *m - p0;
|
||||
auto d = mouse - p0;
|
||||
|
||||
float t = geom::dot(d, r) / geom::dot(r, r);
|
||||
float t = math::dot(d, r) / math::dot(r, r);
|
||||
|
||||
float distance;
|
||||
|
||||
if (0.f <= t && t <= 1.f)
|
||||
{
|
||||
distance = geom::length(d - r * t);
|
||||
distance = math::length(d - r * t);
|
||||
}
|
||||
else
|
||||
{
|
||||
float d0 = geom::distance(p0, *m);
|
||||
float d1 = geom::distance(p1, *m);
|
||||
float d0 = math::distance(p0, mouse);
|
||||
float d1 = math::distance(p1, mouse);
|
||||
|
||||
if (d0 < d1)
|
||||
{
|
||||
|
|
@ -1427,10 +1415,10 @@ void animation_2d_app::do_test()
|
|||
}
|
||||
|
||||
std::optional<system::selection> sel;
|
||||
if (selected && is_left_button_down())
|
||||
if (selected && state().mouse_button_down.contains(app::mouse_button::left))
|
||||
{
|
||||
auto const & b = physics.bones[*selected];
|
||||
auto delta = b.position + b.direction * selected_s * b.length / 2.f - *m;
|
||||
auto delta = b.position + b.direction * selected_s * b.length / 2.f - mouse;
|
||||
sel = system::selection{*selected, selected_s, delta};
|
||||
}
|
||||
|
||||
|
|
@ -1452,7 +1440,7 @@ void animation_2d_app::do_test()
|
|||
}
|
||||
|
||||
{
|
||||
auto cm_vel = geom::vector<float, 2>::zero();
|
||||
auto cm_vel = math::vector<float, 2>::zero();
|
||||
float mass = 0.f;
|
||||
for (auto const & b : physics.bones)
|
||||
{
|
||||
|
|
@ -1503,7 +1491,7 @@ void animation_2d_app::present()
|
|||
}
|
||||
}
|
||||
|
||||
painter.render(geom::orthographic_camera{view_bbox}.transform());
|
||||
painter.render(math::orthographic_camera{view_bbox}.transform());
|
||||
|
||||
float avg_speed = 0.f;
|
||||
|
||||
|
|
@ -1515,14 +1503,14 @@ void animation_2d_app::present()
|
|||
int margin = 40;
|
||||
float const step = 1.f;
|
||||
|
||||
int max_frames_shown = (width() - 2 * margin) / step;
|
||||
int max_frames_shown = (state().size[0] - 2 * margin) / step;
|
||||
int start = std::max(0, static_cast<int>(test_speeds.size() - max_frames_shown));
|
||||
|
||||
for (std::size_t i = start; i + 1 < test_speeds.size(); ++i)
|
||||
{
|
||||
float const scale = 2.f;
|
||||
geom::point p0{40.f + (i - start ) * step, 180.f - test_speeds[i ] * scale};
|
||||
geom::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale};
|
||||
math::point p0{40.f + (i - start ) * step, 180.f - test_speeds[i ] * scale};
|
||||
math::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale};
|
||||
painter.line(p0, p1, 2.f, gfx::red, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1532,7 +1520,7 @@ void animation_2d_app::present()
|
|||
opts.c = gfx::black;
|
||||
opts.x = gfx::painter::x_align::left;
|
||||
opts.y = gfx::painter::y_align::top;
|
||||
opts.scale = 2.f;
|
||||
opts.scale = {2.f, 2.f};
|
||||
painter.text({40.f, 40.f}, util::to_string(train_iterations, "/", max_train_iterations), opts);
|
||||
painter.text({40.f, 64.f}, util::to_string("Best score: ", std::setprecision(10), best_score), opts);
|
||||
painter.text({40.f, 88.f}, util::to_string("Model: ", test_id, "/", population.size(), ", gen ", population[test_id].generation), opts);
|
||||
|
|
@ -1542,12 +1530,17 @@ void animation_2d_app::present()
|
|||
// if (mode == mode::test && !test_speeds.empty()) painter.text({40.f, 136.f}, util::to_string("Speed: ", test_speeds.back()), opts);
|
||||
}
|
||||
|
||||
painter.render(geom::window_camera{width(), height()}.transform());
|
||||
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<animation_2d_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<animation_2d_app>({.name = "Animation 2D example"});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,20 @@
|
|||
#include <psemek/audio/effect/fade_out.hpp>
|
||||
#include <psemek/audio/effect/compressor.hpp>
|
||||
#include <psemek/audio/effect/pause.hpp>
|
||||
#include <psemek/audio/effect/loop.hpp>
|
||||
#include <psemek/audio/combine/loop.hpp>
|
||||
#include <psemek/audio/effect/pitch.hpp>
|
||||
#include <psemek/audio/effect/distortion.hpp>
|
||||
#include <psemek/audio/duplicate.hpp>
|
||||
#include <psemek/audio/stereo.hpp>
|
||||
#include <psemek/audio/mixer.hpp>
|
||||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/audio/combine/duplicate.hpp>
|
||||
#include <psemek/audio/combine/stereo.hpp>
|
||||
#include <psemek/audio/combine/mixer.hpp>
|
||||
#include <psemek/audio/midi.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/geom/constants.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/math/constants.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/prof/profiler.hpp>
|
||||
#include <psemek/io/file_stream.hpp>
|
||||
|
||||
|
|
@ -30,44 +31,44 @@
|
|||
|
||||
using namespace psemek;
|
||||
|
||||
static std::map<SDL_Keycode, int> const key_to_midi
|
||||
static std::map<app::keycode, int> const key_to_midi
|
||||
{
|
||||
{SDLK_z, 59},
|
||||
{SDLK_x, 60},
|
||||
{SDLK_c, 61},
|
||||
{SDLK_v, 62},
|
||||
{SDLK_b, 63},
|
||||
{SDLK_n, 64},
|
||||
{SDLK_m, 65},
|
||||
{SDLK_COMMA, 66},
|
||||
{SDLK_PERIOD, 67},
|
||||
{SDLK_SLASH, 68},
|
||||
{SDLK_a, 69},
|
||||
{SDLK_s, 70},
|
||||
{SDLK_d, 71},
|
||||
{SDLK_f, 72},
|
||||
{SDLK_g, 73},
|
||||
{SDLK_h, 74},
|
||||
{SDLK_j, 75},
|
||||
{SDLK_k, 76},
|
||||
{SDLK_l, 77},
|
||||
{SDLK_SEMICOLON, 78},
|
||||
{SDLK_QUOTE, 79},
|
||||
{SDLK_q, 80},
|
||||
{SDLK_w, 81},
|
||||
{SDLK_e, 82},
|
||||
{SDLK_r, 83},
|
||||
{SDLK_t, 84},
|
||||
{SDLK_y, 85},
|
||||
{SDLK_u, 86},
|
||||
{SDLK_i, 87},
|
||||
{SDLK_o, 88},
|
||||
{SDLK_p, 89},
|
||||
{SDLK_LEFTBRACKET, 90},
|
||||
{SDLK_RIGHTBRACKET, 91},
|
||||
{app::keycode::Z, 59},
|
||||
{app::keycode::X, 60},
|
||||
{app::keycode::C, 61},
|
||||
{app::keycode::V, 62},
|
||||
{app::keycode::B, 63},
|
||||
{app::keycode::N, 64},
|
||||
{app::keycode::M, 65},
|
||||
{app::keycode::COMMA, 66},
|
||||
{app::keycode::PERIOD, 67},
|
||||
{app::keycode::SLASH, 68},
|
||||
{app::keycode::A, 69},
|
||||
{app::keycode::S, 70},
|
||||
{app::keycode::D, 71},
|
||||
{app::keycode::F, 72},
|
||||
{app::keycode::G, 73},
|
||||
{app::keycode::H, 74},
|
||||
{app::keycode::J, 75},
|
||||
{app::keycode::K, 76},
|
||||
{app::keycode::L, 77},
|
||||
{app::keycode::SEMICOLON, 78},
|
||||
{app::keycode::APOSTROPHE, 79},
|
||||
{app::keycode::Q, 80},
|
||||
{app::keycode::W, 81},
|
||||
{app::keycode::E, 82},
|
||||
{app::keycode::R, 83},
|
||||
{app::keycode::T, 84},
|
||||
{app::keycode::Y, 85},
|
||||
{app::keycode::U, 86},
|
||||
{app::keycode::I, 87},
|
||||
{app::keycode::O, 88},
|
||||
{app::keycode::P, 89},
|
||||
{app::keycode::LEFTBRACKET, 90},
|
||||
{app::keycode::RIGHTBRACKET, 91},
|
||||
};
|
||||
|
||||
static geom::interval<int> const key_rows[3] = {
|
||||
static math::interval<int> const key_rows[3] = {
|
||||
{59, 68},
|
||||
{69, 79},
|
||||
{80, 91},
|
||||
|
|
@ -78,64 +79,57 @@ static std::string_view const midi_name[12] = {
|
|||
};
|
||||
|
||||
struct audio_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
audio_app()
|
||||
: app::app("Audio example")
|
||||
audio_app(options const &, context const &)
|
||||
{
|
||||
engine_ = audio::make_engine();
|
||||
mixer_ = audio::make_mixer();
|
||||
|
||||
volume_control_ = audio::volume_stereo(mixer_, 0.5f, 0.5f, 0.1f);
|
||||
pitch_control_ = audio::pitch(volume_control_, 1.f, 0.025f);
|
||||
auto compressor = audio::compressor(pitch_control_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f));
|
||||
pause_control_ = audio::pause(compressor, false, 0.01f);
|
||||
engine_.output()->stream(pause_control_);
|
||||
engine_->output()->stream(pause_control_);
|
||||
}
|
||||
|
||||
void on_key_down(SDL_Keycode key) override
|
||||
void on_event(app::key_event const & event) override
|
||||
{
|
||||
app::app::on_key_down(key);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (key_to_midi.contains(key))
|
||||
if (event.down && key_to_midi.contains(event.key))
|
||||
{
|
||||
int midi = key_to_midi.at(key);
|
||||
int midi = key_to_midi.at(event.key);
|
||||
if (!channels_.contains(midi))
|
||||
{
|
||||
auto tone = audio::karplus_strong(440.f * std::pow(2.f, (midi - 69) / 12.f));
|
||||
auto tone = audio::karplus_strong(audio::midi_frequency(midi));
|
||||
tone = audio::distortion(std::move(tone), 4.f);
|
||||
channels_[midi] = mixer_->add(audio::fade_in(tone, 0.005f));
|
||||
}
|
||||
}
|
||||
|
||||
if (key == SDLK_SPACE)
|
||||
if (!event.down && key_to_midi.contains(event.key))
|
||||
{
|
||||
pause_control_->paused(!pause_control_->paused());
|
||||
}
|
||||
|
||||
if (key == SDLK_KP_PLUS)
|
||||
pitch_control_->pitch(std::pow(2.f, 1.f / 12.f));
|
||||
|
||||
if (key == SDLK_KP_MINUS)
|
||||
pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f));
|
||||
}
|
||||
|
||||
void on_key_up(SDL_Keycode key) override
|
||||
{
|
||||
app::app::on_key_up(key);
|
||||
|
||||
if (key_to_midi.contains(key))
|
||||
{
|
||||
int midi = key_to_midi.at(key);
|
||||
int midi = key_to_midi.at(event.key);
|
||||
auto & ch = channels_[midi];
|
||||
if (auto s = ch->stream())
|
||||
ch->stream(audio::fade_out(s, 0.1f));
|
||||
channels_.erase(midi);
|
||||
}
|
||||
|
||||
if (key == SDLK_KP_PLUS)
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
pause_control_->paused(!pause_control_->paused());
|
||||
|
||||
if (event.down && event.key == app::keycode::KP_PLUS)
|
||||
pitch_control_->pitch(std::pow(2.f, 1.f / 12.f));
|
||||
|
||||
if (event.down && event.key == app::keycode::KP_MINUS)
|
||||
pitch_control_->pitch(std::pow(2.f, - 1.f / 12.f));
|
||||
|
||||
if (!event.down && event.key == app::keycode::KP_PLUS)
|
||||
pitch_control_->pitch(1.f);
|
||||
|
||||
if (key == SDLK_KP_MINUS)
|
||||
if (!event.down && event.key == app::keycode::KP_MINUS)
|
||||
pitch_control_->pitch(1.f);
|
||||
}
|
||||
|
||||
|
|
@ -160,11 +154,11 @@ struct audio_app
|
|||
float margin = 4.f;
|
||||
float border = 8.f;
|
||||
|
||||
float center = width() / 2.f;
|
||||
float y = height() / 2.f + size * std::size(key_rows) / 2.f;
|
||||
float center = state().size[0] / 2.f;
|
||||
float y = state().size[1] / 2.f + size * std::size(key_rows) / 2.f;
|
||||
|
||||
gfx::painter::text_options opts;
|
||||
opts.scale = 2.f;
|
||||
opts.scale = {2.f, 2.f};
|
||||
opts.c = {0, 0, 0, 255};
|
||||
opts.x = gfx::painter::x_align::center;
|
||||
opts.y = gfx::painter::y_align::center;
|
||||
|
|
@ -191,13 +185,13 @@ struct audio_app
|
|||
}
|
||||
}
|
||||
|
||||
opts.scale = 4.f;
|
||||
opts.scale = {4.f, 4.f};
|
||||
opts.c = pause_control_->paused() ? gfx::color_rgba{255, 0, 0, 255} : gfx::color_rgba{0, 127, 0, 255};
|
||||
painter_.text({width() / 2.f, height() - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts);
|
||||
painter_.text({state().size[0] / 2.f, state().size[1] - 200.f}, pause_control_->paused() ? "PAUSED" : "PLAYING", opts);
|
||||
|
||||
{
|
||||
float x = width() - 200.f;
|
||||
float y = height() / 2.f;
|
||||
float x = state().size[0] - 200.f;
|
||||
float y = state().size[1] / 2.f;
|
||||
|
||||
float w = 4.f;
|
||||
float h = 64.f;
|
||||
|
|
@ -210,16 +204,16 @@ struct audio_app
|
|||
painter_.rect({{{x - s, x + s}, {r - w, r + w}}}, {0, 0, 255, 255});
|
||||
}
|
||||
|
||||
painter_.render(geom::window_camera{width(), height()}.transform());
|
||||
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
|
||||
void on_scene_exit() override
|
||||
~audio_app() override
|
||||
{
|
||||
prof::dump();
|
||||
}
|
||||
|
||||
private:
|
||||
audio::engine engine_;
|
||||
std::unique_ptr<audio::engine> engine_;
|
||||
audio::mixer_ptr mixer_;
|
||||
std::shared_ptr<audio::volume_control_stereo> volume_control_;
|
||||
std::shared_ptr<audio::pitch_control> pitch_control_;
|
||||
|
|
@ -234,7 +228,12 @@ private:
|
|||
gfx::painter painter_;
|
||||
};
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<audio_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<audio_app>({.name = "Audio example"});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
BIN
examples/biomes.png
Normal file
BIN
examples/biomes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 B |
|
|
@ -1,13 +1,13 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/geom/box.hpp>
|
||||
#include <psemek/geom/mesh.hpp>
|
||||
#include <psemek/geom/intersection.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/homogeneous.hpp>
|
||||
#include <psemek/geom/constants.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/gradient.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/math/box.hpp>
|
||||
#include <psemek/math/mesh.hpp>
|
||||
#include <psemek/math/intersection.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/homogeneous.hpp>
|
||||
#include <psemek/math/constants.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/gradient.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
#include <psemek/gfx/renderer/simple.hpp>
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/async/threadpool.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
|
@ -50,19 +51,19 @@ auto barycenter(Iterator begin, Iterator end)
|
|||
template <typename Container>
|
||||
auto barycenter(Container const & c)
|
||||
{
|
||||
return barycenter(util::begin(c), util::end(c));
|
||||
return barycenter(util::xbegin(c), util::xend(c));
|
||||
}
|
||||
|
||||
std::vector<geom::point<float, 3>> intersection(geom::vector<float, 4> const & f, std::vector<geom::point<float, 3>> const & vertices, std::vector<geom::segment<std::uint32_t>> const & edges)
|
||||
std::vector<math::point<float, 3>> intersection(math::vector<float, 4> const & f, std::vector<math::point<float, 3>> const & vertices, std::vector<math::segment<std::uint32_t>> const & edges)
|
||||
{
|
||||
std::vector<geom::point<float, 3>> points;
|
||||
std::vector<math::point<float, 3>> points;
|
||||
|
||||
for (auto e : edges)
|
||||
{
|
||||
auto p0 = vertices[e[0]];
|
||||
auto p1 = vertices[e[1]];
|
||||
auto f0 = dot(f, geom::homogeneous(p0));
|
||||
auto f1 = dot(f, geom::homogeneous(p1));
|
||||
auto f0 = dot(f, math::homogeneous(p0));
|
||||
auto f1 = dot(f, math::homogeneous(p1));
|
||||
|
||||
if ((f0 >= 0.f) ^ (f1 >= 0.f))
|
||||
{
|
||||
|
|
@ -70,14 +71,14 @@ std::vector<geom::point<float, 3>> intersection(geom::vector<float, 4> const & f
|
|||
}
|
||||
}
|
||||
|
||||
geom::vector<float, 3> const n { f[0], f[1], f[2] };
|
||||
math::vector<float, 3> const n { f[0], f[1], f[2] };
|
||||
|
||||
if (!points.empty())
|
||||
{
|
||||
auto const & p0 = points[0];
|
||||
|
||||
std::sort(points.begin() + 1, points.end(), [&](geom::point<float, 3> const & p1, geom::point<float, 3> const & p2){
|
||||
return geom::dot(geom::cross(p1 - p0, p2 - p0), n) > 0.f;
|
||||
std::sort(points.begin() + 1, points.end(), [&](math::point<float, 3> const & p1, math::point<float, 3> const & p2){
|
||||
return math::dot(math::cross(p1 - p0, p2 - p0), n) > 0.f;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -88,9 +89,9 @@ template <typename Vertex, std::size_t N, typename Index = std::uint32_t>
|
|||
struct mesh_builder
|
||||
{
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<geom::simplex<Index, N>> indices;
|
||||
std::vector<math::simplex<Index, N>> indices;
|
||||
|
||||
void add(std::vector<Vertex> const & v, std::vector<geom::simplex<Index, N>> const & i)
|
||||
void add(std::vector<Vertex> const & v, std::vector<math::simplex<Index, N>> const & i)
|
||||
{
|
||||
Index const base = static_cast<Index>(vertices.size());
|
||||
std::size_t begin = indices.size();
|
||||
|
|
@ -161,27 +162,27 @@ void main()
|
|||
)";
|
||||
|
||||
struct cloud_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
geom::spherical_camera camera;
|
||||
math::spherical_camera camera;
|
||||
|
||||
float step;
|
||||
|
||||
float max_density = 2.f;
|
||||
geom::interval<float> harmonic_range;
|
||||
math::interval<float> harmonic_range;
|
||||
|
||||
util::clock<std::chrono::duration<float>> clock;
|
||||
|
||||
geom::box<float, 3> bbox;
|
||||
std::vector<geom::point<float, 3>> bbox_vertices;
|
||||
std::vector<geom::segment<std::uint32_t>> bbox_edges;
|
||||
math::box<float, 3> bbox;
|
||||
std::vector<math::point<float, 3>> bbox_vertices;
|
||||
std::vector<math::segment<std::uint32_t>> bbox_edges;
|
||||
|
||||
gfx::mesh bbox_mesh;
|
||||
|
||||
gfx::mesh slice_mesh;
|
||||
|
||||
gfx::mesh light_mesh;
|
||||
std::vector<geom::vector<float, 3>> dirs;
|
||||
std::vector<math::vector<float, 3>> dirs;
|
||||
|
||||
gfx::simple_renderer renderer;
|
||||
|
||||
|
|
@ -191,14 +192,13 @@ struct cloud_app
|
|||
gfx::texture_3d shadow_texture;
|
||||
|
||||
cloud_app(std::size_t size)
|
||||
: app::app("Cloud")
|
||||
{
|
||||
geom::vector<int, 3> cloud_size{2 * size, size, size};
|
||||
math::vector<int, 3> cloud_size{2 * size, size, size};
|
||||
bbox = {{{-2.f, 2.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
|
||||
|
||||
step = bbox[0].length() / cloud_size[0];
|
||||
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.fov_y = math::rad(45.f);
|
||||
camera.near_clip = 0.01f;
|
||||
camera.far_clip = 100.f;
|
||||
|
||||
|
|
@ -207,15 +207,15 @@ struct cloud_app
|
|||
camera.elevation_angle = 0.f;
|
||||
camera.distance = 10.f;
|
||||
|
||||
bbox_vertices = geom::vertices(bbox);
|
||||
bbox_edges = geom::edges(bbox);
|
||||
bbox_vertices = math::vertices(bbox);
|
||||
bbox_edges = math::edges(bbox);
|
||||
|
||||
bbox_mesh.setup<geom::point<float, 3>>();
|
||||
bbox_mesh.setup<math::point<float, 3>>();
|
||||
bbox_mesh.load(bbox_vertices, bbox_edges);
|
||||
|
||||
slice_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>>();
|
||||
slice_mesh.setup<math::point<float, 3>, math::vector<float, 3>>();
|
||||
|
||||
light_mesh.setup<geom::point<float, 3>>();
|
||||
light_mesh.setup<math::point<float, 3>>();
|
||||
|
||||
{
|
||||
async::threadpool bg("bg");
|
||||
|
|
@ -223,7 +223,7 @@ struct cloud_app
|
|||
random::generator rng(random::device{});
|
||||
random::uniform_sphere_vector_distribution<float, 3> d;
|
||||
|
||||
std::vector<util::array<geom::vector<float, 3>, 3>> grad(4);
|
||||
std::vector<util::ndarray<math::vector<float, 3>, 3>> grad(4);
|
||||
std::vector<float> weights(grad.size());
|
||||
float weight_sum = 0.f;
|
||||
|
||||
|
|
@ -245,9 +245,9 @@ struct cloud_app
|
|||
|
||||
// can't use template argument deduction for first argument due to gcc bug
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89062
|
||||
geom::gradient<float> g(std::make_pair(0.2f, 0.f), geom::easing_type::quadratic_out, std::pair{0.3f, max_density});
|
||||
math::gradient<float> g(std::make_pair(0.2f, 0.f), math::easing_type::quadratic_out, std::pair{0.3f, max_density});
|
||||
|
||||
util::array<std::uint8_t, 3> cloud_data({cloud_size[0], cloud_size[1], cloud_size[2]});
|
||||
util::ndarray<std::uint8_t, 3> cloud_data({cloud_size[0], cloud_size[1], cloud_size[2]});
|
||||
|
||||
for (std::size_t z = 0; z < cloud_data.depth(); ++z)
|
||||
{
|
||||
|
|
@ -256,17 +256,17 @@ struct cloud_app
|
|||
bg.post([&, z, y]{
|
||||
for (std::size_t x = 0; x < cloud_data.width(); ++x)
|
||||
{
|
||||
geom::vector<float, 3> p;
|
||||
math::vector<float, 3> p;
|
||||
p[0] = (x * 1.f) / cloud_data.width();
|
||||
p[1] = (y * 1.f) / cloud_data.height();
|
||||
p[2] = (z * 1.f) / cloud_data.depth();
|
||||
|
||||
float v = perlin(p);
|
||||
|
||||
auto d = p - geom::vector{0.5f, 0.5f, 0.5f};
|
||||
v *= std::max(0.f, 1.f - geom::length(d) / 0.5f);
|
||||
auto d = p - math::vector{0.5f, 0.5f, 0.5f};
|
||||
v *= std::max(0.f, 1.f - math::length(d) / 0.5f);
|
||||
|
||||
cloud_data(x, y, z) = geom::clamp(g(v) / max_density * 255.f, {0.f, 255.f});
|
||||
cloud_data(x, y, z) = math::clamp(g(v) / max_density * 255.f, {0.f, 255.f});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -280,9 +280,9 @@ struct cloud_app
|
|||
cloud_texture.linear_filter();
|
||||
cloud_texture.generate_mipmap();
|
||||
|
||||
auto value_at = [this, &cloud_data](geom::point<float, 3> const & p)
|
||||
auto value_at = [this, &cloud_data](math::point<float, 3> const & p)
|
||||
{
|
||||
geom::vector<float, 3> d;
|
||||
math::vector<float, 3> d;
|
||||
d[0] = (p[0] - bbox[0].min) / bbox[0].length() * cloud_data.width();
|
||||
d[1] = (p[1] - bbox[1].min) / bbox[1].length() * cloud_data.height();
|
||||
d[2] = (p[2] - bbox[2].min) / bbox[2].length() * cloud_data.depth();
|
||||
|
|
@ -298,12 +298,12 @@ struct cloud_app
|
|||
if (d[2] < 0.0f) return 0.f;
|
||||
if (d[2] >= cloud_data.depth() - 1) return 0.f;
|
||||
|
||||
geom::vector<int, 3> i;
|
||||
math::vector<int, 3> i;
|
||||
i[0] = std::floor(d[0]);
|
||||
i[1] = std::floor(d[1]);
|
||||
i[2] = std::floor(d[2]);
|
||||
|
||||
geom::vector<float, 3> t;
|
||||
math::vector<float, 3> t;
|
||||
t[0] = d[0] - i[0];
|
||||
t[1] = d[1] - i[1];
|
||||
t[2] = d[2] - i[2];
|
||||
|
|
@ -317,18 +317,18 @@ struct cloud_app
|
|||
float v011 = cloud_data(i[0], i[1] + 1, i[2] + 1);
|
||||
float v111 = cloud_data(i[0] + 1, i[1] + 1, i[2] + 1);
|
||||
|
||||
float v00 = geom::lerp(v000, v100, t[0]);
|
||||
float v10 = geom::lerp(v010, v110, t[0]);
|
||||
float v01 = geom::lerp(v001, v101, t[0]);
|
||||
float v11 = geom::lerp(v011, v111, t[0]);
|
||||
float v00 = math::lerp(v000, v100, t[0]);
|
||||
float v10 = math::lerp(v010, v110, t[0]);
|
||||
float v01 = math::lerp(v001, v101, t[0]);
|
||||
float v11 = math::lerp(v011, v111, t[0]);
|
||||
|
||||
float v0 = geom::lerp(v00, v10, t[1]);
|
||||
float v1 = geom::lerp(v01, v11, t[1]);
|
||||
float v0 = math::lerp(v00, v10, t[1]);
|
||||
float v1 = math::lerp(v01, v11, t[1]);
|
||||
|
||||
return geom::lerp(v0, v1, t[2]) / 255.f * max_density;
|
||||
return math::lerp(v0, v1, t[2]) / 255.f * max_density;
|
||||
};
|
||||
|
||||
util::array<geom::vector<float, 4>, 3> cloud_shadow_f(cloud_data.dims());
|
||||
util::ndarray<math::vector<float, 4>, 3> cloud_shadow_f(cloud_data.dims());
|
||||
|
||||
dirs.resize(32);
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ struct cloud_app
|
|||
float const phi = (1.f + std::sqrt(5.f)) / 2.f;
|
||||
for (int i = 0; i < dirs.size(); ++i)
|
||||
{
|
||||
float a = 2.f * geom::pi * (i / phi);
|
||||
float a = 2.f * math::pi * (i / phi);
|
||||
float b = std::acos(1.f - (2.f * i) / dirs.size());
|
||||
|
||||
dirs[i][0] = std::cos(a) * std::sin(b);
|
||||
|
|
@ -344,14 +344,14 @@ struct cloud_app
|
|||
dirs[i][2] = std::cos(b);
|
||||
}
|
||||
|
||||
float diag = std::sqrt(geom::sqr(bbox[0].length()) + geom::sqr(bbox[1].length()) + geom::sqr(bbox[2].length()));
|
||||
float diag = std::sqrt(math::sqr(bbox[0].length()) + math::sqr(bbox[1].length()) + math::sqr(bbox[2].length()));
|
||||
int steps = diag / step;
|
||||
|
||||
auto process = [&, this](auto const & idx)
|
||||
{
|
||||
float const step = bbox[0].length() / cloud_size[0];
|
||||
|
||||
geom::point<float, 3> o;
|
||||
math::point<float, 3> o;
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
o[i] = bbox[i].min + bbox[i].length() * (idx[i] + 0.5f) / cloud_shadow_f.dim(i);
|
||||
|
||||
|
|
@ -364,7 +364,7 @@ struct cloud_app
|
|||
{
|
||||
float density = 0.f;
|
||||
|
||||
geom::point<float, 3> p = o + dir * (steps * step);
|
||||
math::point<float, 3> p = o + dir * (steps * step);
|
||||
|
||||
for (int s = steps; s > 0; --s)
|
||||
{
|
||||
|
|
@ -374,10 +374,10 @@ struct cloud_app
|
|||
p -= dir * step;
|
||||
}
|
||||
|
||||
sum_0 += density * 2.f * std::sqrt(geom::pi);
|
||||
sum_x += density * dir[0] * std::sqrt(12.f * geom::pi);
|
||||
sum_y += density * dir[1] * std::sqrt(12.f * geom::pi);
|
||||
sum_z += density * dir[2] * std::sqrt(12.f * geom::pi);
|
||||
sum_0 += density * 2.f * std::sqrt(math::pi);
|
||||
sum_x += density * dir[0] * std::sqrt(12.f * math::pi);
|
||||
sum_y += density * dir[1] * std::sqrt(12.f * math::pi);
|
||||
sum_z += density * dir[2] * std::sqrt(12.f * math::pi);
|
||||
++count;
|
||||
}
|
||||
|
||||
|
|
@ -414,8 +414,8 @@ struct cloud_app
|
|||
|
||||
log::info() << "Finished!";
|
||||
|
||||
geom::interval<float> range_0;
|
||||
geom::interval<float> range_d;
|
||||
math::interval<float> range_0;
|
||||
math::interval<float> range_d;
|
||||
|
||||
for (auto const & v : cloud_shadow_f)
|
||||
{
|
||||
|
|
@ -448,14 +448,14 @@ struct cloud_app
|
|||
}
|
||||
}
|
||||
|
||||
util::array<geom::vector<std::uint8_t, 4>, 3> cloud_shadow(cloud_data.dims());
|
||||
util::ndarray<math::vector<std::uint8_t, 4>, 3> cloud_shadow(cloud_data.dims());
|
||||
|
||||
for (auto const & idx : cloud_shadow.indices())
|
||||
{
|
||||
for (std::size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
float v = (cloud_shadow_f(idx)[i] - harmonic_range.min) / harmonic_range.length();
|
||||
cloud_shadow(idx)[i] = geom::clamp(255.f * v, {0.f, 255.f});
|
||||
cloud_shadow(idx)[i] = math::clamp(255.f * v, {0.f, 255.f});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -466,48 +466,55 @@ struct cloud_app
|
|||
}
|
||||
}
|
||||
|
||||
void on_resize(int width, int height) override
|
||||
void on_event(app::resize_event const & event) override
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
float aspect_ratio = (1.f * width) / height;
|
||||
gl::Viewport(0, 0, event.size[0], event.size[1]);
|
||||
|
||||
float aspect_ratio = (1.f * event.size[0]) / event.size[1];
|
||||
camera.set_fov(camera.fov_y, aspect_ratio);
|
||||
}
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||
void on_event(app::mouse_move_event const & event) override
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
auto const old_mouse = state().mouse;
|
||||
|
||||
if (is_middle_button_down())
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (state().mouse_button_down.contains(app::mouse_button::middle))
|
||||
{
|
||||
camera.azimuthal_angle -= dx * 0.01f;
|
||||
camera.elevation_angle += dy * 0.01f;
|
||||
auto const delta = event.position - old_mouse;
|
||||
camera.azimuthal_angle -= delta[0] * 0.01f;
|
||||
camera.elevation_angle += delta[1] * 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
void on_mouse_wheel(int delta) override
|
||||
void on_event(app::mouse_wheel_event const & event) override
|
||||
{
|
||||
camera.distance *= std::pow(0.8f, delta);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera.distance *= std::pow(0.8f, event.delta);
|
||||
}
|
||||
|
||||
void update_slice_mesh()
|
||||
{
|
||||
auto n = -camera.direction();
|
||||
geom::vector<float, 4> f {n[0], n[1], n[2], 0.f};
|
||||
math::vector<float, 4> f {n[0], n[1], n[2], 0.f};
|
||||
|
||||
geom::interval<float> range;
|
||||
math::interval<float> range;
|
||||
|
||||
for (auto p : bbox_vertices)
|
||||
{
|
||||
range |= geom::dot(f, geom::homogeneous(p));
|
||||
range |= math::dot(f, math::homogeneous(p));
|
||||
}
|
||||
|
||||
int count = std::ceil(range.length() / step);
|
||||
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> pos;
|
||||
geom::vector<float, 3> tc;
|
||||
math::point<float, 3> pos;
|
||||
math::vector<float, 3> tc;
|
||||
};
|
||||
|
||||
mesh_builder<vertex, 2> builder;
|
||||
|
|
@ -517,7 +524,7 @@ struct cloud_app
|
|||
f[3] = - (range.max - (range.length() * s) / count);
|
||||
|
||||
auto slice_vertices = intersection(f, bbox_vertices, bbox_edges);
|
||||
auto slice_indices = geom::triangulate_convex(slice_vertices);
|
||||
auto slice_indices = math::triangulate_convex(slice_vertices);
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
for (auto p : slice_vertices)
|
||||
|
|
@ -534,17 +541,20 @@ struct cloud_app
|
|||
slice_mesh.load(builder.vertices, builder.indices);
|
||||
}
|
||||
|
||||
void update() override
|
||||
{}
|
||||
|
||||
void present() override
|
||||
{
|
||||
update_slice_mesh();
|
||||
|
||||
float t = clock.count();
|
||||
|
||||
geom::vector<float, 3> light_dir = geom::normalized(geom::vector{std::cos(t), std::sin(t), 0.5f});
|
||||
math::vector<float, 3> light_dir = math::normalized(math::vector{std::cos(t), std::sin(t), 0.5f});
|
||||
|
||||
{
|
||||
std::vector<geom::point<float, 3>> light_vertices;
|
||||
auto o = geom::point<float, 3>::zero() + light_dir * 4.f;
|
||||
std::vector<math::point<float, 3>> light_vertices;
|
||||
auto o = math::point<float, 3>::zero() + light_dir * 4.f;
|
||||
float s = 0.2f;
|
||||
for (auto d : dirs)
|
||||
{
|
||||
|
|
@ -581,7 +591,7 @@ struct cloud_app
|
|||
cloud_program["u_max_density"] = max_density;
|
||||
cloud_program["u_min_harmonic"] = harmonic_range.min;
|
||||
cloud_program["u_max_harmonic"] = harmonic_range.max;
|
||||
cloud_program["u_light"] = geom::normalized(light_dir);
|
||||
cloud_program["u_light"] = math::normalized(light_dir);
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
cloud_texture.bind();
|
||||
gl::ActiveTexture(gl::TEXTURE1);
|
||||
|
|
@ -592,18 +602,24 @@ struct cloud_app
|
|||
}
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
namespace psemek::app
|
||||
{
|
||||
if (argc != 1 && argc != 2)
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
std::cout << "Usage: " << argv[0] << " [ size ]\n";
|
||||
return 0;
|
||||
return default_application_factory({.name = "Cloud example"}, [](application::options const &, application::context const & context)
|
||||
-> std::unique_ptr<application> {
|
||||
if (context.args.size() != 1 && context.args.size() != 2)
|
||||
{
|
||||
std::cout << "Usage: " << context.args[0] << " [ size ]\n";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::size_t size = 32;
|
||||
if (context.args.size() == 2)
|
||||
size = util::from_string<std::size_t>(context.args[1]);
|
||||
return std::make_unique<cloud_app>(size);
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t size = 32;
|
||||
if (argc == 2)
|
||||
{
|
||||
size = util::from_string<std::size_t>(argv[1]);
|
||||
}
|
||||
return app::main<cloud_app>(size);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/gfx/renderer/deferred.hpp>
|
||||
#include <psemek/gfx/effect/gamma.hpp>
|
||||
#include <psemek/gfx/effect/fxaa.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
|
||||
#include <psemek/geom/mesh.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/translation.hpp>
|
||||
#include <psemek/geom/scale.hpp>
|
||||
#include <psemek/math/mesh.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/rotation.hpp>
|
||||
#include <psemek/math/translation.hpp>
|
||||
#include <psemek/math/scale.hpp>
|
||||
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/moving_average.hpp>
|
||||
|
|
@ -22,14 +22,14 @@
|
|||
using namespace psemek;
|
||||
|
||||
struct deferred_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
deferred_app();
|
||||
deferred_app(options const & options, context const & context);
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override;
|
||||
void on_mouse_wheel(int delta) override;
|
||||
void on_event(app::mouse_move_event const & event) override;
|
||||
void on_event(app::mouse_wheel_event const & event) override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
|
@ -44,7 +44,7 @@ struct deferred_app
|
|||
gfx::texture_2d pre_fxaa_texture;
|
||||
gfx::fxaa fxaa;
|
||||
|
||||
geom::spherical_camera camera;
|
||||
math::spherical_camera camera;
|
||||
|
||||
gfx::mesh plane;
|
||||
gfx::mesh cube;
|
||||
|
|
@ -60,18 +60,17 @@ struct deferred_app
|
|||
gfx::painter painter;
|
||||
};
|
||||
|
||||
deferred_app::deferred_app()
|
||||
: app("Deferred shading example", 0)
|
||||
deferred_app::deferred_app(options const &, context const & context)
|
||||
{
|
||||
vsync(false);
|
||||
context.vsync(false);
|
||||
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.fov_y = math::rad(45.f);
|
||||
camera.near_clip = 0.1f;
|
||||
camera.far_clip = 1000.f;
|
||||
camera.target = {0.f, 0.f, 0.f};
|
||||
camera.distance = 20.f;
|
||||
camera.azimuthal_angle = 0.f;
|
||||
camera.elevation_angle = geom::rad(30.f);
|
||||
camera.elevation_angle = math::rad(30.f);
|
||||
|
||||
pre_gamma_texture.linear_filter();
|
||||
pre_fxaa_texture.linear_filter();
|
||||
|
|
@ -86,10 +85,10 @@ deferred_app::deferred_app()
|
|||
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> position;
|
||||
math::point<float, 3> position;
|
||||
gfx::color_rgba color;
|
||||
geom::vector<float, 2> texcoord;
|
||||
geom::vector<float, 3> normal;
|
||||
math::vector<float, 2> texcoord;
|
||||
math::vector<float, 3> normal;
|
||||
|
||||
static auto attribs()
|
||||
{
|
||||
|
|
@ -99,7 +98,7 @@ deferred_app::deferred_app()
|
|||
|
||||
struct instance
|
||||
{
|
||||
geom::matrix<float, 3, 4> transform;
|
||||
math::matrix<float, 3, 4> transform;
|
||||
|
||||
static auto attribs()
|
||||
{
|
||||
|
|
@ -116,7 +115,7 @@ deferred_app::deferred_app()
|
|||
vertices.push_back({{-1.f, 1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}});
|
||||
vertices.push_back({{ 1.f, 1.f, 0.f}, color, {0.f, 0.f}, {0.f, 0.f, 1.f}});
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> indices;
|
||||
std::vector<math::triangle<std::uint32_t>> indices;
|
||||
indices.push_back({0, 1, 2});
|
||||
indices.push_back({2, 1, 3});
|
||||
|
||||
|
|
@ -125,17 +124,17 @@ deferred_app::deferred_app()
|
|||
}
|
||||
|
||||
{
|
||||
auto box = geom::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
|
||||
auto box = math::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {-1.f, 1.f}}};
|
||||
|
||||
auto triangles = geom::deindex(geom::vertices(box), geom::faces(box));
|
||||
auto triangles = math::deindex(math::vertices(box), math::faces(box));
|
||||
|
||||
std::vector<geom::triangle<vertex>> vertices;
|
||||
std::vector<math::triangle<vertex>> vertices;
|
||||
|
||||
for (auto const & t : triangles)
|
||||
{
|
||||
auto & r = vertices.emplace_back();
|
||||
|
||||
auto n = geom::normal(t[0], t[1], t[2]);
|
||||
auto n = math::normal(t[0], t[1], t[2]);
|
||||
|
||||
std::size_t tcm = 0;
|
||||
if (std::abs(n[1]) > std::abs(n[tcm])) tcm = 1;
|
||||
|
|
@ -163,7 +162,7 @@ deferred_app::deferred_app()
|
|||
{
|
||||
std::vector<vertex> vertices;
|
||||
|
||||
geom::point<float, 3> const origin {0.f, 0.f, 0.f};
|
||||
math::point<float, 3> const origin {0.f, 0.f, 0.f};
|
||||
|
||||
float const radius = 1.f;
|
||||
|
||||
|
|
@ -175,19 +174,19 @@ deferred_app::deferred_app()
|
|||
{
|
||||
for (int i = 0; i < 4 * N; ++i)
|
||||
{
|
||||
float a = (geom::pi * i) / (2 * N);
|
||||
float b = (geom::pi * j) / (2 * N);
|
||||
float a = (math::pi * i) / (2 * N);
|
||||
float b = (math::pi * j) / (2 * N);
|
||||
|
||||
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
|
||||
vertices.push_back({origin + radius * n, c, {0.f, 0.f}, n});
|
||||
}
|
||||
}
|
||||
|
||||
vertices.push_back({origin + geom::vector{0.f, 0.f, -radius}, c, {0.f, 0.f}, {0.f, 0.f, -1.f}});
|
||||
vertices.push_back({origin + geom::vector{0.f, 0.f, radius}, c, {0.f, 0.f}, {0.f, 0.f, 1.f}});
|
||||
vertices.push_back({origin + math::vector{0.f, 0.f, -radius}, c, {0.f, 0.f}, {0.f, 0.f, -1.f}});
|
||||
vertices.push_back({origin + math::vector{0.f, 0.f, radius}, c, {0.f, 0.f}, {0.f, 0.f, 1.f}});
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> indices;
|
||||
std::vector<math::triangle<std::uint32_t>> indices;
|
||||
|
||||
auto idx = [](int i, int j) -> std::uint32_t { return (i % (4 * N)) + 4 * N * (j + N - 1); };
|
||||
|
||||
|
|
@ -217,7 +216,7 @@ deferred_app::deferred_app()
|
|||
{
|
||||
std::vector<vertex> vertices;
|
||||
|
||||
geom::point<float, 3> const position = {0.f, 0.f, 0.f};
|
||||
math::point<float, 3> const position = {0.f, 0.f, 0.f};
|
||||
|
||||
float const radius1 = 0.8f;
|
||||
float const radius2 = 0.2f;
|
||||
|
|
@ -231,18 +230,18 @@ deferred_app::deferred_app()
|
|||
{
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
float a = (2.f * geom::pi * i) / N;
|
||||
float b = (2.f * geom::pi * j) / M;
|
||||
float a = (2.f * math::pi * i) / N;
|
||||
float b = (2.f * math::pi * j) / M;
|
||||
|
||||
geom::vector r{std::cos(a), std::sin(a), 0.f};
|
||||
math::vector r{std::cos(a), std::sin(a), 0.f};
|
||||
|
||||
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
|
||||
vertices.push_back({position + radius1 * r + radius2 * n, color, {0.f, 0.f}, n});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> indices;
|
||||
std::vector<math::triangle<std::uint32_t>> indices;
|
||||
|
||||
auto idx = [](int i, int j) -> std::uint32_t { return (i % N) + N * (j % M); };
|
||||
|
||||
|
|
@ -291,34 +290,39 @@ deferred_app::deferred_app()
|
|||
}
|
||||
}
|
||||
|
||||
void deferred_app::on_resize(int width, int height)
|
||||
void deferred_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
camera.set_fov(camera.fov_y, static_cast<float>(width) / height);
|
||||
app::application_base::on_event(event);
|
||||
camera.set_fov(camera.fov_y, static_cast<float>(event.size[0]) / event.size[1]);
|
||||
|
||||
pre_gamma_texture.load<geom::vector<std::uint16_t, 3>>({width, height});
|
||||
pre_gamma_texture.load<math::vector<std::uint16_t, 3>>(math::cast<std::size_t>(event.size));
|
||||
pre_gamma_framebuffer.color(pre_gamma_texture);
|
||||
pre_gamma_framebuffer.assert_complete();
|
||||
|
||||
pre_fxaa_texture.load<gfx::color_rgb>({width, height});
|
||||
pre_fxaa_texture.load<gfx::color_rgb>(math::cast<std::size_t>(event.size));
|
||||
pre_fxaa_framebuffer.color(pre_fxaa_texture);
|
||||
pre_fxaa_framebuffer.assert_complete();
|
||||
}
|
||||
|
||||
void deferred_app::on_mouse_move(int x, int y, int dx, int dy)
|
||||
void deferred_app::on_event(app::mouse_move_event const & event)
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
auto const old_mouse = state().mouse;
|
||||
|
||||
if (is_middle_button_down())
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (state().mouse_button_down.contains(app::mouse_button::middle))
|
||||
{
|
||||
camera.azimuthal_angle -= dx * 0.01f;
|
||||
camera.elevation_angle += dy * 0.01f;
|
||||
auto delta = event.position - old_mouse;
|
||||
camera.azimuthal_angle -= delta[0] * 0.01f;
|
||||
camera.elevation_angle += delta[1] * 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
void deferred_app::on_mouse_wheel(int delta)
|
||||
void deferred_app::on_event(app::mouse_wheel_event const & event)
|
||||
{
|
||||
camera.distance *= std::pow(0.8f, delta);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera.distance *= std::pow(0.8f, event.delta);
|
||||
}
|
||||
|
||||
void deferred_app::update()
|
||||
|
|
@ -339,7 +343,7 @@ void deferred_app::present()
|
|||
{
|
||||
gfx::deferred_renderer::object obj;
|
||||
obj.mesh = &plane;
|
||||
obj.pre_transform = geom::scale<float, 3>(10.f).affine_matrix();
|
||||
obj.pre_transform = math::scale<float, 3>(10.f).affine_matrix();
|
||||
obj.bbox = {{{-10.f, 10.f}, {-10.f, 10.f}, {0.f, 0.f}}};
|
||||
obj.mat = &plane_material;
|
||||
objects.push_back(obj);
|
||||
|
|
@ -354,7 +358,7 @@ void deferred_app::present()
|
|||
{
|
||||
gfx::deferred_renderer::object obj;
|
||||
obj.mesh = &cube;
|
||||
obj.pre_transform = geom::translation<float, 3>{geom::vector{x, y, 3.f}}.affine_matrix();
|
||||
obj.pre_transform = math::translation<float, 3>{math::vector{x, y, 3.f}}.affine_matrix();
|
||||
obj.bbox = {{{x - 1.f, x + 1.f}, {y - 1.f, y + 1.f}, {2.f, 4.f}}};
|
||||
obj.mat = &cube_material;
|
||||
objects.push_back(obj);
|
||||
|
|
@ -370,7 +374,7 @@ void deferred_app::present()
|
|||
{
|
||||
gfx::deferred_renderer::object obj;
|
||||
obj.mesh = &sphere;
|
||||
obj.pre_transform = geom::translation<float, 3>{geom::vector{0.f, 0.f, z}}.affine_matrix();
|
||||
obj.pre_transform = math::translation<float, 3>{math::vector{0.f, 0.f, z}}.affine_matrix();
|
||||
obj.bbox = {{{-1.f, 1.f}, {-1.f, 1.f}, {z - 1.f, z + 1.f}}};
|
||||
obj.mat = &sphere_material;
|
||||
objects.push_back(obj);
|
||||
|
|
@ -379,7 +383,7 @@ void deferred_app::present()
|
|||
{
|
||||
gfx::deferred_renderer::object obj;
|
||||
obj.mesh = &torus;
|
||||
obj.pre_transform = geom::translation<float, 3>{geom::vector{0.f, 0.f, 4.f}}.affine_matrix();
|
||||
obj.pre_transform = math::translation<float, 3>{math::vector{0.f, 0.f, 4.f}}.affine_matrix();
|
||||
obj.bbox = {{{-1.f, 1.f}, {-1.f, 1.f}, {4 - 0.2f, 4 + 0.2f}}};
|
||||
obj.mat = &sphere_material;
|
||||
objects.push_back(obj);
|
||||
|
|
@ -388,7 +392,7 @@ void deferred_app::present()
|
|||
gfx::deferred_renderer::options options;
|
||||
options.camera = &camera;
|
||||
|
||||
options.clear_color = geom::vector{0.f, 0.f, 0.1f};
|
||||
options.clear_color = math::vector{0.f, 0.f, 0.1f};
|
||||
options.ambient = {1.f, 1.f, 1.f};
|
||||
|
||||
options.directional_lights.emplace_back();
|
||||
|
|
@ -415,7 +419,7 @@ void deferred_app::present()
|
|||
|
||||
for (int i = 0; i < 24; ++i)
|
||||
{
|
||||
float a = (i * geom::pi) / 12.f;
|
||||
float a = (i * math::pi) / 12.f;
|
||||
|
||||
auto & l = options.point_lights.emplace_back();
|
||||
l.color = {15.f, 15.f, 15.f};
|
||||
|
|
@ -433,9 +437,9 @@ void deferred_app::present()
|
|||
float const s = 0.1f;
|
||||
gfx::deferred_renderer::object obj;
|
||||
obj.mesh = &sphere;
|
||||
obj.pre_transform = (geom::translation<float, 3>{l.position - geom::point<float, 3>::zero()}.transform() * geom::scale<float, 3>(s).transform()).affine_matrix();
|
||||
obj.pre_transform = (math::translation<float, 3>{l.position - math::point<float, 3>::zero()}.transform() * math::scale<float, 3>(s).transform()).affine_matrix();
|
||||
obj.bbox = {{{l.position[0] - s, l.position[0] + s}, {l.position[1] - s, l.position[1] + s}, {l.position[2] - s, l.position[2] + s}}};
|
||||
light_materials[i].color = geom::vector{l.color[0], l.color[1], l.color[2], 1.f};
|
||||
light_materials[i].color = math::vector{l.color[0], l.color[1], l.color[2], 1.f};
|
||||
light_materials[i].lit = false;
|
||||
light_materials[i].casts_shadow = false;
|
||||
obj.mat = &light_materials[i];
|
||||
|
|
@ -458,7 +462,7 @@ void deferred_app::present()
|
|||
gfx::render_target target;
|
||||
target.framebuffer = &pre_gamma_framebuffer;
|
||||
target.draw_buffer = gl::COLOR_ATTACHMENT0;
|
||||
target.viewport = {{{0, width()}, {0, height()}}};
|
||||
target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
|
||||
renderer.render(objects, target, options);
|
||||
}
|
||||
|
||||
|
|
@ -466,7 +470,7 @@ void deferred_app::present()
|
|||
gfx::render_target target;
|
||||
target.framebuffer = &pre_fxaa_framebuffer;
|
||||
target.draw_buffer = gl::COLOR_ATTACHMENT0;
|
||||
target.viewport = {{{0, width()}, {0, height()}}};
|
||||
target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
|
||||
gamma_correction.invoke(pre_gamma_texture, target, {1.f / gamma});
|
||||
}
|
||||
|
||||
|
|
@ -474,13 +478,13 @@ void deferred_app::present()
|
|||
gfx::render_target target;
|
||||
target.framebuffer = &gfx::framebuffer::null();
|
||||
target.draw_buffer = gl::BACK_LEFT;
|
||||
target.viewport = {{{0, width()}, {0, height()}}};
|
||||
target.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
|
||||
fxaa.invoke(pre_fxaa_texture, target);
|
||||
}
|
||||
|
||||
{
|
||||
gfx::painter::text_options opts;
|
||||
opts.scale = 2.f;
|
||||
opts.scale = {2.f, 2.f};
|
||||
opts.f = gfx::painter::font::font_9x12;
|
||||
opts.x = gfx::painter::x_align::left;
|
||||
opts.y = gfx::painter::y_align::top;
|
||||
|
|
@ -491,10 +495,15 @@ void deferred_app::present()
|
|||
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
painter.render(geom::window_camera{width(), height()}.transform());
|
||||
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<deferred_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<deferred_app>({.name = "Deferred shading example", .multisampling = 0});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
167
examples/fibonacci_music_box.cpp
Normal file
167
examples/fibonacci_music_box.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#include <psemek/audio/engine.hpp>
|
||||
#include <psemek/audio/wave/silence.hpp>
|
||||
#include <psemek/audio/wave/sine.hpp>
|
||||
#include <psemek/audio/wave/sawtooth.hpp>
|
||||
#include <psemek/audio/wave/square.hpp>
|
||||
#include <psemek/audio/wave/triangle.hpp>
|
||||
#include <psemek/audio/wave/karplus_strong.hpp>
|
||||
#include <psemek/audio/effect/volume.hpp>
|
||||
#include <psemek/audio/effect/fade_in.hpp>
|
||||
#include <psemek/audio/effect/fade_out.hpp>
|
||||
#include <psemek/audio/effect/compressor.hpp>
|
||||
#include <psemek/audio/effect/pause.hpp>
|
||||
#include <psemek/audio/effect/truncate.hpp>
|
||||
#include <psemek/audio/combine/concat.hpp>
|
||||
#include <psemek/audio/combine/loop.hpp>
|
||||
#include <psemek/audio/effect/pitch.hpp>
|
||||
#include <psemek/audio/effect/distortion.hpp>
|
||||
#include <psemek/audio/combine/duplicate.hpp>
|
||||
#include <psemek/audio/combine/stereo.hpp>
|
||||
#include <psemek/audio/combine/mixer.hpp>
|
||||
#include <psemek/audio/midi.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/util/ndarray.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
// Inspired by https://www.youtube.com/watch?v=_aIf4WUCNZU
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
std::vector<std::vector<math::point<int, 2>>> fibonacci_cycles(int n)
|
||||
{
|
||||
util::ndarray<int, 2> cycle({n, n}, -1);
|
||||
|
||||
std::vector<std::vector<math::point<int, 2>>> result;
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
for (int j = 0; j < n; ++j)
|
||||
{
|
||||
if (cycle(i, j) != -1)
|
||||
continue;
|
||||
|
||||
int cycle_id = result.size();
|
||||
auto & current = result.emplace_back();
|
||||
|
||||
math::point p{i, j};
|
||||
while (true)
|
||||
{
|
||||
current.push_back(p);
|
||||
cycle(p[0], p[1]) = cycle_id;
|
||||
p = {p[1], (p[0] + p[1]) % n};
|
||||
|
||||
if (p == math::point{i, j})
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int> major_scale()
|
||||
{
|
||||
return {0, 2, 4, 5, 7, 9, 11};
|
||||
}
|
||||
|
||||
std::vector<int> minor_scale()
|
||||
{
|
||||
return {0, 2, 3, 5, 7, 8, 10};
|
||||
}
|
||||
|
||||
std::shared_ptr<audio::stream> note(int i, float duration)
|
||||
{
|
||||
return audio::fade_in(audio::fade_out(audio::karplus_strong(audio::midi_frequency(69 + i)), duration), duration / 16.f);
|
||||
}
|
||||
|
||||
std::shared_ptr<audio::track> generate(std::vector<int> const & scale, std::vector<math::point<int, 2>> const & cycle, float speed)
|
||||
{
|
||||
auto mixer = audio::make_mixer();
|
||||
|
||||
for (int i = 0; i < cycle.size(); ++i)
|
||||
mixer->add(audio::concat({audio::truncate(audio::silence(), speed * i), note(scale[cycle[i][0] % scale.size()] + 12 * (cycle[i][0] / scale.size()), speed)}));
|
||||
|
||||
return audio::record(mixer, speed * cycle.size());
|
||||
}
|
||||
|
||||
struct fibonacci_music_box_app
|
||||
: app::application_base
|
||||
{
|
||||
fibonacci_music_box_app(options const &, context const &)
|
||||
{
|
||||
auto cycles = fibonacci_cycles(16);
|
||||
|
||||
auto scale = minor_scale();
|
||||
float speed = 0.25f;
|
||||
|
||||
log::info() << "Found cycles of length:";
|
||||
for (int i = 0; i < cycles.size(); ++i)
|
||||
{
|
||||
log::info() << " #" << i << ": " << cycles[i].size();
|
||||
cycles_.push_back(audio::loop(generate(scale, cycles[i], speed)));
|
||||
}
|
||||
|
||||
channels_.resize(cycles_.size());
|
||||
|
||||
engine_ = audio::make_engine();
|
||||
mixer_ = audio::make_mixer();
|
||||
|
||||
auto compressor = audio::compressor(mixer_, audio::from_db(-2.f), 0.95f, 0.002f, 1.f, audio::from_db(1.f));
|
||||
engine_->output()->stream(compressor);
|
||||
|
||||
}
|
||||
|
||||
void on_event(app::key_event const & event) override
|
||||
{
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (event.down && event.key >= app::keycode::NUM_1 && event.key <= app::keycode::NUM_0)
|
||||
{
|
||||
int i;
|
||||
if (event.key == app::keycode::NUM_0)
|
||||
i = 0;
|
||||
else
|
||||
i = 1 + (int(event.key) - int(app::keycode::NUM_1));
|
||||
|
||||
log::info() << "Pressed " << i;
|
||||
|
||||
if (i < channels_.size())
|
||||
{
|
||||
if (channels_[i])
|
||||
{
|
||||
channels_[i]->stop();
|
||||
channels_[i] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
channels_[i] = mixer_->add(cycles_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update() override
|
||||
{}
|
||||
|
||||
void present() override
|
||||
{}
|
||||
|
||||
private:
|
||||
std::unique_ptr<audio::engine> engine_;
|
||||
audio::mixer_ptr mixer_;
|
||||
std::vector<audio::stream_ptr> cycles_;
|
||||
std::vector<audio::channel_ptr> channels_;
|
||||
};
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<fibonacci_music_box_app>({.name = "Fibonacci music box"});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
#include <psemek/gfx/program.hpp>
|
||||
#include <psemek/gfx/texture.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/scale.hpp>
|
||||
#include <psemek/geom/translation.hpp>
|
||||
#include <psemek/geom/easing.hpp>
|
||||
#include <psemek/geom/gradient.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/rotation.hpp>
|
||||
#include <psemek/math/scale.hpp>
|
||||
#include <psemek/math/translation.hpp>
|
||||
#include <psemek/math/easing.hpp>
|
||||
#include <psemek/math/gradient.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform_sphere.hpp>
|
||||
|
|
@ -81,17 +81,17 @@ struct candle_renderer
|
|||
{
|
||||
candle_renderer();
|
||||
|
||||
void add(geom::point<float, 3> const & pos, geom::vector<float, 3> const & dir, float size);
|
||||
void add(math::point<float, 3> const & pos, math::vector<float, 3> const & dir, float size);
|
||||
|
||||
void render(geom::camera const & camera, float time);
|
||||
void render(math::camera const & camera, float time);
|
||||
|
||||
private:
|
||||
struct candle
|
||||
{
|
||||
geom::point<float, 3> pos;
|
||||
geom::vector<float, 3> dir;
|
||||
math::point<float, 3> pos;
|
||||
math::vector<float, 3> dir;
|
||||
float size;
|
||||
geom::vector<float, 2> noise_offset;
|
||||
math::vector<float, 2> noise_offset;
|
||||
};
|
||||
|
||||
std::vector<candle> candles_;
|
||||
|
|
@ -109,7 +109,7 @@ private:
|
|||
|
||||
candle_renderer::candle_renderer()
|
||||
{
|
||||
std::vector<geom::point<float, 3>> vertices;
|
||||
std::vector<math::point<float, 3>> vertices;
|
||||
vertices.push_back({-1.f, 0.f, -1.f});
|
||||
vertices.push_back({ 1.f, 0.f, -1.f});
|
||||
vertices.push_back({ 1.f, 0.f, 1.f});
|
||||
|
|
@ -117,25 +117,25 @@ candle_renderer::candle_renderer()
|
|||
vertices.push_back({ 1.f, 0.f, 1.f});
|
||||
vertices.push_back({-1.f, 0.f, 1.f});
|
||||
|
||||
mesh_.setup<geom::point<float, 3>, gfx::instanced<geom::point<float, 3>>, gfx::instanced<geom::vector<float, 3>>, gfx::instanced<float>, gfx::instanced<geom::vector<float, 2>>>();
|
||||
mesh_.setup<math::point<float, 3>, gfx::instanced<math::point<float, 3>>, gfx::instanced<math::vector<float, 3>>, gfx::instanced<float>, gfx::instanced<math::vector<float, 2>>>();
|
||||
mesh_.load(vertices, gl::TRIANGLES);
|
||||
|
||||
geom::vector<float, 4> c0 {1.f, 0.99f, 0.98f, 1.f};
|
||||
geom::vector<float, 4> c1 {1.f, 0.4f, 0.f, 0.75f};
|
||||
geom::vector<float, 4> c2 {c1[0], c1[1], c1[2], 0.f};
|
||||
math::vector<float, 4> c0 {1.f, 0.99f, 0.98f, 1.f};
|
||||
math::vector<float, 4> c1 {1.f, 0.4f, 0.f, 0.75f};
|
||||
math::vector<float, 4> c2 {c1[0], c1[1], c1[2], 0.f};
|
||||
|
||||
geom::gradient<float> gy{
|
||||
math::gradient<float> gy{
|
||||
std::make_pair(0.f, 0.f),
|
||||
geom::easing_type::quadratic_in,
|
||||
math::easing_type::quadratic_in,
|
||||
std::make_pair(1.f, 2.f),
|
||||
};
|
||||
|
||||
geom::gradient<float, geom::vector<float, 4>> gc
|
||||
math::gradient<float, math::vector<float, 4>> gc
|
||||
{
|
||||
std::make_pair(0.1f, c0),
|
||||
geom::easing_type::linear,
|
||||
math::easing_type::linear,
|
||||
std::pair{0.8f, c1},
|
||||
geom::easing_type::cubic,
|
||||
math::easing_type::cubic,
|
||||
std::pair{1.2f, c2}
|
||||
};
|
||||
|
||||
|
|
@ -172,7 +172,7 @@ candle_renderer::candle_renderer()
|
|||
random::generator rng;
|
||||
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||
|
||||
util::array<geom::vector<float, 2>, 2> grad({16, 16});
|
||||
util::ndarray<math::vector<float, 2>, 2> grad({16, 16});
|
||||
|
||||
for (auto & v : grad) v = d(rng);
|
||||
pcg::perlin<float, 2> perlinx(grad, pcg::seamless);
|
||||
|
|
@ -180,13 +180,13 @@ candle_renderer::candle_renderer()
|
|||
for (auto & v : grad) v = d(rng);
|
||||
pcg::perlin<float, 2> perliny(grad, pcg::seamless);
|
||||
|
||||
gfx::basic_pixmap<geom::vector<std::uint8_t, 2>> pm({512, 512});
|
||||
gfx::basic_pixmap<math::vector<std::uint8_t, 2>> pm({512, 512});
|
||||
for (auto idx : pm.indices())
|
||||
{
|
||||
float x = (0.5f + idx[0]) / pm.width();
|
||||
float y = (0.5f + idx[1]) / pm.height();
|
||||
|
||||
pm(idx) = gfx::to_coloru8(geom::vector{perlinx({x, y}), perliny({x, y})});
|
||||
pm(idx) = gfx::to_coloru8(math::vector{perlinx({x, y}), perliny({x, y})});
|
||||
}
|
||||
noise_texture_.load(pm);
|
||||
noise_texture_.linear_filter();
|
||||
|
|
@ -196,11 +196,11 @@ candle_renderer::candle_renderer()
|
|||
}
|
||||
}
|
||||
|
||||
void candle_renderer::add(geom::point<float, 3> const & pos, geom::vector<float, 3> const & dir, float size)
|
||||
void candle_renderer::add(math::point<float, 3> const & pos, math::vector<float, 3> const & dir, float size)
|
||||
{
|
||||
random::generator rng{random::device{}};
|
||||
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||
geom::vector<float, 2> noise_offset;
|
||||
math::vector<float, 2> noise_offset;
|
||||
while (true)
|
||||
{
|
||||
noise_offset = d(rng);
|
||||
|
|
@ -215,7 +215,7 @@ void candle_renderer::add(geom::point<float, 3> const & pos, geom::vector<float,
|
|||
instances_need_update_ = true;
|
||||
}
|
||||
|
||||
void candle_renderer::render(geom::camera const & camera, float time)
|
||||
void candle_renderer::render(math::camera const & camera, float time)
|
||||
{
|
||||
if (instances_need_update_) update_instances();
|
||||
|
||||
|
|
@ -236,10 +236,10 @@ void candle_renderer::update_instances()
|
|||
{
|
||||
struct instance
|
||||
{
|
||||
geom::point<float, 3> pos;
|
||||
geom::vector<float, 3> dir;
|
||||
math::point<float, 3> pos;
|
||||
math::vector<float, 3> dir;
|
||||
float size;
|
||||
geom::vector<float, 2> noise_offset;
|
||||
math::vector<float, 2> noise_offset;
|
||||
};
|
||||
|
||||
std::vector<instance> instances;
|
||||
|
|
@ -254,19 +254,18 @@ void candle_renderer::update_instances()
|
|||
}
|
||||
|
||||
struct fire_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
geom::spherical_camera camera;
|
||||
math::spherical_camera camera;
|
||||
|
||||
candle_renderer candles;
|
||||
|
||||
util::clock<std::chrono::duration<float>> clock;
|
||||
float time = 0.f;
|
||||
|
||||
fire_app()
|
||||
: app("Fire")
|
||||
fire_app(options const &, context const &)
|
||||
{
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.fov_y = math::rad(45.f);
|
||||
camera.near_clip = 0.01f;
|
||||
camera.far_clip = 1000.f;
|
||||
camera.target = {0.f, 0.f, 0.f};
|
||||
|
|
@ -277,32 +276,36 @@ struct fire_app
|
|||
candles.add({0.f, 0.f, 0.f}, {0.f, 0.f, 1.f}, 1.f);
|
||||
}
|
||||
|
||||
void on_resize(int width, int height) override
|
||||
void on_event(app::resize_event const & event) override
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
camera.set_fov(camera.fov_y, (1.f * width) / height);
|
||||
app::application_base::on_event(event);
|
||||
camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
|
||||
}
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||
void on_event(app::mouse_move_event const & event) override
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
auto const old_mouse = state().mouse;
|
||||
|
||||
if (is_middle_button_down())
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (state().mouse_button_down.contains(app::mouse_button::middle))
|
||||
{
|
||||
camera.azimuthal_angle -= dx * 0.01f;
|
||||
camera.elevation_angle += dy * 0.01f;
|
||||
auto const delta = event.position - old_mouse;
|
||||
|
||||
camera.azimuthal_angle -= delta[0] * 0.01f;
|
||||
camera.elevation_angle += delta[1] * 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
void on_mouse_wheel(int delta) override
|
||||
void on_event(app::mouse_wheel_event const & event) override
|
||||
{
|
||||
app::on_mouse_wheel(delta);
|
||||
camera.distance *= std::pow(0.8f, delta);
|
||||
app::application_base::on_event(event);
|
||||
camera.distance *= std::pow(0.8f, event.delta);
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
if (!is_key_down(SDLK_SPACE))
|
||||
if (!state().key_down.contains(app::keycode::SPACE))
|
||||
time += clock.restart().count();
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +322,12 @@ struct fire_app
|
|||
|
||||
};
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<fire_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<fire_app>({.name = "Fire example", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,636 +0,0 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
#include <psemek/gfx/program.hpp>
|
||||
#include <psemek/gfx/texture.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/gfx/framebuffer.hpp>
|
||||
#include <psemek/gfx/renderbuffer.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/scale.hpp>
|
||||
#include <psemek/geom/translation.hpp>
|
||||
#include <psemek/geom/easing.hpp>
|
||||
#include <psemek/geom/gradient.hpp>
|
||||
#include <psemek/geom/permutation.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform_sphere.hpp>
|
||||
#include <psemek/random/uniform_ball.hpp>
|
||||
#include <psemek/random/uniform_box.hpp>
|
||||
#include <psemek/pcg/perlin.hpp>
|
||||
#include <psemek/pcg/sample.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/assert.hpp>
|
||||
#include <psemek/util/moving_average.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/util/pretty_print.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
static char const ground_vs[] =
|
||||
R"(#version 330
|
||||
|
||||
uniform mat4 u_transform;
|
||||
|
||||
layout (location = 0) in vec4 in_position;
|
||||
layout (location = 1) in vec4 in_color;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_transform * in_position;
|
||||
color = in_color;
|
||||
}
|
||||
)";
|
||||
|
||||
static char const ground_fs[] =
|
||||
R"(#version 330
|
||||
|
||||
in vec4 color;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
out_color = color;
|
||||
}
|
||||
)";
|
||||
|
||||
static char const grass_vs[] =
|
||||
R"(#version 330
|
||||
|
||||
uniform mat4 u_transform;
|
||||
uniform mat4 u_tile_transform;
|
||||
uniform float u_height00;
|
||||
uniform float u_height01;
|
||||
uniform float u_height10;
|
||||
uniform float u_height11;
|
||||
uniform sampler1D u_texture;
|
||||
|
||||
layout (location = 0) in vec4 in_position;
|
||||
layout (location = 1) in float in_t;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 p = (u_tile_transform * vec4(in_position.xy * 2.0 - vec2(0.5), 0.0, 1.0)).xy;
|
||||
|
||||
vec2 o = (u_tile_transform * vec4(0.5, 0.5, 0.0, 1.0)).xy;
|
||||
|
||||
o = vec2(floor(o.x), floor(o.y));
|
||||
|
||||
vec2 d = p - o;
|
||||
|
||||
float h = mix(mix(u_height00, u_height01, d.x), mix(u_height10, u_height11, d.x), d.y);
|
||||
|
||||
gl_Position = u_transform * vec4(p, in_position.z * h, 1.0);
|
||||
// color = mix(vec4(0.0, 0.0, 0.0, 1.0), texture(u_texture, in_t), in_position.z);
|
||||
color = texture(u_texture, in_t);
|
||||
}
|
||||
)";
|
||||
|
||||
static char const grass_fs[] =
|
||||
R"(#version 330
|
||||
|
||||
in vec4 color;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
out_color = color;
|
||||
}
|
||||
)";
|
||||
|
||||
static char const grass_slice_vs[] =
|
||||
R"(#version 330
|
||||
|
||||
uniform mat4 u_transform;
|
||||
uniform mat4 u_tile_transform;
|
||||
|
||||
uniform float u_density00;
|
||||
uniform float u_density01;
|
||||
uniform float u_density10;
|
||||
uniform float u_density11;
|
||||
|
||||
layout (location = 0) in vec4 in_position;
|
||||
layout (location = 1) in vec2 in_texcoord;
|
||||
|
||||
out vec3 texcoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 p = (u_tile_transform * vec4(in_position.xy, 0.0, 1.0)).xy;
|
||||
|
||||
vec2 o = (u_tile_transform * vec4(0.5, 0.5, 0.0, 1.0)).xy;
|
||||
|
||||
o = vec2(floor(o.x), floor(o.y));
|
||||
|
||||
vec2 d = p - o;
|
||||
|
||||
float level = mix(mix(u_density00, u_density01, d.x), mix(u_density10, u_density11, d.x), d.y);
|
||||
|
||||
gl_Position = u_transform * vec4(p, in_position.z, 1.0);
|
||||
texcoord = vec3(in_texcoord, level);
|
||||
}
|
||||
)";
|
||||
|
||||
static char const grass_slice_fs[] =
|
||||
R"(#version 330
|
||||
|
||||
uniform sampler2DArray u_texture;
|
||||
|
||||
in vec3 texcoord;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
float l0 = floor(texcoord.z);
|
||||
float l1 = l0 + 1;
|
||||
float t = texcoord.z - l0;
|
||||
vec4 c0 = texture(u_texture, vec3(texcoord.xy, l0));
|
||||
vec4 c1 = texture(u_texture, vec3(texcoord.xy, l1));
|
||||
vec4 c = mix(c0, c1, t);
|
||||
// vec4 c = c0;
|
||||
out_color = vec4(c.rgb / c.a, c.a);
|
||||
}
|
||||
)";
|
||||
|
||||
struct grass_app
|
||||
: app::app
|
||||
{
|
||||
geom::free_camera camera;
|
||||
|
||||
int size = 64;
|
||||
|
||||
int const density_level_count = 8;
|
||||
|
||||
pcg::perlin<float, 2> density;
|
||||
|
||||
gfx::program ground_program{ground_vs, ground_fs};
|
||||
gfx::mesh ground_mesh;
|
||||
|
||||
gfx::program grass_program{grass_vs, grass_fs};
|
||||
gfx::mesh grass_mesh;
|
||||
gfx::texture_1d grass_texture;
|
||||
|
||||
gfx::program grass_slice_program{grass_slice_vs, grass_slice_fs};
|
||||
gfx::texture_2d_array grass_slice_z_texture;
|
||||
gfx::mesh grass_slice_z_mesh;
|
||||
|
||||
gfx::framebuffer grass_slice_framebuffer;
|
||||
gfx::renderbuffer grass_slice_renderbuffer;
|
||||
|
||||
geom::matrix<float, 4, 4> random_transform[8];
|
||||
util::array<int, 2> random_transform_index;
|
||||
|
||||
util::clock<> frame_clock;
|
||||
util::moving_average<double> frame_time{64};
|
||||
|
||||
util::clock<std::chrono::duration<float>> update_clock;
|
||||
|
||||
gfx::painter painter;
|
||||
|
||||
grass_app()
|
||||
: app("Grass", 4)
|
||||
{
|
||||
vsync(false);
|
||||
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.near_clip = 0.1f;
|
||||
camera.far_clip = 1000.f;
|
||||
camera.pos = {0.5f, 0.5f, 1.5f};
|
||||
camera.rotateYZ(geom::rad(-90.f));
|
||||
|
||||
init_ground();
|
||||
init_grass();
|
||||
init_grass_slices();
|
||||
|
||||
random::generator rng;
|
||||
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||
|
||||
util::array<geom::vector<float, 2>, 2> grad({size / 8 + 1, size / 8 + 1});
|
||||
for (auto & v : grad) v = d(rng);
|
||||
density = pcg::perlin<float, 2>(std::move(grad));
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if (i < 4)
|
||||
random_transform[i] = geom::matrix<float, 4, 4>::identity();
|
||||
else
|
||||
random_transform[i] = geom::swap<float, 3>(0, 1).homogeneous_matrix();
|
||||
|
||||
random_transform[i] = random_transform[i]
|
||||
* geom::translation<float, 3>(geom::vector{0.5f, 0.5f, 0.f}).homogeneous_matrix()
|
||||
* geom::plane_rotation<float, 3>(0, 1, i * geom::pi / 2.f).homogeneous_matrix()
|
||||
* geom::translation<float, 3>(geom::vector{-0.5f, -0.5f, 0.f}).homogeneous_matrix();
|
||||
}
|
||||
|
||||
random_transform_index.resize({size, size});
|
||||
for (auto & i : random_transform_index)
|
||||
i = random::uniform_int_distribution<int>{0, 7}(rng);
|
||||
}
|
||||
|
||||
void init_ground()
|
||||
{
|
||||
ground_mesh.setup<geom::point<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> pos;
|
||||
gfx::color_rgba color;
|
||||
};
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
std::vector<geom::triangle<std::uint32_t>> triangles;
|
||||
|
||||
gfx::color_rgba ground_color{47, 23, 11, 255};
|
||||
|
||||
for (int x = 0; x < size; ++x)
|
||||
{
|
||||
for (int y = 0; y < size; ++y)
|
||||
{
|
||||
std::uint32_t base = vertices.size();
|
||||
|
||||
float d = 0.0f;
|
||||
|
||||
vertices.push_back({{x + d, y + d, 0.f}, ground_color});
|
||||
vertices.push_back({{x + 1 - d, y + d, 0.f}, ground_color});
|
||||
vertices.push_back({{x + d, y + 1 - d, 0.f}, ground_color});
|
||||
vertices.push_back({{x + 1 - d, y + 1 - d, 0.f}, ground_color});
|
||||
|
||||
triangles.push_back({base + 0, base + 1, base + 2});
|
||||
triangles.push_back({base + 2, base + 1, base + 3});
|
||||
}
|
||||
}
|
||||
|
||||
ground_mesh.load(vertices, triangles, gl::STATIC_DRAW);
|
||||
}
|
||||
|
||||
void init_grass()
|
||||
{
|
||||
frame_clock.restart();
|
||||
struct vertex
|
||||
{
|
||||
geom::point<std::uint16_t, 3> pos;
|
||||
std::uint8_t t;
|
||||
std::uint8_t density;
|
||||
|
||||
vertex(geom::point<float, 3> const & p, float t, float d)
|
||||
{
|
||||
for (std::size_t i = 0; i < 2; ++i)
|
||||
pos[i] = geom::clamp(std::round((p[i] + 0.5f) / 2.f * 65535.f), {0.f, 65535.f});
|
||||
|
||||
pos[2] = geom::clamp(std::round(p[2] * 65535.f), {0.f, 65535.f});
|
||||
|
||||
this->t = geom::clamp(std::round(t * 255.f), {0.f, 255.f});
|
||||
this->density = geom::clamp(std::round(d * 255.f), {0.f, 255.f});
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
geom::gradient<float, gfx::color_4f> g
|
||||
{
|
||||
std::make_pair(-1.f, gfx::color_4f{0.f, 0.2f, 0.f, 1.f}),
|
||||
geom::easing_type::linear,
|
||||
std::pair{0.f, gfx::color_4f{0.4f, 0.65f, 0.35f, 1.f}},
|
||||
geom::easing_type::linear,
|
||||
std::pair{1.f, gfx::color_4f{0.7f, 0.75f, 0.2f, 1.f}}
|
||||
};
|
||||
|
||||
util::array<gfx::color_rgba, 1> pm({256});
|
||||
for (std::size_t i = 0; i < pm.width(); ++i)
|
||||
{
|
||||
pm(i) = gfx::to_coloru8(g((i + 0.5f) / pm.width()));
|
||||
}
|
||||
|
||||
grass_texture.load(pm);
|
||||
grass_texture.linear_filter();
|
||||
grass_texture.generate_mipmap();
|
||||
}
|
||||
|
||||
random::generator rng;
|
||||
|
||||
std::size_t memory = 0;
|
||||
|
||||
grass_mesh.setup<gfx::normalized<geom::point<std::uint16_t, 3>>, gfx::normalized<std::uint8_t>, gfx::normalized<std::uint8_t>>();
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
std::vector<geom::triangle<std::uint32_t>> triangles;
|
||||
random::uniform_box_point_distribution<float, 2> d_origin({{{0.f, 1.f}, {0.f, 1.f}}});
|
||||
random::uniform_sphere_vector_distribution<float, 2> d_orientation;
|
||||
random::uniform_real_distribution<float> d_width{1.f / 64.f, 1.f / 128.f};
|
||||
random::uniform_real_distribution<float> d_height{0.25f, 1.f};
|
||||
random::uniform_real_distribution<float> d_density{0.f, 1.f};
|
||||
|
||||
for (int blade = 0; blade < 4096; ++blade)
|
||||
{
|
||||
int const segments = 8;
|
||||
float const max_lean = 0.2f;
|
||||
|
||||
int tx = (blade % 64) % 8;
|
||||
int ty = (blade % 64) / 8;
|
||||
|
||||
auto o = d_origin(rng);
|
||||
o[0] = o[0] / 8.f + tx / 8.f;
|
||||
o[1] = o[1] / 8.f + ty / 8.f;
|
||||
|
||||
auto r = d_orientation(rng);
|
||||
auto s = d_width(rng) * 0.5f;
|
||||
auto h = d_height(rng);
|
||||
|
||||
auto n = geom::ort(r);
|
||||
|
||||
auto color = random::uniform_real_distribution<float>{0.f, 1.f}(rng);
|
||||
|
||||
float den = d_density(rng);
|
||||
|
||||
auto get = [&](float x, float z) -> vertex
|
||||
{
|
||||
assert(z >= 0.f);
|
||||
assert(z <= 1.f);
|
||||
float y = (1.f - std::sqrt(1.f - z * z)) * max_lean;
|
||||
|
||||
float w = 1.f - z / h;
|
||||
|
||||
return {geom::point{o[0] + r[0] * s * x * w + n[0] * y, o[1] + r[1] * s * x * w + n[1] * y, z}, color, den};
|
||||
};
|
||||
|
||||
|
||||
for (int s = 0; s <= segments; ++s)
|
||||
{
|
||||
float t = (s * 1.f) / segments;
|
||||
t = geom::easing(geom::easing_type::quadratic_out, t);
|
||||
float z = h * t;
|
||||
std::uint32_t base = vertices.size();
|
||||
|
||||
if (s < segments)
|
||||
{
|
||||
vertices.push_back(get(-1.f, z));
|
||||
vertices.push_back(get( 1.f, z));
|
||||
|
||||
if (s > 0)
|
||||
{
|
||||
triangles.push_back({base - 2, base - 1, base});
|
||||
triangles.push_back({base, base - 1, base + 1});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
vertices.push_back(get(0.f, z));
|
||||
triangles.push_back({base - 2, base - 1, base});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grass_mesh.load(vertices, triangles, gl::STATIC_DRAW);
|
||||
|
||||
memory += (vertices.size() * sizeof(vertices[0]) + triangles.size() * sizeof(triangles[0]));
|
||||
|
||||
log::info() << memory << " bytes";
|
||||
log::info() << vertices.size() << " vertices";
|
||||
log::info() << frame_clock.count();
|
||||
}
|
||||
|
||||
void init_grass_slices()
|
||||
{
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> position;
|
||||
geom::vector<float, 2> texcoord;
|
||||
};
|
||||
|
||||
int const slice_count = 4;
|
||||
float slice_width = 1.f / slice_count;
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
vertices.push_back({{0.f, 0.f, slice_width}, {0.f, 0.f}});
|
||||
vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}});
|
||||
vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}});
|
||||
vertices.push_back({{0.f, 1.f, slice_width}, {0.f, 1.f}});
|
||||
vertices.push_back({{1.f, 0.f, slice_width}, {1.f, 0.f}});
|
||||
vertices.push_back({{1.f, 1.f, slice_width}, {1.f, 1.f}});
|
||||
|
||||
grass_slice_z_mesh.setup<geom::point<float, 3>, geom::vector<float, 2>>();
|
||||
grass_slice_z_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
|
||||
|
||||
std::size_t slice_resolution = 256;
|
||||
|
||||
grass_slice_z_texture.load<gfx::color_rgba>({slice_resolution, slice_resolution, density_level_count});
|
||||
grass_slice_renderbuffer.storage(gl::DEPTH24_STENCIL8, {slice_resolution, slice_resolution});
|
||||
for (int d = 0; d < density_level_count; ++d)
|
||||
{
|
||||
grass_slice_framebuffer.color(grass_slice_z_texture, d);
|
||||
grass_slice_framebuffer.depth(grass_slice_renderbuffer);
|
||||
grass_slice_framebuffer.assert_complete();
|
||||
gl::Viewport(0, 0, slice_resolution, slice_resolution);
|
||||
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||||
gl::Enable(gl::DEPTH_TEST);
|
||||
gl::DepthFunc(gl::LEQUAL);
|
||||
grass_program.bind();
|
||||
grass_program["u_tile_transform"] = geom::matrix<float, 4, 4>::identity();
|
||||
grass_program["u_height00"] = 1.f;
|
||||
grass_program["u_height01"] = 1.f;
|
||||
grass_program["u_height10"] = 1.f;
|
||||
grass_program["u_height11"] = 1.f;
|
||||
grass_program["u_texture"] = 0;
|
||||
grass_texture.bind();
|
||||
for (int x = -1; x <= 1; ++x)
|
||||
{
|
||||
for (int y = -1; y <= 1; ++y)
|
||||
{
|
||||
grass_program["u_transform"] = geom::translation<float, 3>({-1.f + 2.f * x, -1.f + 2.f * y, 0.f}).homogeneous_matrix() * geom::scale<float, 3>({2.f, 2.f, 1.f}).homogeneous_matrix();
|
||||
grass_mesh.draw(0, (grass_mesh.index_count() * (d + 1)) / density_level_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
gfx::framebuffer::null().bind();
|
||||
|
||||
grass_slice_z_texture.linear_filter();
|
||||
grass_slice_z_texture.anisotropy();
|
||||
grass_slice_z_texture.generate_mipmap();
|
||||
grass_slice_z_texture.clamp();
|
||||
// grass_slice_z_texture.bind();
|
||||
// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
|
||||
// gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR);
|
||||
}
|
||||
|
||||
void on_resize(int width, int height) override
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
camera.set_fov(camera.fov_y, (1.f * width) / height);
|
||||
}
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
|
||||
if (is_middle_button_down())
|
||||
{
|
||||
camera.rotateZX(0.01f * dx);
|
||||
camera.rotateYZ(0.01f * dy);
|
||||
}
|
||||
}
|
||||
|
||||
void on_mouse_wheel(int delta) override
|
||||
{
|
||||
app::on_mouse_wheel(delta);
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
float dt = update_clock.restart().count();
|
||||
|
||||
auto d = camera.direction();
|
||||
float s = 20.f;
|
||||
|
||||
if (is_key_down(SDLK_LSHIFT))
|
||||
s = 2.5f;
|
||||
|
||||
if (is_key_down(SDLK_SPACE))
|
||||
{
|
||||
d[2] = 0.f;
|
||||
d = geom::normalized(d);
|
||||
}
|
||||
|
||||
auto n = geom::normalized(geom::cross(d, geom::vector{0.f, 0.f, 1.f}));
|
||||
|
||||
if (is_key_down(SDLK_w))
|
||||
{
|
||||
camera.pos += d * dt * s;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_s))
|
||||
{
|
||||
camera.pos -= d * dt * s;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_a))
|
||||
{
|
||||
camera.pos -= n * dt * s;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_d))
|
||||
{
|
||||
camera.pos += n * dt * s;
|
||||
}
|
||||
}
|
||||
|
||||
void present() override
|
||||
{
|
||||
gl::ClearColor(0.8f, 0.8f, 1.f, 1.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||||
|
||||
gl::Enable(gl::DEPTH_TEST);
|
||||
gl::DepthFunc(gl::LEQUAL);
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
auto camera_transform = camera.transform();
|
||||
|
||||
ground_program.bind();
|
||||
ground_program["u_transform"] = camera_transform;
|
||||
ground_mesh.draw();
|
||||
|
||||
grass_program.bind();
|
||||
grass_program["u_texture"] = 0;
|
||||
grass_texture.bind();
|
||||
grass_program["u_transform"] = camera_transform;
|
||||
|
||||
std::size_t triangles = 0;
|
||||
|
||||
for (int x = 0; x < size; ++x)
|
||||
{
|
||||
for (int y = 0; y < size; ++y)
|
||||
{
|
||||
if (x < size / 2) continue;
|
||||
|
||||
grass_program["u_tile_transform"] = geom::translation<float, 3>(geom::vector{x, y, 0.f}).homogeneous_matrix()
|
||||
* random_transform[random_transform_index(x, y)];
|
||||
|
||||
float d = density({(x + 0.5f) / size, (y + 0.5f) / size});
|
||||
|
||||
int i = std::floor(d * density_level_count);
|
||||
if (i > density_level_count - 1) i = density_level_count - 1;
|
||||
|
||||
{
|
||||
float h00 = density({(x + 0.f) / size, (y + 0.f) / size});
|
||||
float h01 = density({(x + 1.f) / size, (y + 0.f) / size});
|
||||
float h10 = density({(x + 0.f) / size, (y + 1.f) / size});
|
||||
float h11 = density({(x + 1.f) / size, (y + 1.f) / size});
|
||||
|
||||
grass_program["u_height00"] = h00;
|
||||
grass_program["u_height01"] = h01;
|
||||
grass_program["u_height10"] = h10;
|
||||
grass_program["u_height11"] = h11;
|
||||
grass_mesh.draw(0, ((i + 1) * grass_mesh.index_count()) / density_level_count);
|
||||
|
||||
triangles += (((i + 1) * grass_mesh.index_count()) / density_level_count) / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grass_slice_program.bind();;
|
||||
grass_slice_program["u_transform"] = camera_transform;
|
||||
grass_slice_program["u_texture"] = 0;
|
||||
grass_slice_z_texture.bind();
|
||||
|
||||
for (int x = 0; x < size; ++x)
|
||||
{
|
||||
for (int y = 0; y < size; ++y)
|
||||
{
|
||||
if (x >= size / 2) continue;
|
||||
|
||||
grass_slice_program["u_tile_transform"] = geom::translation<float, 3>(geom::vector{x, y, 0.f}).homogeneous_matrix()
|
||||
* random_transform[random_transform_index(x, y)];
|
||||
|
||||
float d00 = density({(x + 0.f) / size, (y + 0.f) / size}) * density_level_count;
|
||||
float d01 = density({(x + 1.f) / size, (y + 0.f) / size}) * density_level_count;
|
||||
float d10 = density({(x + 0.f) / size, (y + 1.f) / size}) * density_level_count;
|
||||
float d11 = density({(x + 1.f) / size, (y + 1.f) / size}) * density_level_count;
|
||||
|
||||
grass_slice_program["u_density00"] = d00;
|
||||
grass_slice_program["u_density01"] = d01;
|
||||
grass_slice_program["u_density10"] = d10;
|
||||
grass_slice_program["u_density11"] = d11;
|
||||
|
||||
grass_slice_z_mesh.draw();
|
||||
}
|
||||
}
|
||||
|
||||
frame_time.push(frame_clock.restart().count());
|
||||
|
||||
{
|
||||
gfx::painter::text_options opts;
|
||||
opts.x = gfx::painter::x_align::left;
|
||||
opts.y = gfx::painter::y_align::top;
|
||||
opts.f = gfx::painter::font::font_9x12;
|
||||
opts.scale = 2.f;
|
||||
opts.c = {0, 0, 0, 255};
|
||||
|
||||
painter.text({0.f, 0.f}, util::to_string("FPS: ", std::round(1.0 / frame_time.average())), opts);
|
||||
painter.text({0.f, 24.f}, util::to_string("Camera: ", camera.position()), opts);
|
||||
painter.text({0.f, 48.f}, util::to_string("Triangles: ", triangles), opts);
|
||||
}
|
||||
|
||||
gl::Disable(gl::DEPTH_TEST);
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
geom::window_camera camera;
|
||||
camera.width = width();
|
||||
camera.height = height();
|
||||
painter.render(camera.transform());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
return app::main<grass_app>();
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
BIN
examples/heightmap_seed_0.png
Normal file
BIN
examples/heightmap_seed_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
examples/heightmap_seed_1.png
Normal file
BIN
examples/heightmap_seed_1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
BIN
examples/heightmap_seed_2.png
Normal file
BIN
examples/heightmap_seed_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
BIN
examples/heightmap_seed_3.png
Normal file
BIN
examples/heightmap_seed_3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
|
|
@ -1,5 +1,7 @@
|
|||
#include <psemek/parser/primitives.hpp>
|
||||
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
|
@ -74,14 +76,23 @@ Stream & operator << (Stream & s, std::variant<T, Ts...> const & v)
|
|||
return s;
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
using namespace psemek::parser;
|
||||
|
||||
auto const p = map(concat(integer<int>, ws, one_of(ch('+'), ch('-')), ws, integer<int>), [](auto const & t){
|
||||
auto id = [](auto x){ return x; };
|
||||
return std::make_tuple(std::get<0>(t), std::visit(id, std::get<2>(t)), std::get<4>(t));
|
||||
});
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory({.name = "Parser example"}, [](auto const & ...){
|
||||
using namespace psemek::parser;
|
||||
|
||||
auto const p = map(concat(integer<int>, ws, one_of(ch('+'), ch('-')), ws, integer<int>), [](auto const & t){
|
||||
auto id = [](auto x){ return x; };
|
||||
return std::make_tuple(std::get<0>(t), std::visit(id, std::get<2>(t)), std::get<4>(t));
|
||||
});
|
||||
|
||||
std::cout << p.parse("45 + 67") << std::endl;
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
std::cout << p.parse("45 + 67") << std::endl;
|
||||
}
|
||||
|
|
|
|||
441
examples/particle_life_2d.cpp
Normal file
441
examples/particle_life_2d.cpp
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/contains.hpp>
|
||||
#include <psemek/cg/kdtree.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
#include <psemek/random/uniform_box.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/ndarray.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
struct particle
|
||||
{
|
||||
math::point<float, 2> position;
|
||||
math::vector<float, 2> velocity;
|
||||
float mass;
|
||||
float radius;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct particle_life_2d_app
|
||||
: app::application_base
|
||||
{
|
||||
particle_life_2d_app(options const &, context const &)
|
||||
{
|
||||
float S = 5.f;
|
||||
float W = 160.f * S;
|
||||
float H = 90.f * S;
|
||||
area_ = {{{-W, W}, {-H, H}}};
|
||||
|
||||
types_ = 10;
|
||||
|
||||
auto seed = random::device{}();
|
||||
|
||||
// Explosions
|
||||
// types_ = 6;
|
||||
// seed = 3940378904;
|
||||
|
||||
// Cells
|
||||
// types_ = 8;
|
||||
// seed = 3071915692;
|
||||
|
||||
// Cells v2
|
||||
// types_ = 6;
|
||||
// seed = 3337663839;
|
||||
|
||||
// Cool cells!
|
||||
types_ = 10;
|
||||
seed = 388834085;
|
||||
|
||||
// One cell, use central gravity
|
||||
// types_ = 10;
|
||||
// seed = 175098945;
|
||||
|
||||
// Mitochondria cell
|
||||
// types_ = 10;
|
||||
// seed = 1763331406;
|
||||
|
||||
// Just fun
|
||||
// types_ = 10;
|
||||
// seed = 1676103531;
|
||||
|
||||
// Another cell
|
||||
// types_ = 10;
|
||||
// seed = 2396049785;
|
||||
|
||||
log::info() << "Seed: " << seed;
|
||||
|
||||
random::generator rng{seed, 0};
|
||||
|
||||
random::uniform_distribution<int> dcolor(63, 255);
|
||||
random::uniform_distribution<int> dtype(0, types_ - 1);
|
||||
random::uniform_box_point_distribution<float, 2> darea(area_);
|
||||
|
||||
for (int i = 0; i < types_; ++i)
|
||||
colors_.push_back({dcolor(rng), dcolor(rng), dcolor(rng), 255});
|
||||
|
||||
for (int i = 0; i < 4 * 1024; ++i)
|
||||
{
|
||||
particles_.push_back({
|
||||
.position = darea(rng),
|
||||
.velocity = {0.f, 0.f},
|
||||
.mass = 1.f,
|
||||
.radius = 1.f,
|
||||
.type = dtype(rng),
|
||||
// .type = random::uniform_from(rng, {4, 6}),
|
||||
});
|
||||
}
|
||||
|
||||
auto dforce = [](auto & rng)
|
||||
{
|
||||
return random::uniform(rng, 20.f, 100.f) * (random::uniform<bool>(rng) ? 1.f : -1.f);
|
||||
};
|
||||
|
||||
random::uniform_distribution<float> ddistance(3.f, 20.f);
|
||||
|
||||
force_constants_.resize({types_, types_});
|
||||
force_distance_.resize({types_, types_});
|
||||
collision_distance_.resize({types_, types_});
|
||||
|
||||
for (int i = 0; i < types_; ++i)
|
||||
for (int j = 0; j < types_; ++j)
|
||||
force_constants_(i, j) = dforce(rng);
|
||||
|
||||
for (int i = 0; i < types_; ++i)
|
||||
for (int j = 0; j < types_; ++j)
|
||||
force_distance_(i, j) = ddistance(rng);
|
||||
|
||||
for (int i = 0; i < types_; ++i)
|
||||
for (int j = 0; j < types_; ++j)
|
||||
collision_distance_(i, j) = random::uniform(rng, 0.125f, 0.75f) * force_distance_(i, j);
|
||||
|
||||
// for (int i = 0; i < types_; ++i)
|
||||
// for (int j = 0; j < types_; ++j)
|
||||
// force_constants_(i, j) = -100.f * ((i == j) ? 1.f : 2.f);
|
||||
|
||||
max_force_distance_.resize(types_, 2.f);
|
||||
for (int i = 0; i < types_; ++i)
|
||||
for (int j = 0; j < types_; ++j)
|
||||
math::make_max(max_force_distance_[i], force_distance_(i, j));
|
||||
}
|
||||
|
||||
void on_event(app::mouse_wheel_event const & event) override
|
||||
{
|
||||
app::application_base::on_event(event);
|
||||
|
||||
scale_target_ *= std::pow(1.25f, -event.delta);
|
||||
}
|
||||
|
||||
void on_event(app::key_event const & event) override
|
||||
{
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
paused_ ^= true;
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
float const dt = 0.02f;
|
||||
|
||||
scale_ += (scale_target_ - scale_) * (- std::expm1(- 20.f * dt));
|
||||
|
||||
auto visible_area = area_;
|
||||
visible_area = math::expand(visible_area, 0.5f * (scale_ - 1.f) * visible_area.dimensions());
|
||||
|
||||
float window_aspect_ratio = state().size[0] * 1.f / state().size[1];
|
||||
float area_aspect_ratio = visible_area[0].length() / visible_area[1].length();
|
||||
|
||||
if (area_aspect_ratio < window_aspect_ratio)
|
||||
{
|
||||
view_area_[1] = visible_area[1];
|
||||
view_area_[0] = math::expand(math::interval<float>::singleton(visible_area[0].center()), visible_area[1].length() * window_aspect_ratio * 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
view_area_[0] = visible_area[0];
|
||||
view_area_[1] = math::expand(math::interval<float>::singleton(visible_area[1].center()), visible_area[0].length() / window_aspect_ratio * 0.5f);
|
||||
}
|
||||
|
||||
if (!paused_)
|
||||
for (int step = 0; step < 5; ++step)
|
||||
{
|
||||
cg::kdtree<float, 2, int> kdtree;
|
||||
|
||||
for (int i = 0; i < particles_.size(); ++i)
|
||||
kdtree.insert({particles_[i].position, i});
|
||||
|
||||
float const collision_strength = 200.f;
|
||||
|
||||
mouseover_particle_ = std::nullopt;
|
||||
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||
{
|
||||
math::point<float, 2> m;
|
||||
m[0] = math::lerp(view_area_[0], state().mouse[0] * 1.f / state().size[0]);
|
||||
m[1] = math::lerp(view_area_[1], 1.f - state().mouse[1] * 1.f / state().size[1]);
|
||||
mouseover_particle_ = kdtree.closest(m).data;
|
||||
}
|
||||
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < particles_.size(); ++i)
|
||||
{
|
||||
// ======== kd-tree based algorithm ========
|
||||
|
||||
auto & pi = particles_[i];
|
||||
auto f = math::vector{0.f, 0.f};
|
||||
|
||||
kdtree.closer_than_map(pi.position, max_force_distance_[pi.type], [&](auto const & value){
|
||||
auto j = value.data;
|
||||
|
||||
if (i == j)
|
||||
return;
|
||||
|
||||
auto const & pj = particles_[j];
|
||||
|
||||
auto r = pj.position - pi.position;
|
||||
float l = math::length(r);
|
||||
auto n = r / l;
|
||||
|
||||
// ==== 1/R forces ====
|
||||
// float lg = std::max(0.5f, l);
|
||||
// auto g = n / (lg * lg);
|
||||
|
||||
// if (l < force_distance_(pi.type, pj.type))
|
||||
// f += force_constants_(pi.type, pj.type) * g;
|
||||
|
||||
// float collision_distance = pi.radius + pj.radius;
|
||||
|
||||
// if (l < collision_distance)
|
||||
// {
|
||||
// float d = std::max(0.25f, l / collision_distance);
|
||||
|
||||
// f -= collision_strength * n * (1.f / (d * d) - 1.f);
|
||||
// }
|
||||
|
||||
// ==== Linear forces ====
|
||||
if (l < force_distance_(pi.type, pj.type))
|
||||
f += force_constants_(pi.type, pj.type) * n * (1.f - l / force_distance_(pi.type, pj.type));
|
||||
|
||||
if (l < collision_distance_(pi.type, pj.type))
|
||||
f -= 10.f * std::abs(force_constants_(pi.type, pj.type)) * n * (1.f - l / collision_distance_(pi.type, pj.type));
|
||||
|
||||
(void)collision_strength;
|
||||
});
|
||||
|
||||
pi.velocity += f * dt / pi.mass;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ======== Naive algorithm v2 ========
|
||||
|
||||
// auto & pi = particles_[i];
|
||||
// auto f = math::vector{0.f, 0.f};
|
||||
|
||||
// for (int j = 0; j < particles_.size(); ++j)
|
||||
// {
|
||||
// if (i == j)
|
||||
// continue;
|
||||
|
||||
// auto const & pj = particles_[j];
|
||||
|
||||
// auto r = pj.position - pi.position;
|
||||
// float l = math::length(r);
|
||||
// auto n = r / l;
|
||||
// auto g = n / (l * l);
|
||||
|
||||
// if (l < force_distance_(pi.type, pj.type))
|
||||
// f += force_constants_(pi.type, pj.type) * g;
|
||||
|
||||
// if (l < pi.radius + pj.radius)
|
||||
// {
|
||||
// float d = l / (pi.radius + pj.radius);
|
||||
|
||||
// f -= collision_strength * n * (1.f / (d * d) - 1.f);
|
||||
// }
|
||||
// }
|
||||
|
||||
// pi.velocity += f * dt / pi.mass;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ======== Naive algorithm ========
|
||||
|
||||
// for (int j = i + 1; j < particles_.size(); ++j)
|
||||
// {
|
||||
// auto & pi = particles_[i];
|
||||
// auto & pj = particles_[j];
|
||||
|
||||
// auto fi = math::vector{0.f, 0.f};
|
||||
// auto fj = math::vector{0.f, 0.f};
|
||||
|
||||
// auto r = pj.position - pi.position;
|
||||
// float l = math::length(r);
|
||||
// auto n = r / l;
|
||||
// auto g = n / (l * l);
|
||||
|
||||
// if (l < force_distance_(pi.type, pj.type))
|
||||
// fi += force_constants_(pi.type, pj.type) * g;
|
||||
|
||||
// if (l < force_distance_(pj.type, pi.type))
|
||||
// fj -= force_constants_(pj.type, pi.type) * g;
|
||||
|
||||
// if (l < pi.radius + pj.radius)
|
||||
// {
|
||||
// float d = l / (pi.radius + pj.radius);
|
||||
|
||||
// auto f = collision_strength * n * (1.f / (d * d) - 1.f);
|
||||
// fi -= f;
|
||||
// fj += f;
|
||||
// }
|
||||
|
||||
// pi.velocity += fi * dt / pi.mass;
|
||||
// pj.velocity += fj * dt / pj.mass;
|
||||
// }
|
||||
}
|
||||
|
||||
float const friction = 10.f;
|
||||
float const friction_factor = std::exp(- friction * dt);
|
||||
float const wall_distance = 0.f;
|
||||
float const wall_force = 10.f;
|
||||
float const gravity = 0.f;
|
||||
float const center_gravity = 0.f;
|
||||
float const line_gravity = 0.f;
|
||||
float const circle_gravity = 0.f;
|
||||
float const circle_radius = 100.f;
|
||||
bool const circle_wall = false;
|
||||
float const circle_wall_radius = 100.f;
|
||||
|
||||
for (auto & p : particles_)
|
||||
{
|
||||
auto r = p.position - p.position.zero();
|
||||
|
||||
p.velocity[1] -= gravity * dt;
|
||||
p.velocity[1] -= line_gravity * p.position[1] * dt;
|
||||
p.velocity -= center_gravity * r * dt;
|
||||
p.velocity += circle_gravity * r / math::length(r) * (circle_radius - math::length(r)) * dt;
|
||||
p.velocity *= friction_factor;
|
||||
p.position += p.velocity * dt;
|
||||
|
||||
for (int d : {0, 1})
|
||||
{
|
||||
// if (p.position[d] < area_[d].min + p.radius)
|
||||
// {
|
||||
// p.position[d] = area_[d].min + p.radius;
|
||||
// p.velocity[d] *= -1.f;
|
||||
// }
|
||||
|
||||
// if (p.position[d] > area_[d].max - p.radius)
|
||||
// {
|
||||
// p.position[d] = area_[d].max - p.radius;
|
||||
// p.velocity[d] *= -1.f;
|
||||
// }
|
||||
|
||||
if (circle_wall)
|
||||
{
|
||||
auto r = p.position - p.position.zero();
|
||||
auto l = math::length(r);
|
||||
if (l > circle_wall_radius)
|
||||
{
|
||||
p.velocity -= wall_force * r / l * (l - circle_wall_radius) * dt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (p.position[d] < area_[d].min + wall_distance)
|
||||
{
|
||||
p.velocity[d] += wall_force * (area_[d].min + wall_distance - p.position[d]) * dt;
|
||||
}
|
||||
|
||||
if (p.position[d] > area_[d].max - wall_distance)
|
||||
{
|
||||
p.velocity[d] -= wall_force * (p.position[d] - (area_[d].max - wall_distance)) * dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void present() override
|
||||
{
|
||||
gl::Viewport(0, 0, state().size[0], state().size[1]);
|
||||
|
||||
gl::ClearColor(0.02f, 0.02f, 0.02f, 1.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
for (auto const & p : particles_)
|
||||
{
|
||||
auto c0 = colors_[p.type];
|
||||
c0[3] = 15;
|
||||
|
||||
auto c1 = colors_[p.type];
|
||||
c1[3] = 0;
|
||||
|
||||
painter_.circle(p.position, p.radius * 8.f, c0, c1);
|
||||
}
|
||||
|
||||
for (auto const & p : particles_)
|
||||
painter_.circle(p.position, p.radius, colors_[p.type]);
|
||||
|
||||
painter_.render(math::orthographic_camera{view_area_}.transform());
|
||||
|
||||
if (mouseover_particle_)
|
||||
{
|
||||
int type = particles_[*mouseover_particle_].type;
|
||||
painter_.text({state().size[0] / 2.f, state().size[1] - 20.f}, std::format("Species #{}", type), {
|
||||
.scale = {2.f, 2.f},
|
||||
.y = gfx::painter::y_align::bottom,
|
||||
.c = colors_[type],
|
||||
});
|
||||
}
|
||||
|
||||
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
|
||||
private:
|
||||
gfx::painter painter_;
|
||||
|
||||
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_;
|
||||
|
||||
math::box<float, 2> area_;
|
||||
math::box<float, 2> view_area_;
|
||||
float scale_ = 1.f;
|
||||
float scale_target_ = 1.f;
|
||||
|
||||
int types_;
|
||||
std::vector<gfx::color_rgba> colors_;
|
||||
util::ndarray<float, 2> force_constants_;
|
||||
util::ndarray<float, 2> force_distance_;
|
||||
util::ndarray<float, 2> collision_distance_;
|
||||
std::vector<float> max_force_distance_;
|
||||
|
||||
std::vector<particle> particles_;
|
||||
|
||||
std::optional<int> mouseover_particle_;
|
||||
|
||||
bool paused_ = true;
|
||||
};
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<particle_life_2d_app>({.name = "Particle Life 2D"});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,22 +1,24 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
|
||||
#include <psemek/geom/point.hpp>
|
||||
#include <psemek/geom/simplex.hpp>
|
||||
#include <psemek/geom/orthographic.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/distance.hpp>
|
||||
#include <psemek/math/point.hpp>
|
||||
#include <psemek/math/simplex.hpp>
|
||||
#include <psemek/math/orthographic.hpp>
|
||||
#include <psemek/math/rotation.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/distance.hpp>
|
||||
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/unused.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/util/statistics.hpp>
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace psemek;
|
||||
|
|
@ -48,11 +50,11 @@ namespace psemek::util
|
|||
|
||||
struct stick_model
|
||||
{
|
||||
std::vector<geom::point<float, 2>> points;
|
||||
std::vector<geom::vector<float, 2>> vels;
|
||||
std::vector<math::point<float, 2>> points;
|
||||
std::vector<math::vector<float, 2>> vels;
|
||||
std::vector<bool> movable;
|
||||
|
||||
std::vector<geom::segment<std::uint32_t>> sticks;
|
||||
std::vector<math::segment<std::uint32_t>> sticks;
|
||||
std::vector<float> stick_length;
|
||||
std::vector<bool> stick_solid;
|
||||
|
||||
|
|
@ -62,36 +64,33 @@ struct stick_model
|
|||
void stick_model::add_stick(std::size_t i, std::size_t j, bool solid)
|
||||
{
|
||||
sticks.push_back({i, j});
|
||||
stick_length.push_back(geom::distance(points[i], points[j]));
|
||||
stick_length.push_back(math::distance(points[i], points[j]));
|
||||
stick_solid.push_back(solid);
|
||||
}
|
||||
|
||||
static const float ball_radius = 0.1f;
|
||||
|
||||
struct physics_demo_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
physics_demo_app();
|
||||
physics_demo_app(options const &, context const &);
|
||||
~physics_demo_app();
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
|
||||
void on_left_button_down() override;
|
||||
void on_left_button_up() override;
|
||||
void on_event(app::mouse_button_event const & event) override;
|
||||
|
||||
void on_right_button_down() override;
|
||||
void on_event(app::mouse_move_event const & event) override;
|
||||
|
||||
void on_mouse_move(int x, int y, int, int) override;
|
||||
void on_event(app::key_event const & event) override;
|
||||
|
||||
void on_key_down(SDL_Keycode key) override;
|
||||
|
||||
void on_mouse_wheel(int delta) override;
|
||||
void on_event(app::mouse_wheel_event const & event) override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
geom::point<float, 2> screen_to_world(geom::point<float, 2> const & p) const;
|
||||
geom::point<float, 2> world_to_screen(geom::point<float, 2> const & p) const;
|
||||
math::point<float, 2> screen_to_world(math::point<float, 2> const & p) const;
|
||||
math::point<float, 2> world_to_screen(math::point<float, 2> const & p) const;
|
||||
|
||||
util::clock<std::chrono::duration<float>> frame_clock;
|
||||
float update_time_left = 0.f;
|
||||
|
|
@ -102,11 +101,11 @@ struct physics_demo_app
|
|||
|
||||
gfx::painter painter;
|
||||
|
||||
geom::box<float, 2> view_region;
|
||||
math::box<float, 2> view_region;
|
||||
|
||||
std::optional<std::size_t> closest_point;
|
||||
std::optional<std::size_t> closest_stick;
|
||||
std::optional<geom::vector<float, 2>> drag_delta;
|
||||
std::optional<math::vector<float, 2>> drag_delta;
|
||||
|
||||
std::optional<std::size_t> new_spring_start;
|
||||
|
||||
|
|
@ -115,8 +114,7 @@ struct physics_demo_app
|
|||
util::statistics<float> physics_update_stats;
|
||||
};
|
||||
|
||||
physics_demo_app::physics_demo_app()
|
||||
: app("Physics Demo", 4)
|
||||
physics_demo_app::physics_demo_app(options const &, context const &)
|
||||
{
|
||||
view_region[0] = {0.f, 0.f};
|
||||
view_region[1] = {-2.f, 5.f};
|
||||
|
|
@ -167,7 +165,7 @@ physics_demo_app::physics_demo_app()
|
|||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
float a = (2.f * geom::pi * i) / N;
|
||||
float a = (2.f * math::pi * i) / N;
|
||||
model.points.push_back({std::cos(a) * 0.5f, y + std::sin(a) * 0.5f});
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +193,7 @@ physics_demo_app::physics_demo_app()
|
|||
model.add_stick(3, 4);
|
||||
}
|
||||
|
||||
model.vels.assign(model.points.size(), geom::vector<float, 2>::zero());
|
||||
model.vels.assign(model.points.size(), math::vector<float, 2>::zero());
|
||||
model.movable.assign(model.points.size(), true);
|
||||
}
|
||||
|
||||
|
|
@ -204,11 +202,11 @@ physics_demo_app::~physics_demo_app()
|
|||
log::info() << "Update: " << physics_update_stats;
|
||||
}
|
||||
|
||||
void physics_demo_app::on_resize(int width, int height)
|
||||
void physics_demo_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
float aspect_ratio = static_cast<float>(width) / height;
|
||||
float aspect_ratio = static_cast<float>(event.size[0]) / event.size[1];
|
||||
|
||||
float xc = view_region[0].center();
|
||||
float yl = view_region[1].length();
|
||||
|
|
@ -217,56 +215,53 @@ void physics_demo_app::on_resize(int width, int height)
|
|||
view_region[0].max = xc + yl / 2.f * aspect_ratio;
|
||||
}
|
||||
|
||||
void physics_demo_app::on_left_button_down()
|
||||
void physics_demo_app::on_event(app::mouse_button_event const & event)
|
||||
{
|
||||
app::on_left_button_down();
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (mouse() && closest_point)
|
||||
if (event.button == app::mouse_button::left && event.down && closest_point)
|
||||
{
|
||||
drag_delta = world_to_screen(model.points[*closest_point]) - geom::cast<float>(*mouse());
|
||||
model.vels[*closest_point] = geom::vector<float, 2>::zero();
|
||||
drag_delta = world_to_screen(model.points[*closest_point]) - math::cast<float>(state().mouse);
|
||||
model.vels[*closest_point] = math::vector<float, 2>::zero();
|
||||
}
|
||||
|
||||
if (event.button == app::mouse_button::left && !event.down)
|
||||
{
|
||||
drag_delta = std::nullopt;
|
||||
}
|
||||
|
||||
if (event.button == app::mouse_button::right && event.down)
|
||||
{
|
||||
if (new_spring_start)
|
||||
{
|
||||
new_spring_start = std::nullopt;
|
||||
}
|
||||
else if (closest_point)
|
||||
{
|
||||
model.movable[*closest_point] = !model.movable[*closest_point];
|
||||
model.vels[*closest_point] = math::vector<float, 2>::zero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void physics_demo_app::on_left_button_up()
|
||||
void physics_demo_app::on_event(app::mouse_move_event const & event)
|
||||
{
|
||||
app::on_left_button_up();
|
||||
|
||||
drag_delta = std::nullopt;
|
||||
}
|
||||
|
||||
void physics_demo_app::on_right_button_down()
|
||||
{
|
||||
if (new_spring_start)
|
||||
{
|
||||
new_spring_start = std::nullopt;
|
||||
}
|
||||
else if (closest_point)
|
||||
{
|
||||
model.movable[*closest_point] = !model.movable[*closest_point];
|
||||
model.vels[*closest_point] = geom::vector<float, 2>::zero();
|
||||
}
|
||||
}
|
||||
|
||||
void physics_demo_app::on_mouse_move(int x, int y, int dx, int dy)
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (!drag_delta)
|
||||
{
|
||||
closest_point = std::nullopt;
|
||||
closest_stick = std::nullopt;
|
||||
|
||||
if (mouse())
|
||||
{
|
||||
auto const m = geom::cast<float>(*mouse());
|
||||
auto const m = math::cast<float>(state().mouse);
|
||||
|
||||
std::size_t closest = 0;
|
||||
float distance = std::numeric_limits<float>::infinity();
|
||||
|
||||
for (std::size_t i = 0; i < model.points.size(); ++i)
|
||||
{
|
||||
auto const d = geom::distance(m, world_to_screen(model.points[i]));
|
||||
auto const d = math::distance(m, world_to_screen(model.points[i]));
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
|
|
@ -286,7 +281,7 @@ void physics_demo_app::on_mouse_move(int x, int y, int dx, int dy)
|
|||
|
||||
for (std::size_t i = 0; i < model.sticks.size(); ++i)
|
||||
{
|
||||
auto const d = geom::distance(m, geom::simplex{world_to_screen(model.points[model.sticks[i][0]]), world_to_screen(model.points[model.sticks[i][1]])});
|
||||
auto const d = math::distance(m, math::simplex{world_to_screen(model.points[model.sticks[i][0]]), world_to_screen(model.points[model.sticks[i][1]])});
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
|
|
@ -303,13 +298,15 @@ void physics_demo_app::on_mouse_move(int x, int y, int dx, int dy)
|
|||
}
|
||||
}
|
||||
|
||||
void physics_demo_app::on_key_down(SDL_Keycode key)
|
||||
void physics_demo_app::on_event(app::key_event const & event)
|
||||
{
|
||||
if (key == SDLK_SPACE)
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
{
|
||||
paused = !paused;
|
||||
}
|
||||
else if (key == SDLK_x)
|
||||
else if (event.down && event.key == app::keycode::X)
|
||||
{
|
||||
if (closest_point)
|
||||
{
|
||||
|
|
@ -317,7 +314,7 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
|
|||
model.vels.erase(model.vels.begin() + *closest_point);
|
||||
model.movable.erase(model.movable.begin() + *closest_point);
|
||||
|
||||
std::vector<geom::segment<std::uint32_t>> new_sticks;
|
||||
std::vector<math::segment<std::uint32_t>> new_sticks;
|
||||
std::vector<float> new_stick_length;
|
||||
std::vector<bool> new_stick_solid;
|
||||
|
||||
|
|
@ -350,7 +347,7 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
|
|||
closest_stick = std::nullopt;
|
||||
}
|
||||
}
|
||||
else if (key == SDLK_c)
|
||||
else if (event.down && event.key == app::keycode::C)
|
||||
{
|
||||
if (new_spring_start && closest_point)
|
||||
{
|
||||
|
|
@ -360,10 +357,10 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
|
|||
new_spring_start = std::nullopt;
|
||||
}
|
||||
}
|
||||
else if (new_spring_start && mouse())
|
||||
else if (new_spring_start)
|
||||
{
|
||||
model.points.push_back(screen_to_world(geom::cast<float>(*mouse())));
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.points.push_back(screen_to_world(math::cast<float>(state().mouse)));
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.movable.push_back(false);
|
||||
model.add_stick(*new_spring_start, model.points.size() - 1);
|
||||
new_spring_start = model.points.size() - 1;
|
||||
|
|
@ -373,65 +370,59 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
|
|||
new_spring_start = *closest_point;
|
||||
closest_point = std::nullopt;
|
||||
}
|
||||
else if (mouse())
|
||||
else
|
||||
{
|
||||
model.points.push_back(screen_to_world(geom::cast<float>(*mouse())));
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.points.push_back(screen_to_world(math::cast<float>(state().mouse)));
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.movable.push_back(false);
|
||||
}
|
||||
}
|
||||
else if (key == SDLK_n)
|
||||
else if (event.down && event.key == app::keycode::N)
|
||||
{
|
||||
if (mouse())
|
||||
{
|
||||
model.points.push_back(screen_to_world(geom::cast<float>(*mouse())));
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
}
|
||||
model.points.push_back(screen_to_world(math::cast<float>(state().mouse)));
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
}
|
||||
else if (key == SDLK_f)
|
||||
else if (event.down && event.key == app::keycode::F)
|
||||
{
|
||||
if (closest_stick)
|
||||
{
|
||||
model.stick_solid[*closest_stick] = !model.stick_solid[*closest_stick];
|
||||
}
|
||||
}
|
||||
else if (key == SDLK_w)
|
||||
{
|
||||
if (mouse())
|
||||
{
|
||||
std::uint32_t const base = model.points.size();
|
||||
|
||||
int N = 12;
|
||||
|
||||
auto o = screen_to_world(geom::cast<float>(*mouse()));
|
||||
|
||||
model.points.push_back(o);
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
|
||||
float r = 0.5f;
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
float a = (2.f * geom::pi * i) / N;
|
||||
model.points.push_back({o[0] + std::cos(a) * r, o[1] + std::sin(a) * r});
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
model.add_stick(base, base + i + 1, false);
|
||||
model.add_stick(base + i + 1, base + 1 + ((i + 1) % N));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == SDLK_b)
|
||||
else if (event.down && event.key == app::keycode::W)
|
||||
{
|
||||
std::uint32_t const base = model.points.size();
|
||||
|
||||
auto o = screen_to_world(geom::cast<float>(*mouse()));
|
||||
int N = 12;
|
||||
|
||||
auto o = screen_to_world(math::cast<float>(state().mouse));
|
||||
|
||||
model.points.push_back(o);
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
|
||||
float r = 0.5f;
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
float a = (2.f * math::pi * i) / N;
|
||||
model.points.push_back({o[0] + std::cos(a) * r, o[1] + std::sin(a) * r});
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
model.add_stick(base, base + i + 1, false);
|
||||
model.add_stick(base + i + 1, base + 1 + ((i + 1) % N));
|
||||
}
|
||||
}
|
||||
else if (event.down && event.key == app::keycode::B)
|
||||
{
|
||||
std::uint32_t const base = model.points.size();
|
||||
|
||||
auto o = screen_to_world(math::cast<float>(state().mouse));
|
||||
|
||||
float w = 0.25f;
|
||||
|
||||
|
|
@ -440,10 +431,10 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
|
|||
model.points.push_back({o[0] - w, o[1] + w});
|
||||
model.points.push_back({o[0] + w, o[1] + w});
|
||||
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.vels.push_back(geom::vector<float, 2>::zero());
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.vels.push_back(math::vector<float, 2>::zero());
|
||||
model.movable.push_back(true);
|
||||
model.movable.push_back(true);
|
||||
model.movable.push_back(true);
|
||||
|
|
@ -458,13 +449,15 @@ void physics_demo_app::on_key_down(SDL_Keycode key)
|
|||
}
|
||||
}
|
||||
|
||||
void physics_demo_app::on_mouse_wheel(int delta)
|
||||
void physics_demo_app::on_event(app::mouse_wheel_event const & event)
|
||||
{
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (closest_stick)
|
||||
{
|
||||
auto & l = model.stick_length[*closest_stick];
|
||||
|
||||
l += delta * ball_radius / 2.f;
|
||||
l += event.delta * ball_radius / 2.f;
|
||||
|
||||
l = std::max(3.f * ball_radius, l);
|
||||
}
|
||||
|
|
@ -490,7 +483,7 @@ void physics_demo_app::update()
|
|||
float const stick_friction = 500.f;
|
||||
float const stick_bounce = 0.9f;
|
||||
|
||||
geom::vector<float, 2> const gravity {0.f, -10.f};
|
||||
math::vector<float, 2> const gravity {0.f, -10.f};
|
||||
|
||||
update_time_left += frame_clock.restart().count();
|
||||
|
||||
|
|
@ -514,7 +507,7 @@ void physics_demo_app::update()
|
|||
|
||||
std::vector<util::fixed_vector<std::array<int, 2>, 8>> ball_cells(model.points.size());
|
||||
|
||||
util::array<util::fixed_vector<std::uint32_t, 16>, 2> cells({std::ceil(view_region[0].length() / cell_size), std::ceil(view_region[1].length() / cell_size)});
|
||||
util::ndarray<util::fixed_vector<std::uint32_t, 16>, 2> cells({std::ceil(view_region[0].length() / cell_size), std::ceil(view_region[1].length() / cell_size)});
|
||||
|
||||
float dx = view_region[0].length() / cells.width();
|
||||
float dy = view_region[1].length() / cells.height();
|
||||
|
|
@ -522,10 +515,10 @@ void physics_demo_app::update()
|
|||
for (std::size_t i = 0; i < model.points.size(); ++i)
|
||||
{
|
||||
int x = std::floor(cells.width() * (model.points[i][0] - view_region[0].min) / view_region[0].length());
|
||||
x = geom::clamp<int>(x, {0, cells.width() - 1});
|
||||
x = math::clamp<int>(x, {0, cells.width() - 1});
|
||||
|
||||
int y = std::floor(cells.height() * (model.points[i][1] - view_region[1].min) / view_region[1].length());
|
||||
y = geom::clamp<int>(y, {0, cells.height() - 1});
|
||||
y = math::clamp<int>(y, {0, cells.height() - 1});
|
||||
|
||||
float cx = view_region[0].min + dx * x;
|
||||
float cy = view_region[1].min + dy * y;
|
||||
|
|
@ -584,9 +577,9 @@ void physics_demo_app::update()
|
|||
|
||||
std::optional<std::size_t> fixed;
|
||||
|
||||
if (closest_point && drag_delta && mouse())
|
||||
if (closest_point && drag_delta)
|
||||
{
|
||||
auto target = screen_to_world(geom::cast<float>(*mouse()) + *drag_delta);
|
||||
auto target = screen_to_world(math::cast<float>(state().mouse) + *drag_delta);
|
||||
auto & point = model.points[*closest_point];
|
||||
|
||||
auto & vel = model.vels[*closest_point];
|
||||
|
|
@ -598,7 +591,7 @@ void physics_demo_app::update()
|
|||
fixed = closest_point;
|
||||
}
|
||||
|
||||
std::vector<geom::vector<float, 2>> force(model.points.size(), geom::vector<float, 2>::zero());
|
||||
std::vector<math::vector<float, 2>> force(model.points.size(), math::vector<float, 2>::zero());
|
||||
|
||||
for (std::size_t i = 0; i < model.points.size(); ++i)
|
||||
{
|
||||
|
|
@ -609,7 +602,7 @@ void physics_demo_app::update()
|
|||
{
|
||||
auto const & s = model.sticks[i];
|
||||
auto d = model.points[s[1]] - model.points[s[0]];
|
||||
auto l = geom::length(d);
|
||||
auto l = math::length(d);
|
||||
auto n = d / l;
|
||||
|
||||
auto f = - spring_constant * (l - model.stick_length[i]) * n / 2.f;
|
||||
|
|
@ -627,17 +620,17 @@ void physics_demo_app::update()
|
|||
model.vels[i] += dt * force[i];
|
||||
}
|
||||
|
||||
std::vector<geom::vector<float, 2>> impulse(model.points.size(), geom::vector<float, 2>::zero());
|
||||
std::vector<geom::vector<float, 2>> offset(model.points.size(), geom::vector<float, 2>::zero());
|
||||
std::vector<math::vector<float, 2>> impulse(model.points.size(), math::vector<float, 2>::zero());
|
||||
std::vector<math::vector<float, 2>> offset(model.points.size(), math::vector<float, 2>::zero());
|
||||
|
||||
for (std::size_t i = 0; i < model.sticks.size(); ++i)
|
||||
{
|
||||
auto const & s = model.sticks[i];
|
||||
|
||||
auto rel = model.vels[s[1]] - model.vels[s[0]];
|
||||
auto n = geom::normalized(model.points[s[1]] - model.points[s[0]]);
|
||||
auto n = math::normalized(model.points[s[1]] - model.points[s[0]]);
|
||||
|
||||
rel = n * geom::dot(rel, n);
|
||||
rel = n * math::dot(rel, n);
|
||||
|
||||
impulse[s[0]] += rel / 2.f * spring_damping_constant * dt;
|
||||
impulse[s[1]] -= rel / 2.f * spring_damping_constant * dt;
|
||||
|
|
@ -646,7 +639,7 @@ void physics_demo_app::update()
|
|||
for (std::size_t i = 0; i < model.points.size(); ++i)
|
||||
{
|
||||
bool collision = false;
|
||||
geom::vector<float, 2> n;
|
||||
math::vector<float, 2> n;
|
||||
float d;
|
||||
|
||||
if (model.points[i][0] < view_region[0].min + ball_radius && model.vels[i][0] < 0.f)
|
||||
|
|
@ -682,7 +675,7 @@ void physics_demo_app::update()
|
|||
offset[i] += n * d;
|
||||
(void)d;
|
||||
|
||||
auto nv = n * geom::dot(n, model.vels[i]);
|
||||
auto nv = n * math::dot(n, model.vels[i]);
|
||||
auto tv = model.vels[i] - nv;
|
||||
|
||||
auto jn = - (1.f + ground_bounce) * nv;
|
||||
|
|
@ -691,9 +684,9 @@ void physics_demo_app::update()
|
|||
|
||||
unused(ground_mu);
|
||||
|
||||
// if (geom::length(jt) > ground_mu * geom::length(jn))
|
||||
// if (math::length(jt) > ground_mu * math::length(jn))
|
||||
// {
|
||||
// jt = geom::normalized(jt) * ground_mu * geom::length(jn);
|
||||
// jt = math::normalized(jt) * ground_mu * math::length(jn);
|
||||
// }
|
||||
|
||||
impulse[i] += jn + jt;
|
||||
|
|
@ -715,14 +708,14 @@ void physics_demo_app::update()
|
|||
|
||||
checked.push_back(j);
|
||||
|
||||
auto r = geom::distance(model.points[i], model.points[j]);
|
||||
auto r = math::distance(model.points[i], model.points[j]);
|
||||
|
||||
if (r < ball_radius * 2.f)
|
||||
{
|
||||
auto n = (model.points[j] - model.points[i]) / r;
|
||||
auto rel = model.vels[j] - model.vels[i];
|
||||
|
||||
auto d = geom::dot(rel, n);
|
||||
auto d = math::dot(rel, n);
|
||||
|
||||
if (d < 0.f)
|
||||
{
|
||||
|
|
@ -752,23 +745,23 @@ void physics_demo_app::update()
|
|||
|
||||
if (i == s[0] || i == s[1]) continue;
|
||||
|
||||
auto d = geom::normalized(model.points[s[1]] - model.points[s[0]]);
|
||||
auto d = math::normalized(model.points[s[1]] - model.points[s[0]]);
|
||||
|
||||
auto t = geom::dot(d, model.points[i] - model.points[s[0]]) / geom::distance(model.points[s[1]], model.points[s[0]]);
|
||||
auto t = math::dot(d, model.points[i] - model.points[s[0]]) / math::distance(model.points[s[1]], model.points[s[0]]);
|
||||
|
||||
if (t < 0.f || t > 1.f) continue;
|
||||
|
||||
auto v = geom::lerp(model.vels[s[0]], model.vels[s[1]], t);
|
||||
auto v = math::lerp(model.vels[s[0]], model.vels[s[1]], t);
|
||||
|
||||
auto rel = model.vels[i] - v;
|
||||
|
||||
auto n = geom::ort(d);
|
||||
auto n = math::ort(d);
|
||||
|
||||
float r = geom::dot(n, model.points[i] - model.points[s[0]]);
|
||||
float r = math::dot(n, model.points[i] - model.points[s[0]]);
|
||||
|
||||
if (geom::dot(rel, n) * r >= 0.f) continue;
|
||||
if (math::dot(rel, n) * r >= 0.f) continue;
|
||||
|
||||
auto nv = n * geom::dot(rel, n);
|
||||
auto nv = n * math::dot(rel, n);
|
||||
auto tv = rel - nv;
|
||||
|
||||
unused(stick_friction);
|
||||
|
|
@ -776,10 +769,10 @@ void physics_demo_app::update()
|
|||
|
||||
if (std::abs(r) >= ball_radius) continue;
|
||||
|
||||
geom::vector<float, 2> off = (r < 0.f ? -1.f : 1.f) * n * (ball_radius - std::abs(r));
|
||||
math::vector<float, 2> off = (r < 0.f ? -1.f : 1.f) * n * (ball_radius - std::abs(r));
|
||||
unused(off);
|
||||
|
||||
geom::vector<float, 2> imp = - nv * (1.f + stick_bounce) - tv * stick_friction * dt;
|
||||
math::vector<float, 2> imp = - nv * (1.f + stick_bounce) - tv * stick_friction * dt;
|
||||
|
||||
offset[i] += off * 2.f / 3.f;
|
||||
offset[s[0]] -= off / 3.f * (1.f - t);
|
||||
|
|
@ -807,7 +800,7 @@ void physics_demo_app::update()
|
|||
|
||||
void physics_demo_app::present()
|
||||
{
|
||||
gl::Viewport(0, 0, width(), height());
|
||||
gl::Viewport(0, 0, state().size[0], state().size[1]);
|
||||
|
||||
gl::ClearColor(0.8f, 0.8f, 0.9f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
|
@ -821,9 +814,9 @@ void physics_demo_app::present()
|
|||
model.stick_solid[i] ? gfx::color_rgba{0, 0, 0, 255} : gfx::color_rgba{63, 63, 63, 255});
|
||||
}
|
||||
|
||||
if (new_spring_start && mouse())
|
||||
if (new_spring_start)
|
||||
{
|
||||
painter.line(model.points[*new_spring_start], screen_to_world(geom::cast<float>(*mouse())), ball_radius / 2.f, gfx::red);
|
||||
painter.line(model.points[*new_spring_start], screen_to_world(math::cast<float>(state().mouse)), ball_radius / 2.f, gfx::red);
|
||||
}
|
||||
|
||||
if (closest_stick)
|
||||
|
|
@ -841,7 +834,7 @@ void physics_demo_app::present()
|
|||
painter.circle(model.points[*closest_point], ball_radius, gfx::red);
|
||||
}
|
||||
|
||||
painter.render(geom::orthographic<float, 3>(geom::box<float, 3>{{view_region[0], view_region[1], {-1.f, 1.f}}}).homogeneous_matrix());
|
||||
painter.render(math::orthographic<float, 3>(math::box<float, 3>{{view_region[0], view_region[1], {-1.f, 1.f}}}).homogeneous_matrix());
|
||||
|
||||
text = util::to_string("Balls: ", model.points.size(), "\n", text);
|
||||
|
||||
|
|
@ -851,7 +844,7 @@ void physics_demo_app::present()
|
|||
opts.y = gfx::painter::y_align::top;
|
||||
opts.c = gfx::black;
|
||||
opts.f = gfx::painter::font::font_9x12;
|
||||
opts.scale = 2.f;
|
||||
opts.scale = {2.f, 2.f};
|
||||
|
||||
float y = 10.f;
|
||||
for (std::size_t i = 0;;)
|
||||
|
|
@ -872,22 +865,28 @@ void physics_demo_app::present()
|
|||
|
||||
text.clear();
|
||||
|
||||
painter.render(geom::window_camera{width(), height()}.transform());
|
||||
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
|
||||
geom::point<float, 2> physics_demo_app::screen_to_world(geom::point<float, 2> const & p) const
|
||||
math::point<float, 2> physics_demo_app::screen_to_world(math::point<float, 2> const & p) const
|
||||
{
|
||||
return geom::orthographic<float, 2>(view_region).inverse()(geom::point{2.f * p[0] / width() - 1.f, 1.f - 2.f * p[1] / height()});
|
||||
return math::orthographic<float, 2>(view_region).inverse()(math::point{2.f * p[0] / state().size[0] - 1.f, 1.f - 2.f * p[1] / state().size[1]});
|
||||
}
|
||||
|
||||
geom::point<float, 2> physics_demo_app::world_to_screen(geom::point<float, 2> const & p) const
|
||||
math::point<float, 2> physics_demo_app::world_to_screen(math::point<float, 2> const & p) const
|
||||
{
|
||||
auto q = geom::orthographic<float, 2>(view_region)(p);
|
||||
auto q = math::orthographic<float, 2>(view_region)(p);
|
||||
|
||||
return {(q[0] + 1.f) / 2.f * width(), (1.f - q[1]) / 2.f * height()};
|
||||
return {(q[0] + 1.f) / 2.f * state().size[0], (1.f - q[1]) / 2.f * state().size[1]};
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<physics_demo_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<physics_demo_app>({.name = "Physics example"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/phys/engine_2d.hpp>
|
||||
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
|
||||
#include <psemek/geom/box.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/math/box.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/rotation.hpp>
|
||||
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/recursive.hpp>
|
||||
|
|
@ -18,18 +18,20 @@
|
|||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
struct physics_2d_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
phys2d::engine physics;
|
||||
|
||||
geom::box<float, 2> view_box;
|
||||
geom::box<float, 2> simulation_box;
|
||||
math::box<float, 2> view_box;
|
||||
math::box<float, 2> simulation_box;
|
||||
|
||||
gfx::painter painter;
|
||||
|
||||
|
|
@ -52,11 +54,11 @@ struct physics_2d_app
|
|||
|
||||
std::vector<float> radiuses;
|
||||
|
||||
std::optional<geom::point<float, 2>> mouse;
|
||||
std::optional<math::point<float, 2>> mouse;
|
||||
|
||||
std::optional<std::size_t> selected_ball;
|
||||
|
||||
std::optional<geom::vector<float, 2>> drag_delta;
|
||||
std::optional<math::vector<float, 2>> drag_delta;
|
||||
|
||||
std::size_t motor_id;
|
||||
|
||||
|
|
@ -66,8 +68,7 @@ struct physics_2d_app
|
|||
|
||||
random::generator gen;
|
||||
|
||||
physics_2d_app()
|
||||
: app("Physics 2D example", 4)
|
||||
physics_2d_app(options const &, context const &)
|
||||
{
|
||||
simulation_box = {{{-world_width, world_width}, {-world_height, world_height}}};
|
||||
|
||||
|
|
@ -103,7 +104,7 @@ struct physics_2d_app
|
|||
{
|
||||
for (int x = nx / 2 - ky / 2; x < nx / 2 - ky / 2 + (ky - y); ++x)
|
||||
{
|
||||
geom::point<float, 2> pos{simulation_box.corner((x + 0.5f * y + 0.5f) / nx, (y + 0.5f) / ny)};
|
||||
math::point<float, 2> pos{simulation_box.corner((x + 0.5f * y + 0.5f) / nx, (y + 0.5f) / ny)};
|
||||
physics.add_object(box_group, box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
|
||||
}
|
||||
}
|
||||
|
|
@ -117,19 +118,19 @@ struct physics_2d_app
|
|||
{
|
||||
if (chess && (y % 2))
|
||||
{
|
||||
geom::point<float, 2> pos{simulation_box.corner((0.25f) / nx, (y + 0.5f) / ny)};
|
||||
math::point<float, 2> pos{simulation_box.corner((0.25f) / nx, (y + 0.5f) / ny)};
|
||||
physics.add_object(box_group, small_box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
|
||||
}
|
||||
|
||||
for (int x = 0; x < nx - chess * (y % 2); ++x)
|
||||
{
|
||||
geom::point<float, 2> pos{simulation_box.corner((x + 0.5f + chess * 0.5f * (y % 2)) / nx, (y + 0.5f) / ny)};
|
||||
math::point<float, 2> pos{simulation_box.corner((x + 0.5f + chess * 0.5f * (y % 2)) / nx, (y + 0.5f) / ny)};
|
||||
physics.add_object(box_group, box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
|
||||
}
|
||||
|
||||
if (chess && (y % 2))
|
||||
{
|
||||
geom::point<float, 2> pos{simulation_box.corner((nx - 0.25f) / nx, (y + 0.5f) / ny)};
|
||||
math::point<float, 2> pos{simulation_box.corner((nx - 0.25f) / nx, (y + 0.5f) / ny)};
|
||||
physics.add_object(box_group, small_box_shape, material, {pos, 0.f}, {{0.f, 0.f}, 0.01f});
|
||||
}
|
||||
}
|
||||
|
|
@ -145,9 +146,9 @@ struct physics_2d_app
|
|||
if(false)
|
||||
for (int i = 0; i < 15; ++i)
|
||||
{
|
||||
float a = geom::rad(360 * i / 15.f);
|
||||
float r = ball_radius * 15.f / geom::pi;
|
||||
geom::point<float, 2> pos{r * std::cos(a), r * std::sin(a)};
|
||||
float a = math::rad(360 * i / 15.f);
|
||||
float r = ball_radius * 15.f / math::pi;
|
||||
math::point<float, 2> pos{r * std::cos(a), r * std::sin(a)};
|
||||
physics.add_object(ball_group, ball_shape, material, {pos, 0.f}, {});
|
||||
}
|
||||
}
|
||||
|
|
@ -168,12 +169,12 @@ struct physics_2d_app
|
|||
physics.add_object(wall_group, wall_3_shape, wall_material, {}, {});
|
||||
|
||||
auto task = util::recursive([this, dx = box_width * 0.4f](auto && self) mutable -> void {
|
||||
physics.add_object(box_group, box_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
|
||||
// physics.add_object(ball_group, ball_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
|
||||
physics.add_object(box_group, box_shape, material, {simulation_box.corner(0.5f, 0.9f) + math::vector{dx, 0.f}, 0.f}, {});
|
||||
// physics.add_object(ball_group, ball_shape, material, {simulation_box.corner(0.5f, 0.9f) + math::vector{dx, 0.f}, 0.f}, {});
|
||||
|
||||
// float r = random::uniform_distribution<float>{0.05f, 0.5f}(gen);
|
||||
// auto new_shape = physics.add_shape(phys2d::ball{r});
|
||||
// physics.add_object(ball_group, new_shape, material, {simulation_box.corner(0.5f, 0.9f) + geom::vector{dx, 0.f}, 0.f}, {});
|
||||
// physics.add_object(ball_group, new_shape, material, {simulation_box.corner(0.5f, 0.9f) + math::vector{dx, 0.f}, 0.f}, {});
|
||||
// radiuses.push_back(r);
|
||||
|
||||
dx *= -1.f;
|
||||
|
|
@ -201,11 +202,11 @@ struct physics_2d_app
|
|||
prof::dump();
|
||||
}
|
||||
|
||||
void on_resize(int width, int height) override
|
||||
void on_event(app::resize_event const & event) override
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
float const ratio = static_cast<float>(width) / height;
|
||||
float const ratio = static_cast<float>(event.size[0]) / event.size[1];
|
||||
|
||||
float const c = view_box[0].center();
|
||||
float const l = view_box[1].length() * ratio;
|
||||
|
|
@ -213,52 +214,51 @@ struct physics_2d_app
|
|||
view_box[0] = {c - l / 2.f, c + l / 2.f};
|
||||
}
|
||||
|
||||
void on_left_button_down() override
|
||||
void on_event(app::mouse_button_event const & event) override
|
||||
{
|
||||
app::on_left_button_down();
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (selected_ball && mouse)
|
||||
if (event.button == app::mouse_button::left && event.down)
|
||||
{
|
||||
auto const & s = physics.group_static_state(ball_group)[*selected_ball];
|
||||
auto r = s.position - *mouse;
|
||||
r = geom::plane_rotation<float, 2>(0, 1, -s.rotation)(r);
|
||||
drag_delta = r;
|
||||
if (selected_ball && mouse)
|
||||
{
|
||||
auto const & s = physics.group_static_state(ball_group)[*selected_ball];
|
||||
auto r = s.position - *mouse;
|
||||
r = math::plane_rotation<float, 2>(0, 1, -s.rotation)(r);
|
||||
drag_delta = r;
|
||||
}
|
||||
else if (mouse)
|
||||
{
|
||||
// physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
|
||||
// physics.add_object(ball_group, large_ball_shape, material, {*mouse, 0.f}, {});
|
||||
physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
|
||||
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
|
||||
}
|
||||
}
|
||||
else if (mouse)
|
||||
|
||||
if (event.button == app::mouse_button::left && !event.down)
|
||||
{
|
||||
// physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
|
||||
// physics.add_object(ball_group, large_ball_shape, material, {*mouse, 0.f}, {});
|
||||
physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
|
||||
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
|
||||
drag_delta = std::nullopt;
|
||||
}
|
||||
|
||||
if (event.button == app::mouse_button::right && event.down)
|
||||
{
|
||||
if (mouse)
|
||||
{
|
||||
physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
|
||||
// physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
|
||||
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
|
||||
// physics.explode(*mouse, 10.f, 100.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_left_button_up() override
|
||||
void on_event(app::mouse_move_event const & event) override
|
||||
{
|
||||
app::on_left_button_up();
|
||||
app::application_base::on_event(event);
|
||||
|
||||
drag_delta = std::nullopt;
|
||||
}
|
||||
|
||||
void on_right_button_down() override
|
||||
{
|
||||
app::on_right_button_down();
|
||||
|
||||
if (mouse)
|
||||
{
|
||||
physics.add_object(ball_group, ball_shape, material, {*mouse, 0.f}, {{0.f, 0.f}, 0.f});
|
||||
// physics.add_object(box_group, box_shape, material, {*mouse, 0.f}, {});
|
||||
// physics.add_object(box_group, wide_box_shape, material, {*mouse, 0.f}, {});
|
||||
// physics.explode(*mouse, 10.f, 100.f);
|
||||
}
|
||||
}
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
|
||||
float mx = x * 1.f / width();
|
||||
float my = 1.f - y * 1.f / height();
|
||||
float mx = event.position[0] * 1.f / state().size[0];
|
||||
float my = 1.f - event.position[1] * 1.f / state().size[1];
|
||||
|
||||
mouse = view_box.corner(mx, my);
|
||||
}
|
||||
|
|
@ -270,9 +270,9 @@ struct physics_2d_app
|
|||
float MOTOR = 15.f;
|
||||
|
||||
float m = 0.f;
|
||||
if (is_key_down(SDLK_LEFT))
|
||||
if (state().key_down.contains(app::keycode::LEFT))
|
||||
m += MOTOR;
|
||||
if (is_key_down(SDLK_RIGHT))
|
||||
if (state().key_down.contains(app::keycode::RIGHT))
|
||||
m += -MOTOR;
|
||||
m = 15.f;
|
||||
motor = m;
|
||||
|
|
@ -286,13 +286,13 @@ struct physics_2d_app
|
|||
{
|
||||
auto r = -*drag_delta;
|
||||
|
||||
auto R = geom::plane_rotation<float, 2>(0, 1, s.rotation);
|
||||
auto R = math::plane_rotation<float, 2>(0, 1, s.rotation);
|
||||
|
||||
geom::vector<float, 2> f;
|
||||
math::vector<float, 2> f;
|
||||
f[0] = s.position[0] + R(r)[0] - (*mouse)[0];
|
||||
f[1] = s.position[1] + R(r)[1] - (*mouse)[1];
|
||||
|
||||
geom::matrix<float, 2, 3> J;
|
||||
math::matrix<float, 2, 3> J;
|
||||
J[0][0] = 1.f;
|
||||
J[0][1] = 0.f;
|
||||
J[0][2] = - std::sin(s.rotation) * r[0] + std::cos(s.rotation) * r[1];
|
||||
|
|
@ -300,12 +300,12 @@ struct physics_2d_app
|
|||
J[1][1] = 1.f;
|
||||
J[1][2] = std::cos(s.rotation) * r[0] - std::sin(s.rotation) * r[1];
|
||||
|
||||
geom::vector<float, 3> v;
|
||||
math::vector<float, 3> v;
|
||||
v[0] = d.velocity[0];
|
||||
v[1] = d.velocity[1];
|
||||
v[2] = d.angular_velocity;
|
||||
|
||||
auto dv = geom::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
|
||||
auto dv = math::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
|
||||
if (dv)
|
||||
{
|
||||
d.velocity[0] += (*dv)[0] * lambda;
|
||||
|
|
@ -323,24 +323,24 @@ struct physics_2d_app
|
|||
auto & d1 = physics.group_dynamic_state(group)[j];
|
||||
|
||||
auto r = s1.position - s0.position;
|
||||
float l = geom::length(r);
|
||||
float l = math::length(r);
|
||||
|
||||
geom::vector<float, 1> f;
|
||||
math::vector<float, 1> f;
|
||||
f[0] = l - target;
|
||||
|
||||
geom::matrix<float, 1, 4> J;
|
||||
math::matrix<float, 1, 4> J;
|
||||
J[0][0] = -r[0] / l;
|
||||
J[0][1] = -r[1] / l;
|
||||
J[0][2] = r[0] / l;
|
||||
J[0][3] = r[1] / l;
|
||||
|
||||
geom::vector<float, 4> v;
|
||||
math::vector<float, 4> v;
|
||||
v[0] = d0.velocity[0];
|
||||
v[1] = d0.velocity[1];
|
||||
v[2] = d1.velocity[0];
|
||||
v[3] = d1.velocity[1];
|
||||
|
||||
auto dv = geom::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
|
||||
auto dv = math::least_squares(J, -(1.f / 8.f) * f / dt - (J * v));
|
||||
if (dv)
|
||||
{
|
||||
d0.velocity[0] += (*dv)[0] * lambda;
|
||||
|
|
@ -413,7 +413,7 @@ struct physics_2d_app
|
|||
for (std::size_t i = 0; i < physics.group_size(ball_group); ++i)
|
||||
{
|
||||
auto p = physics.group_static_state(ball_group)[i].position;
|
||||
float d = geom::distance(p, *mouse);
|
||||
float d = math::distance(p, *mouse);
|
||||
if (d <= std::min(closest, ball_radius))
|
||||
{
|
||||
closest = d;
|
||||
|
|
@ -441,8 +441,8 @@ struct physics_2d_app
|
|||
painter.circle(p, r, gfx::black);
|
||||
painter.circle(p, r - line_width, gfx::light(gfx::blue, selected ? 0.8f : 1.f).as_color_rgba());
|
||||
|
||||
geom::vector const n{std::cos(a), std::sin(a)};
|
||||
geom::vector const m = geom::ort(n);
|
||||
math::vector const n{std::cos(a), std::sin(a)};
|
||||
math::vector const m = math::ort(n);
|
||||
|
||||
painter.line(p - n * ball_radius / 2.f, p + n * ball_radius / 2.f, line_width, gfx::black, false);
|
||||
painter.line(p - m * ball_radius / 2.f, p + m * ball_radius / 2.f, line_width, gfx::black, false);
|
||||
|
|
@ -458,13 +458,13 @@ struct physics_2d_app
|
|||
float w = std::get<phys2d::box const *>(physics.get_shape(physics.get_object(box_group, i).shape))->width / 2.f;
|
||||
float h = std::get<phys2d::box const *>(physics.get_shape(physics.get_object(box_group, i).shape))->height / 2.f;
|
||||
|
||||
geom::vector<float, 2> q[4];
|
||||
math::vector<float, 2> q[4];
|
||||
q[0] = {-w, -h};
|
||||
q[1] = { w, -h};
|
||||
q[2] = { w, h};
|
||||
q[3] = {-w, h};
|
||||
|
||||
auto m = geom::plane_rotation<float, 2>(0, 1, a).linear_matrix();
|
||||
auto m = math::plane_rotation<float, 2>(0, 1, a).linear_matrix();
|
||||
|
||||
for (std::size_t j = 0; j < 4; ++j)
|
||||
q[j] = m * q[j];
|
||||
|
|
@ -483,12 +483,17 @@ struct physics_2d_app
|
|||
painter.line(simulation_box.corner(1, 1), simulation_box.corner(0, 1), line_width, gfx::black, false);
|
||||
painter.line(simulation_box.corner(0, 1), simulation_box.corner(0, 0), line_width, gfx::black, false);
|
||||
|
||||
painter.render(geom::orthographic_camera{view_box}.transform());
|
||||
painter.render(math::orthographic_camera{view_box}.transform());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<physics_2d_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<physics_2d_app>({.name = "Physics example", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/phys/engine_3d.hpp>
|
||||
|
||||
|
|
@ -9,11 +9,11 @@
|
|||
#include <psemek/cg/body/icosahedron.hpp>
|
||||
#include <psemek/cg/body/box.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/mesh.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/translation.hpp>
|
||||
#include <psemek/geom/scale.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/mesh.hpp>
|
||||
#include <psemek/math/rotation.hpp>
|
||||
#include <psemek/math/translation.hpp>
|
||||
#include <psemek/math/scale.hpp>
|
||||
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
|
|
@ -23,6 +23,8 @@
|
|||
|
||||
#include <psemek/util/clock.hpp>
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
static char const program_vs[] =
|
||||
|
|
@ -85,21 +87,20 @@ void main()
|
|||
)";
|
||||
|
||||
struct physics_3d_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
physics_3d_app()
|
||||
: app::app("Physics 3D", 4)
|
||||
, program_(program_vs, program_fs)
|
||||
physics_3d_app(options const &, context const &)
|
||||
: program_(program_vs, program_fs)
|
||||
, rng_{random::device{}}
|
||||
{
|
||||
camera_.near_clip = 0.1f;
|
||||
camera_.far_clip = 1000.f;
|
||||
camera_.fov_y = geom::rad(90.f);
|
||||
camera_.fov_y = math::rad(90.f);
|
||||
camera_.fov_x = camera_.fov_y;
|
||||
camera_.target = {0.f, 0.f, 0.f};
|
||||
camera_.distance = 10.f;
|
||||
camera_.elevation_angle = geom::rad(45.f);
|
||||
camera_.azimuthal_angle = geom::rad(30.f);
|
||||
camera_.elevation_angle = math::rad(45.f);
|
||||
camera_.azimuthal_angle = math::rad(30.f);
|
||||
|
||||
camera_distance_tgt_ = camera_.distance;
|
||||
camera_azimuthal_angle_tgt_ = camera_.azimuthal_angle;
|
||||
|
|
@ -107,12 +108,12 @@ struct physics_3d_app
|
|||
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> position;
|
||||
geom::vector<float, 3> normal;
|
||||
math::point<float, 3> position;
|
||||
math::vector<float, 3> normal;
|
||||
|
||||
static void setup(gfx::mesh & m)
|
||||
{
|
||||
m.setup<geom::point<float, 3>, geom::vector<float, 3>>();
|
||||
m.setup<math::point<float, 3>, math::vector<float, 3>>();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -135,21 +136,21 @@ struct physics_3d_app
|
|||
auto const & body_vertices = cg::vertices(body);
|
||||
auto const & body_triangles = cg::triangles(body);
|
||||
|
||||
std::vector<geom::point<float, 3>> positions;
|
||||
std::vector<math::point<float, 3>> positions;
|
||||
std::copy(body_vertices.begin(), body_vertices.end(), std::back_inserter(positions));
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> triangles;
|
||||
std::vector<math::triangle<std::uint32_t>> triangles;
|
||||
for (auto const & t : body_triangles)
|
||||
triangles.push_back({t[0], t[1], t[2]});
|
||||
|
||||
for (int iterations = 0; iterations < 2; ++iterations)
|
||||
{
|
||||
geom::subdivide(positions, triangles);
|
||||
math::subdivide(positions, triangles);
|
||||
for (auto & p : positions)
|
||||
p = p.zero() + geom::normalized(p - p.zero());
|
||||
p = p.zero() + math::normalized(p - p.zero());
|
||||
}
|
||||
|
||||
auto normals = geom::smooth_normals(positions, triangles);
|
||||
auto normals = math::smooth_normals(positions, triangles);
|
||||
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
|
|
@ -166,19 +167,19 @@ struct physics_3d_app
|
|||
auto const & body_vertices = cg::vertices(body);
|
||||
auto const & body_triangles = cg::triangles(body);
|
||||
|
||||
std::vector<geom::point<float, 3>> positions;
|
||||
std::vector<math::point<float, 3>> positions;
|
||||
std::copy(body_vertices.begin(), body_vertices.end(), std::back_inserter(positions));
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> triangles;
|
||||
std::vector<math::triangle<std::uint32_t>> triangles;
|
||||
for (auto const & t : body_triangles)
|
||||
triangles.push_back({t[0], t[1], t[2]});
|
||||
|
||||
auto flat_positions = geom::deindex(positions, triangles);
|
||||
auto flat_positions = math::deindex(positions, triangles);
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
for (auto const & t : flat_positions)
|
||||
{
|
||||
auto n = geom::normal(t[0], t[1], t[2]);
|
||||
auto n = math::normal(t[0], t[1], t[2]);
|
||||
vertices.push_back({t[0], n});
|
||||
vertices.push_back({t[1], n});
|
||||
vertices.push_back({t[2], n});
|
||||
|
|
@ -204,34 +205,37 @@ struct physics_3d_app
|
|||
// engine_.add_object(engine_.add_shape(phys3d::half_space{{ 0.f, -1.f, 0.f}, -10.f}), engine_.add_material({1.f, 1.f, 50.f}), {{0.f, 0.f, 0.f}});
|
||||
}
|
||||
|
||||
void on_resize(int width, int height) override
|
||||
void on_event(app::resize_event const & event) override
|
||||
{
|
||||
app::app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera_.set_fov(camera_.fov_y, (1.f * width) / height);
|
||||
camera_.set_fov(camera_.fov_y, (1.f * event.size[0]) / event.size[1]);
|
||||
}
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||
void on_event(app::mouse_move_event const & event) override
|
||||
{
|
||||
app::app::on_mouse_move(x, y, dx, dy);
|
||||
auto const old_mouse = state().mouse;
|
||||
|
||||
if (is_right_button_down())
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (state().mouse_button_down.contains(app::mouse_button::right))
|
||||
{
|
||||
camera_elevation_angle_tgt_ += dy * 0.003f;
|
||||
camera_azimuthal_angle_tgt_ -= dx * 0.003f;
|
||||
auto const delta = event.position - old_mouse;
|
||||
camera_elevation_angle_tgt_ += delta[1] * 0.003f;
|
||||
camera_azimuthal_angle_tgt_ -= delta[0] * 0.003f;
|
||||
}
|
||||
}
|
||||
|
||||
void on_mouse_wheel(int delta) override
|
||||
void on_event(app::mouse_wheel_event const & event) override
|
||||
{
|
||||
app::app::on_mouse_wheel(delta);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera_distance_tgt_ *= std::pow(0.8f, delta);
|
||||
camera_distance_tgt_ *= std::pow(0.8f, event.delta);
|
||||
}
|
||||
|
||||
void on_key_down(SDL_Keycode key) override
|
||||
void on_event(app::key_event const & event) override
|
||||
{
|
||||
if (key == SDLK_SPACE)
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
{
|
||||
// float r = 7.f;
|
||||
// engine_.add_object(engine_.add_shape(phys3d::ball{3.f}), engine_.add_material({1.f, 0.125f, 50.f}), {{random::uniform(rng_, -r, r), random::uniform(rng_, -r, r), 20.f}});
|
||||
|
|
@ -249,7 +253,7 @@ struct physics_3d_app
|
|||
engine_.add_object(engine_.add_shape(phys3d::box{{1.5f, 1.5f, 1.5f}}), engine_.add_material({1.f, 0.f, 10.f}), state);
|
||||
// engine_.add_object(engine_.add_shape(phys3d::ball{1.f}), engine_.add_material({1.f, 0.5f, 100.f}), state);
|
||||
}
|
||||
else if (key == SDLK_f)
|
||||
else if (event.down && event.key == app::keycode::F)
|
||||
{
|
||||
float const f = 1.f;
|
||||
for (std::uint32_t h = 1; h < engine_.object_count(); ++h)
|
||||
|
|
@ -282,7 +286,7 @@ struct physics_3d_app
|
|||
auto w = engine_.get_object_state(h).angular_velocity;
|
||||
auto H = engine_.get_object_state(h).position[2];
|
||||
|
||||
E += 0.5f * geom::length_sqr(v) * m + 0.5f * geom::dot(w, I * w) + m * 10.f * H;
|
||||
E += 0.5f * math::length_sqr(v) * m + 0.5f * math::dot(w, I * w) + m * 10.f * H;
|
||||
}
|
||||
log::info() << "Energy: " << E;
|
||||
}
|
||||
|
|
@ -303,15 +307,15 @@ struct physics_3d_app
|
|||
program_.bind();
|
||||
program_["u_camera_transform"] = camera_.transform();
|
||||
|
||||
program_["u_light_direction"] = geom::normalized(geom::vector{1.f, 1.f, 1.f});
|
||||
program_["u_light_color"] = geom::vector{1.f, 1.f, 1.f};
|
||||
program_["u_light_direction"] = math::normalized(math::vector{1.f, 1.f, 1.f});
|
||||
program_["u_light_color"] = math::vector{1.f, 1.f, 1.f};
|
||||
|
||||
program_["u_object_transform"] = geom::matrix<float, 4, 4>::identity();
|
||||
program_["u_object_color"] = geom::vector{0.5f, 0.5f, 0.5f};
|
||||
program_["u_object_transform"] = math::matrix<float, 4, 4>::identity();
|
||||
program_["u_object_color"] = math::vector{0.5f, 0.5f, 0.5f};
|
||||
program_["u_grid"] = 0;
|
||||
plane_mesh_.draw();
|
||||
|
||||
program_["u_object_color"] = geom::vector{0.f, 0.f, 1.f};
|
||||
program_["u_object_color"] = math::vector{0.f, 0.f, 1.f};
|
||||
program_["u_grid"] = 1;
|
||||
for (phys3d::engine::object_handle h = 0; h < engine_.object_count(); ++h)
|
||||
{
|
||||
|
|
@ -322,17 +326,17 @@ struct physics_3d_app
|
|||
if (auto s = std::get_if<phys3d::ball const *>(&sh))
|
||||
{
|
||||
program_["u_object_transform"] =
|
||||
geom::translation<float, 3>(engine_.get_object_state(h).position - geom::point<float, 3>::zero()).homogeneous_matrix() *
|
||||
geom::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
|
||||
geom::scale<float, 3>((*s)->radius).homogeneous_matrix();
|
||||
math::translation<float, 3>(engine_.get_object_state(h).position - math::point<float, 3>::zero()).homogeneous_matrix() *
|
||||
math::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
|
||||
math::scale<float, 3>((*s)->radius).homogeneous_matrix();
|
||||
sphere_mesh_.draw();
|
||||
}
|
||||
else if (auto s = std::get_if<phys3d::box const *>(&sh))
|
||||
{
|
||||
program_["u_object_transform"] =
|
||||
geom::translation<float, 3>(engine_.get_object_state(h).position - geom::point<float, 3>::zero()).homogeneous_matrix() *
|
||||
geom::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
|
||||
geom::scale<float, 3>((*s)->dimensions / 2.f).homogeneous_matrix();
|
||||
math::translation<float, 3>(engine_.get_object_state(h).position - math::point<float, 3>::zero()).homogeneous_matrix() *
|
||||
math::quaternion_rotation<float>(engine_.get_object_state(h).rotation).homogeneous_matrix() *
|
||||
math::scale<float, 3>((*s)->dimensions / 2.f).homogeneous_matrix();
|
||||
box_mesh_.draw();
|
||||
}
|
||||
}
|
||||
|
|
@ -341,7 +345,7 @@ struct physics_3d_app
|
|||
private:
|
||||
gfx::program program_;
|
||||
|
||||
geom::spherical_camera camera_;
|
||||
math::spherical_camera camera_;
|
||||
float camera_distance_tgt_;
|
||||
float camera_azimuthal_angle_tgt_;
|
||||
float camera_elevation_angle_tgt_;
|
||||
|
|
@ -358,7 +362,12 @@ private:
|
|||
random::generator rng_;
|
||||
};
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<physics_3d_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<physics_3d_app>({.name = "Physics 3D example"});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
258
examples/platformer.cpp
Normal file
258
examples/platformer.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/math/scale.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/constants.hpp>
|
||||
#include <psemek/math/intersection.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
struct map
|
||||
{
|
||||
std::vector<math::box<float, 2>> platforms;
|
||||
};
|
||||
|
||||
struct player
|
||||
{
|
||||
math::vector<float, 2> size;
|
||||
math::point<float, 2> position;
|
||||
math::vector<float, 2> velocity;
|
||||
|
||||
math::point<float, 2> ghost_position;
|
||||
math::vector<float, 2> ghost_velocity;
|
||||
|
||||
bool grounded = false;
|
||||
|
||||
math::box<float, 2> bbox() const
|
||||
{
|
||||
return math::expand(math::box<float, 2>::singleton(position), size);
|
||||
}
|
||||
};
|
||||
|
||||
struct particle
|
||||
{
|
||||
math::point<float, 2> position;
|
||||
math::vector<float, 2> velocity;
|
||||
float size;
|
||||
float lifetime;
|
||||
gfx::color_rgba color;
|
||||
};
|
||||
|
||||
struct platformer_app
|
||||
: app::application_base
|
||||
{
|
||||
platformer_app(options const &, context const &)
|
||||
{
|
||||
map_.platforms.push_back({{{-10.f, 10.f}, {-5.f, -4.f}}});
|
||||
|
||||
map_.platforms.push_back({{{-5.f, -3.f}, {-2.5f, -2.f}}});
|
||||
map_.platforms.push_back({{{-1.f, 1.f}, {-2.f, -1.5f}}});
|
||||
map_.platforms.push_back({{{ 3.f, 5.f}, {-1.5f, -1.f}}});
|
||||
|
||||
map_.platforms.push_back({{{-7.f, -6.f}, {-3.5f, -3.f}}});
|
||||
|
||||
player_.size = {0.25f, 0.5f};
|
||||
player_.position = {0.f, 5.f};
|
||||
player_.velocity = {0.f, 0.f};
|
||||
player_.ghost_position = player_.position;
|
||||
player_.ghost_velocity = {0.f, 0.f};
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
float const dt = frame_clock_.restart().count();
|
||||
|
||||
float const gravity = -50.f;
|
||||
float const acceleration = 200.f;
|
||||
float const friction = 25.f;
|
||||
float const jump_speed = 15.f;
|
||||
|
||||
math::vector const ghost_spring_force{200.f, 500.f};
|
||||
math::vector const ghost_spring_damping{10.f, 20.f};
|
||||
|
||||
float const particle_grow_speed = 0.1f;
|
||||
float const move_particle_spawn_period = 1.f / 32.f;
|
||||
int const jump_particle_count = 16;
|
||||
int const land_particle_count = 32;
|
||||
|
||||
player_.velocity[0] *= std::exp(- friction * dt);
|
||||
|
||||
player_.velocity[1] += dt * gravity;
|
||||
|
||||
player_.position += player_.velocity * dt;
|
||||
|
||||
{
|
||||
auto v = (player_.position - player_.ghost_position);
|
||||
player_.ghost_velocity += math::pointwise_mult(v, ghost_spring_force) * dt;
|
||||
player_.ghost_velocity[0] *= std::exp(- ghost_spring_damping[0] * dt);
|
||||
player_.ghost_velocity[1] *= std::exp(- ghost_spring_damping[1] * dt);
|
||||
}
|
||||
player_.ghost_position += player_.ghost_velocity * dt;
|
||||
|
||||
bool was_grounded = player_.grounded;
|
||||
auto velocity_before_collision = player_.velocity;
|
||||
|
||||
player_.grounded = false;
|
||||
|
||||
for (auto const & platform : map_.platforms)
|
||||
{
|
||||
auto const player_box = player_.bbox();
|
||||
auto const intersection = platform & player_box;
|
||||
if (intersection.empty()) continue;
|
||||
|
||||
if (intersection[0].length() < intersection[1].length())
|
||||
{
|
||||
if (intersection[0].center() < player_.position[0])
|
||||
player_.position[0] += intersection[0].length();
|
||||
else
|
||||
player_.position[0] -= intersection[0].length();
|
||||
|
||||
player_.velocity[0] = 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (intersection[1].center() < player_.position[1])
|
||||
{
|
||||
player_.position[1] += intersection[1].length();
|
||||
player_.grounded = true;
|
||||
}
|
||||
else
|
||||
player_.position[1] -= intersection[1].length();
|
||||
|
||||
player_.velocity[1] = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
if (state().key_down.contains(app::keycode::A))
|
||||
player_.velocity[0] -= acceleration * dt;
|
||||
if (state().key_down.contains(app::keycode::D))
|
||||
player_.velocity[0] += acceleration * dt;
|
||||
|
||||
if (player_.grounded && (state().key_down.contains(app::keycode::A) ^ state().key_down.contains(app::keycode::D)))
|
||||
{
|
||||
move_particle_spawn_timer_ += dt;
|
||||
if (move_particle_spawn_timer_ > move_particle_spawn_period)
|
||||
{
|
||||
move_particle_spawn_timer_ -= move_particle_spawn_period;
|
||||
float s = state().key_down.contains(app::keycode::A) ? 1.f : -1.f;
|
||||
|
||||
auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]};
|
||||
auto velocity = math::direction(random::uniform(rng_, math::rad(60.f), math::rad(120.f)));
|
||||
auto size = random::uniform(rng_, 0.05f, 0.15f);
|
||||
auto lifetime = random::uniform(rng_, 0.25f, 0.5f);
|
||||
int c = random::uniform(rng_, 63, 191);
|
||||
gfx::color_rgba color{c, 0, c, 255};
|
||||
|
||||
particles_.push_back({position, velocity, size, lifetime, color});
|
||||
}
|
||||
}
|
||||
|
||||
if (player_.grounded && state().key_down.contains(app::keycode::W))
|
||||
{
|
||||
player_.velocity[1] += jump_speed;
|
||||
|
||||
for (int i = 0; i < jump_particle_count; ++i)
|
||||
{
|
||||
float s = random::uniform(rng_, -1.f, 1.f);
|
||||
|
||||
auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]};
|
||||
auto velocity = (random::uniform<bool>(rng_) ? 1.f : -1.f) * math::direction(random::uniform(rng_, math::rad(-30.f), math::rad(30.f))) * 3.f;
|
||||
auto size = random::uniform(rng_, 0.05f, 0.15f);
|
||||
auto lifetime = random::uniform(rng_, 0.25f, 0.5f);
|
||||
int c = random::uniform(rng_, 63, 191);
|
||||
gfx::color_rgba color{c, 0, c, 255};
|
||||
|
||||
particles_.push_back({position, velocity, size, lifetime, color});
|
||||
}
|
||||
}
|
||||
|
||||
if (!was_grounded && player_.grounded)
|
||||
{
|
||||
for (int i = 0; i < land_particle_count; ++i)
|
||||
{
|
||||
float s = random::uniform(rng_, -1.f, 1.f);
|
||||
|
||||
auto position = player_.position + math::vector{s * player_.size[0], -player_.size[1]};
|
||||
auto velocity = math::direction(random::uniform(rng_, math::rad(0.f), math::rad(180.f))) * (-velocity_before_collision[1]) * 0.2f;
|
||||
auto size = random::uniform(rng_, 0.05f, 0.15f);
|
||||
auto lifetime = random::uniform(rng_, 0.25f, 0.5f);
|
||||
int c = random::uniform(rng_, 63, 191);
|
||||
gfx::color_rgba color{c, 0, c, 255};
|
||||
|
||||
particles_.push_back({position, velocity, size, lifetime, color});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<particle> alive_particles;
|
||||
for (auto & p : particles_)
|
||||
{
|
||||
p.lifetime -= dt;
|
||||
if (p.lifetime <= 0.f) continue;
|
||||
|
||||
p.position += p.velocity * dt;
|
||||
p.size += particle_grow_speed * dt;
|
||||
alive_particles.push_back(p);
|
||||
}
|
||||
particles_ = std::move(alive_particles);
|
||||
}
|
||||
|
||||
void present() override
|
||||
{
|
||||
gl::ClearColor(1.f, 1.f, 1.f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
for (auto const & box : map_.platforms)
|
||||
painter_.rect(box, {0, 0, 0, 255});
|
||||
|
||||
{
|
||||
math::point bottom = player_.position - math::vector{0.f, player_.size[1]};
|
||||
math::point top = player_.ghost_position + math::vector{0.f, player_.size[1]};
|
||||
|
||||
auto d = math::vector{player_.size[0], 0.f};
|
||||
|
||||
gfx::color_rgba color{255, 0, 0, 255};
|
||||
painter_.triangle(bottom - d, bottom + d, top - d, color);
|
||||
painter_.triangle(bottom + d, top - d, top + d, color);
|
||||
}
|
||||
|
||||
for (auto const & p : particles_)
|
||||
{
|
||||
auto colorf = gfx::to_colorf(p.color);
|
||||
colorf[3] *= 1.f - std::exp(- 2.f * p.lifetime);
|
||||
painter_.circle(p.position, p.size, gfx::to_coloru8(colorf));
|
||||
}
|
||||
|
||||
float const aspect_ratio = state().size[0] * 1.f / state().size[1];
|
||||
float const view_size = 5.f;
|
||||
math::box<float, 2> view_box{{{-view_size * aspect_ratio, view_size * aspect_ratio}, {-view_size, view_size}}};
|
||||
|
||||
painter_.render(math::orthographic_camera{view_box}.transform());
|
||||
}
|
||||
|
||||
private:
|
||||
map map_;
|
||||
player player_;
|
||||
std::vector<particle> particles_;
|
||||
float move_particle_spawn_timer_ = 0.f;
|
||||
|
||||
random::generator rng_;
|
||||
|
||||
util::clock<> frame_clock_;
|
||||
gfx::painter painter_;
|
||||
};
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<platformer_app>({.name = "Platformer example"});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/gfx/program.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
|
|
@ -9,11 +9,11 @@
|
|||
#include <psemek/gfx/framebuffer.hpp>
|
||||
#include <psemek/gfx/error.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/mesh.hpp>
|
||||
#include <psemek/geom/homogeneous.hpp>
|
||||
#include <psemek/geom/gram_schmidt.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/mesh.hpp>
|
||||
#include <psemek/math/homogeneous.hpp>
|
||||
#include <psemek/math/gram_schmidt.hpp>
|
||||
|
||||
#include <psemek/util/clock.hpp>
|
||||
|
||||
|
|
@ -96,16 +96,16 @@ struct phong_renderer
|
|||
|
||||
struct light
|
||||
{
|
||||
geom::vector<float, 4> position;
|
||||
math::vector<float, 4> position;
|
||||
};
|
||||
|
||||
struct render_options
|
||||
{
|
||||
gfx::framebuffer const * framebuffer;
|
||||
GLenum draw_buffer;
|
||||
geom::box<int, 2> viewport;
|
||||
geom::matrix<float, 4, 4> transform;
|
||||
geom::point<float, 3> camera_position;
|
||||
math::box<int, 2> viewport;
|
||||
math::matrix<float, 4, 4> transform;
|
||||
math::point<float, 3> camera_position;
|
||||
struct light light;
|
||||
};
|
||||
|
||||
|
|
@ -134,13 +134,13 @@ void phong_renderer::render(render_options const & options)
|
|||
program_.bind();
|
||||
program_["u_transform"] = options.transform;
|
||||
program_["u_light_position"] = options.light.position;
|
||||
program_["u_camera_position"] = geom::homogeneous(options.camera_position);
|
||||
program_["u_camera_position"] = math::homogeneous(options.camera_position);
|
||||
|
||||
for (auto const & state : render_states_)
|
||||
{
|
||||
if (!state.mesh) continue;
|
||||
|
||||
program_["u_material"] = geom::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
|
||||
program_["u_material"] = math::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
|
||||
state.mesh->draw();
|
||||
}
|
||||
|
||||
|
|
@ -177,12 +177,12 @@ struct shadow_map_builder
|
|||
struct render_state
|
||||
{
|
||||
gfx::mesh const * mesh;
|
||||
geom::box<float, 3> bbox;
|
||||
math::box<float, 3> bbox;
|
||||
};
|
||||
|
||||
struct render_options
|
||||
{
|
||||
geom::vector<float, 3> light;
|
||||
math::vector<float, 3> light;
|
||||
};
|
||||
|
||||
shadow_map_builder(std::size_t width, std::size_t height);
|
||||
|
|
@ -191,7 +191,7 @@ struct shadow_map_builder
|
|||
|
||||
void build(render_options const & options);
|
||||
|
||||
geom::matrix<float, 4, 4> const & transform() const;
|
||||
math::matrix<float, 4, 4> const & transform() const;
|
||||
|
||||
gfx::texture_2d const & texture() const;
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ private:
|
|||
gfx::framebuffer framebuffer_;
|
||||
gfx::texture_2d depth_texture_;
|
||||
|
||||
geom::matrix<float, 4, 4> transform_;
|
||||
math::matrix<float, 4, 4> transform_;
|
||||
};
|
||||
|
||||
shadow_map_builder::shadow_map_builder(std::size_t width, std::size_t height)
|
||||
|
|
@ -224,7 +224,7 @@ void shadow_map_builder::push(render_state const & state)
|
|||
|
||||
void shadow_map_builder::build(render_options const & options)
|
||||
{
|
||||
geom::vector<float, 3> light_axes[3];
|
||||
math::vector<float, 3> light_axes[3];
|
||||
|
||||
light_axes[2] = -options.light;
|
||||
light_axes[1] = {0.f, 0.f, 1.f};
|
||||
|
|
@ -232,24 +232,24 @@ void shadow_map_builder::build(render_options const & options)
|
|||
if (light_axes[2][2] > 0.5f)
|
||||
light_axes[1] = {0.f, 1.f, 0.f};
|
||||
|
||||
geom::gram_schmidt(light_axes[2], light_axes[1]);
|
||||
math::gram_schmidt(light_axes[2], light_axes[1]);
|
||||
|
||||
light_axes[0] = geom::cross(light_axes[2], light_axes[1]);
|
||||
light_axes[0] = math::cross(light_axes[2], light_axes[1]);
|
||||
|
||||
geom::box<float, 3> light_bbox;
|
||||
math::box<float, 3> light_bbox;
|
||||
|
||||
geom::point<float, 3> origin = geom::point<float, 3>::zero();
|
||||
math::point<float, 3> origin = math::point<float, 3>::zero();
|
||||
|
||||
for (auto const & state : render_states_)
|
||||
{
|
||||
for (auto const & v : geom::vertices(state.bbox))
|
||||
for (auto const & v : math::vertices(state.bbox))
|
||||
{
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
light_bbox[i] |= geom::dot(light_axes[i], v - origin);
|
||||
light_bbox[i] |= math::dot(light_axes[i], v - origin);
|
||||
}
|
||||
}
|
||||
|
||||
transform_ = geom::orthographic_camera{light_bbox}.projection() * geom::homogeneous(geom::by_rows(light_axes[0], light_axes[1], light_axes[2]));
|
||||
transform_ = math::orthographic_camera{light_bbox}.projection() * math::homogeneous(math::by_rows(light_axes[0], light_axes[1], light_axes[2]));
|
||||
|
||||
framebuffer_.bind();
|
||||
gl::DrawBuffer(gl::NONE);
|
||||
|
|
@ -289,7 +289,7 @@ void shadow_map_builder::build(render_options const & options)
|
|||
*/
|
||||
}
|
||||
|
||||
geom::matrix<float, 4, 4> const & shadow_map_builder::transform() const
|
||||
math::matrix<float, 4, 4> const & shadow_map_builder::transform() const
|
||||
{
|
||||
return transform_;
|
||||
}
|
||||
|
|
@ -399,21 +399,21 @@ struct shadow_renderer
|
|||
struct material material;
|
||||
gfx::mesh const * mesh;
|
||||
bool casts_shadow;
|
||||
std::optional<geom::box<float, 3>> bbox;
|
||||
std::optional<math::box<float, 3>> bbox;
|
||||
};
|
||||
|
||||
struct light
|
||||
{
|
||||
geom::vector<float, 4> position;
|
||||
math::vector<float, 4> position;
|
||||
};
|
||||
|
||||
struct render_options
|
||||
{
|
||||
gfx::framebuffer const * framebuffer;
|
||||
GLenum draw_buffer;
|
||||
geom::box<int, 2> viewport;
|
||||
geom::matrix<float, 4, 4> transform;
|
||||
geom::point<float, 3> camera_position;
|
||||
math::box<int, 2> viewport;
|
||||
math::matrix<float, 4, 4> transform;
|
||||
math::point<float, 3> camera_position;
|
||||
struct light light;
|
||||
};
|
||||
|
||||
|
|
@ -460,7 +460,7 @@ void shadow_renderer::render(render_options const & options)
|
|||
program_.bind();
|
||||
program_["u_transform"] = options.transform;
|
||||
program_["u_light_position"] = options.light.position;
|
||||
program_["u_camera_position"] = geom::homogeneous(options.camera_position);
|
||||
program_["u_camera_position"] = math::homogeneous(options.camera_position);
|
||||
program_["u_shadow_transform"] = builder_.transform();
|
||||
program_["u_shadow_map"] = 0;
|
||||
|
||||
|
|
@ -469,7 +469,7 @@ void shadow_renderer::render(render_options const & options)
|
|||
|
||||
for (auto const & state : render_states_)
|
||||
{
|
||||
program_["u_material"] = geom::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
|
||||
program_["u_material"] = math::vector{state.material.ambient, state.material.diffuse, state.material.specular, state.material.shininess};
|
||||
state.mesh->draw();
|
||||
}
|
||||
|
||||
|
|
@ -478,15 +478,15 @@ void shadow_renderer::render(render_options const & options)
|
|||
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> position;
|
||||
geom::vector<float, 3> normal;
|
||||
math::point<float, 3> position;
|
||||
math::vector<float, 3> normal;
|
||||
gfx::color_rgba color;
|
||||
};
|
||||
|
||||
struct shadow_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
geom::spherical_camera camera;
|
||||
math::spherical_camera camera;
|
||||
|
||||
shadow_renderer renderer;
|
||||
|
||||
|
|
@ -495,44 +495,44 @@ struct shadow_app
|
|||
|
||||
gfx::mesh cube_mesh;
|
||||
shadow_renderer::material cube_material;
|
||||
geom::box<float, 3> cube_bbox;
|
||||
math::box<float, 3> cube_bbox;
|
||||
|
||||
gfx::mesh sphere_mesh;
|
||||
shadow_renderer::material sphere_material;
|
||||
geom::box<float, 3> sphere_bbox;
|
||||
math::box<float, 3> sphere_bbox;
|
||||
|
||||
gfx::mesh torus_mesh;
|
||||
shadow_renderer::material torus_material;
|
||||
geom::box<float, 3> torus_bbox;
|
||||
math::box<float, 3> torus_bbox;
|
||||
|
||||
util::clock<std::chrono::duration<float>> clock;
|
||||
float time = 0.f;
|
||||
bool paused = false;
|
||||
|
||||
shadow_app();
|
||||
shadow_app(options const &, context const &);
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override;
|
||||
void on_event(app::mouse_move_event const & event) override;
|
||||
|
||||
void on_mouse_wheel(int delta) override;
|
||||
void on_event(app::mouse_wheel_event const & event) override;
|
||||
|
||||
void on_key_down(SDL_Keycode key) override;
|
||||
void on_event(app::key_event const & event) override;
|
||||
|
||||
void update() override {}
|
||||
void present() override;
|
||||
};
|
||||
|
||||
shadow_app::shadow_app()
|
||||
: app("Shadow")
|
||||
shadow_app::shadow_app(options const &, context const & context)
|
||||
{
|
||||
vsync(true);
|
||||
context.vsync(true);
|
||||
|
||||
camera.near_clip = 0.1f;
|
||||
camera.far_clip = 1000.f;
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.fov_y = math::rad(45.f);
|
||||
|
||||
camera.azimuthal_angle = 0.f;
|
||||
camera.elevation_angle = geom::rad(30.f);
|
||||
camera.elevation_angle = math::rad(30.f);
|
||||
camera.target = {0.f, 0.f, 0.f};
|
||||
camera.distance = 10.f;
|
||||
|
||||
|
|
@ -547,7 +547,7 @@ shadow_app::shadow_app()
|
|||
vertices.push_back({{ 10.f, -10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
|
||||
vertices.push_back({{ 10.f, 10.f, 0.f}, {0.f, 0.f, 1.f}, {127, 127, 127, 255}});
|
||||
|
||||
plane_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
plane_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
plane_mesh.load(vertices, gl::TRIANGLES, gl::STATIC_DRAW);
|
||||
|
||||
plane_material.ambient = 0.2f;
|
||||
|
|
@ -557,12 +557,12 @@ shadow_app::shadow_app()
|
|||
}
|
||||
|
||||
{
|
||||
auto cube = geom::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {0.f, 5.f}}};
|
||||
auto cube = math::box<float, 3>{{{-1.f, 1.f}, {-1.f, 1.f}, {0.f, 5.f}}};
|
||||
|
||||
auto vertices = geom::vertices(cube);
|
||||
auto faces = geom::faces(cube);
|
||||
auto normals = geom::flat_normals(vertices, faces);
|
||||
auto flat_vertices = geom::deindex(vertices, faces);
|
||||
auto vertices = math::vertices(cube);
|
||||
auto faces = math::faces(cube);
|
||||
auto normals = math::flat_normals(vertices, faces);
|
||||
auto flat_vertices = math::deindex(vertices, faces);
|
||||
|
||||
std::vector<vertex> mesh_vertices;
|
||||
|
||||
|
|
@ -573,7 +573,7 @@ shadow_app::shadow_app()
|
|||
mesh_vertices.push_back({flat_vertices[i][2], normals[i], {255, 127, 127, 255}});
|
||||
}
|
||||
|
||||
cube_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
cube_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
cube_mesh.load(mesh_vertices, gl::TRIANGLES, gl::STATIC_DRAW);
|
||||
|
||||
cube_material.ambient = 0.2f;
|
||||
|
|
@ -587,7 +587,7 @@ shadow_app::shadow_app()
|
|||
{
|
||||
std::vector<vertex> vertices;
|
||||
|
||||
geom::point<float, 3> const position = {3.f, 2.f, 3.f};
|
||||
math::point<float, 3> const position = {3.f, 2.f, 3.f};
|
||||
|
||||
float const radius = 1.f;
|
||||
|
||||
|
|
@ -599,19 +599,19 @@ shadow_app::shadow_app()
|
|||
{
|
||||
for (int i = 0; i < 4 * N; ++i)
|
||||
{
|
||||
float a = (geom::pi * i) / (2 * N);
|
||||
float b = (geom::pi * j) / (2 * N);
|
||||
float a = (math::pi * i) / (2 * N);
|
||||
float b = (math::pi * j) / (2 * N);
|
||||
|
||||
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
|
||||
vertices.push_back({position + radius * n, n, color});
|
||||
}
|
||||
}
|
||||
|
||||
vertices.push_back({position + geom::vector{0.f, 0.f, -radius}, {0.f, 0.f, -1.f}, color});
|
||||
vertices.push_back({position + geom::vector{0.f, 0.f, radius}, {0.f, 0.f, 1.f}, color});
|
||||
vertices.push_back({position + math::vector{0.f, 0.f, -radius}, {0.f, 0.f, -1.f}, color});
|
||||
vertices.push_back({position + math::vector{0.f, 0.f, radius}, {0.f, 0.f, 1.f}, color});
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> indices;
|
||||
std::vector<math::triangle<std::uint32_t>> indices;
|
||||
|
||||
auto idx = [](int i, int j) -> std::uint32_t { return (i % (4 * N)) + 4 * N * (j + N - 1); };
|
||||
|
||||
|
|
@ -634,7 +634,7 @@ shadow_app::shadow_app()
|
|||
indices.push_back({idx(i, N - 1), idx(i + 1, N - 1), (2 * N - 1) * (4 * N) + 1});
|
||||
}
|
||||
|
||||
sphere_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
sphere_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
sphere_mesh.load(vertices, indices, gl::STATIC_DRAW);
|
||||
|
||||
sphere_material.ambient = 0.2f;
|
||||
|
|
@ -650,7 +650,7 @@ shadow_app::shadow_app()
|
|||
{
|
||||
std::vector<vertex> vertices;
|
||||
|
||||
geom::point<float, 3> const position = {-3.f, 2.f, 3.f};
|
||||
math::point<float, 3> const position = {-3.f, 2.f, 3.f};
|
||||
|
||||
float const radius1 = 1.f;
|
||||
float const radius2 = 0.2f;
|
||||
|
|
@ -664,18 +664,18 @@ shadow_app::shadow_app()
|
|||
{
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
float a = (2.f * geom::pi * i) / N;
|
||||
float b = (2.f * geom::pi * j) / M;
|
||||
float a = (2.f * math::pi * i) / N;
|
||||
float b = (2.f * math::pi * j) / M;
|
||||
|
||||
geom::vector r{std::cos(a), std::sin(a), 0.f};
|
||||
math::vector r{std::cos(a), std::sin(a), 0.f};
|
||||
|
||||
geom::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
math::vector n{std::cos(a) * std::cos(b), std::sin(a) * std::cos(b), std::sin(b)};
|
||||
|
||||
vertices.push_back({position + radius1 * r + radius2 * n, n, color});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<geom::triangle<std::uint32_t>> indices;
|
||||
std::vector<math::triangle<std::uint32_t>> indices;
|
||||
|
||||
auto idx = [](int i, int j) -> std::uint32_t { return (i % N) + N * (j % M); };
|
||||
|
||||
|
|
@ -688,7 +688,7 @@ shadow_app::shadow_app()
|
|||
}
|
||||
}
|
||||
|
||||
torus_mesh.setup<geom::point<float, 3>, geom::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
torus_mesh.setup<math::point<float, 3>, math::vector<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
torus_mesh.load(vertices, indices, gl::STATIC_DRAW);
|
||||
|
||||
torus_material.ambient = 0.2f;
|
||||
|
|
@ -702,34 +702,39 @@ shadow_app::shadow_app()
|
|||
}
|
||||
}
|
||||
|
||||
void shadow_app::on_resize(int width, int height)
|
||||
void shadow_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera.set_fov(camera.fov_y, (1.f * width) / height);
|
||||
camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
|
||||
}
|
||||
|
||||
void shadow_app::on_mouse_move(int x, int y, int dx, int dy)
|
||||
void shadow_app::on_event(app::mouse_move_event const & event)
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
auto const old_mouse = state().mouse;
|
||||
|
||||
if (is_middle_button_down())
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (state().mouse_button_down.contains(app::mouse_button::middle))
|
||||
{
|
||||
camera.azimuthal_angle -= dx * 0.01f;
|
||||
camera.elevation_angle += dy * 0.01f;
|
||||
auto const delta = event.position - old_mouse;
|
||||
camera.azimuthal_angle -= delta[0] * 0.01f;
|
||||
camera.elevation_angle += delta[1] * 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
void shadow_app::on_mouse_wheel(int delta)
|
||||
void shadow_app::on_event(app::mouse_wheel_event const & event)
|
||||
{
|
||||
app::on_mouse_wheel(delta);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera.distance *= std::pow(0.8f, delta);
|
||||
camera.distance *= std::pow(0.8f, event.delta);
|
||||
}
|
||||
|
||||
void shadow_app::on_key_down(SDL_Keycode key)
|
||||
void shadow_app::on_event(app::key_event const & event)
|
||||
{
|
||||
if (key == SDLK_SPACE)
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
paused = !paused;
|
||||
}
|
||||
|
||||
|
|
@ -740,12 +745,12 @@ void shadow_app::present()
|
|||
else
|
||||
clock.restart();
|
||||
|
||||
geom::vector<float, 3> light_dir = {std::cos(time), std::sin(time), 0.5f};
|
||||
math::vector<float, 3> light_dir = {std::cos(time), std::sin(time), 0.5f};
|
||||
|
||||
gfx::framebuffer::null().bind();
|
||||
gl::DrawBuffer(gl::BACK);
|
||||
|
||||
gl::Viewport(0, 0, width(), height());
|
||||
gl::Viewport(0, 0, state().size[0], state().size[1]);
|
||||
|
||||
gl::ClearColor(0.7f, 0.7f, 1.f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||||
|
|
@ -760,10 +765,10 @@ void shadow_app::present()
|
|||
|
||||
shadow_renderer::render_options options;
|
||||
options.framebuffer = &gfx::framebuffer::null();
|
||||
options.viewport = {{{0, width()}, {0, height()}}};
|
||||
options.viewport = {{{0, state().size[0]}, {0, state().size[1]}}};
|
||||
options.draw_buffer = gl::BACK;
|
||||
options.transform = camera.transform();
|
||||
options.light.position = geom::homogeneous(light_dir);
|
||||
options.light.position = math::homogeneous(light_dir);
|
||||
options.camera_position = camera.position();
|
||||
|
||||
renderer.render(options);
|
||||
|
|
@ -771,7 +776,13 @@ void shadow_app::present()
|
|||
gfx::check_error();
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<shadow_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<shadow_app>({.name = "Shadow example"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
957
examples/soft_creatures_2d.cpp
Normal file
957
examples/soft_creatures_2d.cpp
Normal file
|
|
@ -0,0 +1,957 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/phys/engine_2d.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/math/simplex.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/scale.hpp>
|
||||
#include <psemek/util/hash_table.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
#include <psemek/random/normal.hpp>
|
||||
#include <psemek/random/weighted.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/util/thread.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <deque>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
static float const sim_dt = 0.005f;
|
||||
static float const gravity = 25.f;
|
||||
static float const air_friction = 0.1f;
|
||||
static float const ground_friction = 100.f;
|
||||
static float const hills_friction = 100.f;
|
||||
static float const spring_damping = 10.f;
|
||||
static float const max_spring_force = 25000.f;
|
||||
|
||||
static math::interval<float> const cell_size_range{1.f, 1.f};
|
||||
static math::interval<float> const cell_period_range{0.25f, 4.f};
|
||||
static float const muscle_expand_extent = 0.5f;
|
||||
|
||||
static int const population_size = 4 * 1024;
|
||||
|
||||
static float const simulation_time = 30.f;
|
||||
static float const display_simulation_time = 30.f;
|
||||
|
||||
enum class cell_type
|
||||
{
|
||||
hard_tissue,
|
||||
soft_tissue,
|
||||
muscle,
|
||||
dead,
|
||||
};
|
||||
|
||||
struct cell_info
|
||||
{
|
||||
cell_type type;
|
||||
float size = 1.f;
|
||||
float phase = 0.f;
|
||||
float period = 1.f;
|
||||
};
|
||||
|
||||
float spring_force(cell_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case cell_type::soft_tissue:
|
||||
return 2000.f;
|
||||
case cell_type::hard_tissue:
|
||||
return 5000.f;
|
||||
case cell_type::muscle:
|
||||
return 2000.f;
|
||||
case cell_type::dead:
|
||||
return 100.f;
|
||||
}
|
||||
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
static math::vector<int, 2> const neighbours[4] =
|
||||
{
|
||||
{-1, 0},
|
||||
{ 1, 0},
|
||||
{ 0, -1},
|
||||
{ 0, 1},
|
||||
};
|
||||
|
||||
struct map
|
||||
{
|
||||
math::vector<float, 2> ground_normal{0.f, 1.f};
|
||||
|
||||
std::vector<math::point<float, 2>> ground_points;
|
||||
};
|
||||
|
||||
using creature_blueprint = std::unordered_map<math::point<int, 2>, cell_info>;
|
||||
|
||||
struct creature
|
||||
{
|
||||
struct cell
|
||||
{
|
||||
std::uint32_t indices[4];
|
||||
cell_info info;
|
||||
};
|
||||
|
||||
creature_blueprint blueprint;
|
||||
int generation = 0;
|
||||
std::optional<float> score = std::nullopt;
|
||||
|
||||
std::vector<math::point<float, 2>> positions;
|
||||
std::vector<math::vector<float, 2>> velocities;
|
||||
std::vector<cell> cells;
|
||||
|
||||
std::vector<math::segment<std::uint32_t>> outer_edges;
|
||||
|
||||
math::point<float, 2> center() const;
|
||||
math::vector<float, 2> center_velocity() const;
|
||||
void translate(math::vector<float, 2> const & delta);
|
||||
void push(math::vector<float, 2> const & delta_velocity);
|
||||
|
||||
bool dead() const;
|
||||
|
||||
void update(float dt);
|
||||
void collide(map const & map, float dt);
|
||||
|
||||
void compute_outer_edges();
|
||||
};
|
||||
|
||||
math::point<float, 2> creature::center() const
|
||||
{
|
||||
math::vector<float, 2> sum{0.f, 0.f};
|
||||
for (int i = 1; i < positions.size(); ++i)
|
||||
{
|
||||
sum += positions[i] - positions[0];
|
||||
}
|
||||
return positions[0] + sum / (1.f * positions.size());
|
||||
}
|
||||
|
||||
math::vector<float, 2> creature::center_velocity() const
|
||||
{
|
||||
math::vector<float, 2> sum{0.f, 0.f};
|
||||
for (int i = 1; i < velocities.size(); ++i)
|
||||
{
|
||||
sum += velocities[i];
|
||||
}
|
||||
return sum / (1.f * velocities.size());
|
||||
}
|
||||
|
||||
void creature::translate(math::vector<float, 2> const & delta)
|
||||
{
|
||||
for (auto & pos : positions)
|
||||
pos += delta;
|
||||
}
|
||||
|
||||
void creature::push(math::vector<float, 2> const & delta_velocity)
|
||||
{
|
||||
for (auto & vel : velocities)
|
||||
vel += delta_velocity;
|
||||
}
|
||||
|
||||
bool creature::dead() const
|
||||
{
|
||||
for (auto const & cell : cells)
|
||||
if (cell.info.type == cell_type::dead)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void creature::update(float dt)
|
||||
{
|
||||
for (auto & vel : velocities)
|
||||
vel[1] -= gravity * dt;
|
||||
|
||||
for (auto & vel : velocities)
|
||||
{
|
||||
auto v = math::length(vel);
|
||||
vel -= air_friction * v * vel * dt;
|
||||
}
|
||||
|
||||
for (int i = 0; i < positions.size(); ++i)
|
||||
positions[i] += velocities[i] * dt;
|
||||
|
||||
static math::vector<float, 2> const deltas[4]
|
||||
{
|
||||
{-0.5f, -0.5f},
|
||||
{ 0.5f, -0.5f},
|
||||
{ 0.5f, 0.5f},
|
||||
{-0.5f, 0.5f},
|
||||
};
|
||||
|
||||
for (auto & cell : cells)
|
||||
{
|
||||
float size = cell.info.size;
|
||||
|
||||
if (cell.info.type == cell_type::muscle)
|
||||
{
|
||||
cell.info.phase += dt / cell.info.period;
|
||||
while (cell.info.phase >= 1.f)
|
||||
cell.info.phase -= 1.f;
|
||||
size *= 1.f + muscle_expand_extent * std::sin(cell.info.phase * 2.f * math::pi);
|
||||
}
|
||||
|
||||
math::point<float, 2> center{0.f, 0.f};
|
||||
for (auto idx : cell.indices)
|
||||
{
|
||||
center += (positions[idx] - math::point{0.f, 0.f}) * 0.25f;
|
||||
}
|
||||
|
||||
float A = 0.f;
|
||||
float B = 0.f;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
auto const p = positions[cell.indices[i]] - center;
|
||||
auto const q = deltas[i] * size;
|
||||
|
||||
A += math::dot(p, q);
|
||||
B += math::det(p, q);
|
||||
}
|
||||
|
||||
float const angle = - std::atan2(B, A);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
auto const target = center + math::rotate(deltas[i] * size, angle);
|
||||
|
||||
auto force = spring_force(cell.info.type) * (target - positions[cell.indices[i]]);
|
||||
if (auto f = math::length(force); f > max_spring_force)
|
||||
cell.info.type = cell_type::dead;
|
||||
velocities[cell.indices[i]] += force * dt;
|
||||
}
|
||||
|
||||
auto cmvel = math::vector{0.f, 0.f};
|
||||
auto rotation = 0.f;
|
||||
for (auto idx : cell.indices)
|
||||
{
|
||||
cmvel += velocities[idx] * 0.25f;
|
||||
rotation += math::det(positions[idx] - center, velocities[idx]) * (0.25f / math::length_sqr(positions[idx] - center));
|
||||
}
|
||||
|
||||
for (auto idx : cell.indices)
|
||||
{
|
||||
auto target_vel = cmvel + math::ort(positions[idx] - center) * rotation;
|
||||
|
||||
velocities[idx] += (target_vel - velocities[idx]) * (1.f - std::exp(- spring_damping * dt));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void creature::collide(map const & map, float dt)
|
||||
{
|
||||
for (int i = 0; i < positions.size(); ++i)
|
||||
{
|
||||
auto delta = positions[i] - math::point<float, 2>{0.f, 0.f};
|
||||
auto dist = math::dot(delta, map.ground_normal);
|
||||
if (dist < 0.f)
|
||||
{
|
||||
positions[i] -= dist * map.ground_normal;
|
||||
|
||||
auto tangent = math::ort(map.ground_normal);
|
||||
auto vn = math::dot(velocities[i], map.ground_normal);
|
||||
auto vt = math::dot(velocities[i], tangent);
|
||||
|
||||
vn = std::max(0.f, vn);
|
||||
vt *= std::exp(- dt * ground_friction);
|
||||
|
||||
velocities[i] = map.ground_normal * vn + tangent * vt;
|
||||
}
|
||||
|
||||
auto it = std::upper_bound(map.ground_points.begin(), map.ground_points.end(), positions[i][0], [](float v, auto const & p){ return v < p[0]; });
|
||||
if (it != map.ground_points.begin() && it != map.ground_points.end())
|
||||
{
|
||||
auto j = (it - map.ground_points.begin()) - 1;
|
||||
|
||||
auto n = math::normalized(math::ort(map.ground_points[j + 1] - map.ground_points[j]));
|
||||
float dist = math::dot(positions[i] - map.ground_points[j], n);
|
||||
|
||||
if (dist < 0.f)
|
||||
{
|
||||
positions[i] -= dist * n;
|
||||
|
||||
auto tangent = math::ort(n);
|
||||
auto vn = math::dot(velocities[i], n);
|
||||
auto vt = math::dot(velocities[i], tangent);
|
||||
|
||||
vn = std::max(0.f, vn);
|
||||
vt *= std::exp(- dt * hills_friction);
|
||||
|
||||
velocities[i] = n * vn + tangent * vt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void creature::compute_outer_edges()
|
||||
{
|
||||
util::hash_set<math::segment<std::uint32_t>> edge_set;
|
||||
|
||||
for (auto const & cell : cells)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
math::segment<std::uint32_t> segment;
|
||||
segment[0] = cell.indices[i];
|
||||
segment[1] = cell.indices[(i + 1) % 4];
|
||||
edge_set.insert(segment);
|
||||
}
|
||||
}
|
||||
|
||||
outer_edges.clear();
|
||||
for (auto const & edge : edge_set)
|
||||
{
|
||||
auto dual = edge;
|
||||
std::swap(dual[0], dual[1]);
|
||||
if (!edge_set.contains(dual))
|
||||
outer_edges.push_back(edge);
|
||||
}
|
||||
}
|
||||
|
||||
gfx::color_rgba cell_color(cell_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case cell_type::soft_tissue:
|
||||
return {255, 255, 255, 255};
|
||||
case cell_type::hard_tissue:
|
||||
return {127, 127, 127, 255};
|
||||
case cell_type::muscle:
|
||||
return {255, 127, 127, 255};
|
||||
case cell_type::dead:
|
||||
return {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
return {255, 0, 255, 255};
|
||||
}
|
||||
|
||||
void draw(gfx::painter & painter, creature const & creature)
|
||||
{
|
||||
for (auto const & cell : creature.cells)
|
||||
{
|
||||
math::point<float, 2> center{0.f, 0.f};
|
||||
for (auto idx : cell.indices)
|
||||
center += 0.25f * (creature.positions[idx] - math::point{0.f, 0.f});
|
||||
|
||||
gfx::color_rgba const color = cell_color(cell.info.type);
|
||||
gfx::color_rgba const bgcolor = gfx::dark(color, 0.25f);
|
||||
|
||||
math::point<float, 2> ps[4];
|
||||
for (int i = 0; i < 4; ++i)
|
||||
ps[i] = creature.positions[cell.indices[i]];
|
||||
|
||||
painter.triangle(ps[0], ps[1], ps[2], bgcolor);
|
||||
painter.triangle(ps[2], ps[0], ps[3], bgcolor);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
ps[i] = math::lerp(ps[i], center, 0.25f);
|
||||
|
||||
painter.triangle(ps[0], ps[1], ps[2], color);
|
||||
painter.triangle(ps[2], ps[0], ps[3], color);
|
||||
}
|
||||
|
||||
for (auto const & edge : creature.outer_edges)
|
||||
{
|
||||
gfx::color_rgba const color{0, 0, 0, 255};
|
||||
float const width = 0.125f;
|
||||
painter.line(creature.positions[edge.points[0]], creature.positions[edge.points[1]], width, color, false);
|
||||
}
|
||||
}
|
||||
|
||||
struct creature_builder
|
||||
{
|
||||
creature_builder() = default;
|
||||
|
||||
creature_builder(creature_blueprint blueprint)
|
||||
: blueprint_(std::move(blueprint))
|
||||
{}
|
||||
|
||||
void add(math::point<int, 2> const position, cell_info const & info)
|
||||
{
|
||||
blueprint_[position] = info;
|
||||
}
|
||||
|
||||
bool contains(math::point<int, 2> const & position) const
|
||||
{
|
||||
return blueprint_.contains(position);
|
||||
}
|
||||
|
||||
creature build(int generation)
|
||||
{
|
||||
static math::vector<int, 2> const vertex_delta[4]
|
||||
{
|
||||
{0, 0},
|
||||
{1, 0},
|
||||
{1, 1},
|
||||
{0, 1},
|
||||
};
|
||||
|
||||
creature result;
|
||||
result.generation = generation;
|
||||
|
||||
util::hash_map<math::point<int, 2>, std::uint32_t> vertex_id;
|
||||
|
||||
for (auto const & cell : blueprint_)
|
||||
{
|
||||
auto & cell_out = result.cells.emplace_back();
|
||||
cell_out.info = cell.second;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
math::point<int, 2> vertex = cell.first + vertex_delta[i];
|
||||
if (auto it = vertex_id.find(vertex); it != vertex_id.end())
|
||||
{
|
||||
cell_out.indices[i] = it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
cell_out.indices[i] = (vertex_id[vertex] = result.positions.size());
|
||||
result.positions.push_back(math::cast<float>(vertex));
|
||||
result.velocities.push_back(math::vector<float, 2>::zero());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.blueprint = std::move(blueprint_);
|
||||
result.compute_outer_edges();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool connected() const
|
||||
{
|
||||
if (blueprint_.empty())
|
||||
return false;
|
||||
|
||||
util::hash_set<math::point<int, 2>> visited;
|
||||
|
||||
std::deque<math::point<int, 2>> queue;
|
||||
queue.push_back(blueprint_.begin()->first);
|
||||
visited.insert(queue.back());
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
auto cur = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
for (auto n : neighbours)
|
||||
{
|
||||
auto nn = cur + n;
|
||||
if (blueprint_.contains(nn) && !visited.contains(nn))
|
||||
{
|
||||
visited.insert(nn);
|
||||
queue.push_back(nn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visited.size() == blueprint_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
creature_blueprint blueprint_;
|
||||
};
|
||||
|
||||
int const display_creature_count = 4;
|
||||
|
||||
cell_info random_cell(random::generator & rng)
|
||||
{
|
||||
cell_info result;
|
||||
result.size = random::uniform(rng, cell_size_range);
|
||||
if (auto t = random::uniform(rng, 0, 2); t == 0)
|
||||
result.type = cell_type::hard_tissue;
|
||||
else if (t == 1)
|
||||
result.type = cell_type::soft_tissue;
|
||||
else
|
||||
{
|
||||
result.type = cell_type::muscle;
|
||||
result.phase = random::uniform<float>(rng);
|
||||
result.period = random::uniform<float>(rng, cell_period_range);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct soft_creatures_2d_app
|
||||
: app::application_base
|
||||
{
|
||||
soft_creatures_2d_app(options const &, context const &)
|
||||
{
|
||||
random::generator rng{random::device{}};
|
||||
|
||||
// map_.ground_points.push_back({5.f, 0.f});
|
||||
// for (int x = 10; x <= 200; x += 1)
|
||||
// map_.ground_points.push_back({x / 2.f, random::uniform(rng, 0.f, std::min(x, 200) / 200.f * 4.f)});
|
||||
// map_.ground_points.push_back({x, std::min(2.f, math::sqr(x - 10) * 0.25f)});
|
||||
// map_.ground_points.push_back({x, random::uniform(rng, 0.f, 4.f)});
|
||||
|
||||
for (int species = 0; species < population_size; ++species)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int x_size = random::uniform(rng, 2, 7);
|
||||
int y_size = random::uniform(rng, 2, 5);
|
||||
|
||||
x_size = 1;
|
||||
y_size = 1;
|
||||
|
||||
creature_builder builder;
|
||||
for (int x = 0; x < x_size; ++x)
|
||||
{
|
||||
for (int y = 0; y < y_size; ++y)
|
||||
{
|
||||
if (random::uniform<float>(rng) > 0.75f)
|
||||
continue;
|
||||
|
||||
builder.add({x, y}, random_cell(rng));
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.connected())
|
||||
{
|
||||
population_.push_back(builder.build(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (species < display_creature_count)
|
||||
display_creatures_.push_back(population_.back());
|
||||
}
|
||||
|
||||
simulator_thread_ = util::thread([this]{ run_simulation(); });
|
||||
}
|
||||
|
||||
void on_event(app::key_event const & event) override
|
||||
{
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
paused_ ^= true;
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
app::application_base::stop();
|
||||
stopping_.store(true);
|
||||
simulator_thread_.join();
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
if (state().key_down.contains(app::keycode::ESCAPE))
|
||||
stop();
|
||||
|
||||
if (state().key_down.contains(app::keycode::RIGHT) || simulation_time_ >= display_simulation_time)
|
||||
get_next_display_creatures();
|
||||
|
||||
float const frame_dt = frame_clock_.restart().count();
|
||||
|
||||
if (!paused_) physics_lag_ += frame_dt;
|
||||
|
||||
while (physics_lag_ >= sim_dt)
|
||||
{
|
||||
physics_lag_ -= sim_dt;
|
||||
simulation_time_ += sim_dt;
|
||||
for (auto & creature : display_creatures_)
|
||||
{
|
||||
creature.update(sim_dt);
|
||||
creature.collide(map_, sim_dt);
|
||||
}
|
||||
}
|
||||
|
||||
float max_view_x = 0.f;
|
||||
for (int c = 0; c < display_creatures_.size(); ++c)
|
||||
for (auto const & p : display_creatures_[c].positions)
|
||||
math::make_max(max_view_x, p[0]);
|
||||
max_view_x = std::max(max_view_x + 10.f, max_view_x_);
|
||||
|
||||
max_view_x_ += (max_view_x - max_view_x_) * (1.f - std::exp(- 4.f * frame_dt));
|
||||
}
|
||||
|
||||
void present() override
|
||||
{
|
||||
gl::ClearColor(0.5f, 0.6f, 0.8f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
for (int c = 0; c < display_creatures_.size(); ++c)
|
||||
{
|
||||
float aspect_ratio = state().size[0] * 1.f / state().size[1] * display_creatures_.size();
|
||||
math::box<float, 2> view_box = {{{0.f, 0.f}, {0.f, 0.f}}};
|
||||
view_box[0] = {max_view_x_ - display_width, max_view_x_};
|
||||
view_box[1] = {0.f, view_box[0].length() / aspect_ratio};
|
||||
view_box[1] -= 2.f;
|
||||
|
||||
math::box<float, 2> ground_box;
|
||||
ground_box[0] = math::interval<float>::full();
|
||||
ground_box[1] = {math::limits<float>::min(), 0.f};
|
||||
ground_box = ground_box & view_box;
|
||||
gfx::color_rgba ground_color{127, 91, 65, 255};
|
||||
painter_.rect(ground_box, ground_color);
|
||||
|
||||
gfx::color_rgba hills_color{91, 127, 65, 255};
|
||||
for (int i = 0; i + 1 < map_.ground_points.size(); ++i)
|
||||
{
|
||||
float x0 = map_.ground_points[i ][0];
|
||||
float x1 = map_.ground_points[i + 1][0];
|
||||
|
||||
float y0 = map_.ground_points[i ][1];
|
||||
float y1 = map_.ground_points[i + 1][1];
|
||||
|
||||
painter_.triangle({x0, 0.f}, {x1, 0.f}, {x1, y1}, hills_color);
|
||||
painter_.triangle({x0, 0.f}, {x1, y1}, {x0, y0}, hills_color);
|
||||
}
|
||||
|
||||
int ruler_min = std::ceil(view_box[0].min / 5.f);
|
||||
int ruler_max = std::floor(view_box[0].max / 5.f);
|
||||
|
||||
for (int r = ruler_min; r <= ruler_max; ++r)
|
||||
{
|
||||
float x = r * 5.f;
|
||||
float y = ((r % 2) == 0) ? -1.f : -0.5f;
|
||||
y = std::max(y, view_box[1].min);
|
||||
|
||||
float scale = 2.f * view_box[0].length() / state().size[0];
|
||||
|
||||
painter_.line({x, 0.f}, {x, y}, 0.125f, {255, 255, 255, 255}, false);
|
||||
painter_.text3d({x + 0.1f, -0.5f, 0.f}, util::to_string(r * 5), {.scale = {scale, scale}, .x = gfx::painter::x_align::left, .c = {255, 255, 255, 127}}, math::scale<float, 3>({1.f, -1.f, 1.f}).linear_matrix());
|
||||
}
|
||||
|
||||
draw(painter_, display_creatures_[c]);
|
||||
|
||||
float height = view_box[1].length();
|
||||
view_box[1].max += c * height;
|
||||
view_box[1].min = view_box[1].max - display_creatures_.size() * height;
|
||||
painter_.render(math::orthographic_camera(view_box).transform());
|
||||
}
|
||||
|
||||
int text_row = 0;
|
||||
auto put_line = [&](std::string const & line)
|
||||
{
|
||||
painter_.text({13.f, 10.f + text_row * 24.f}, line, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
|
||||
painter_.text({12.f, 9.f + text_row * 24.f}, line, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
|
||||
++text_row;
|
||||
};
|
||||
|
||||
int simulated_generation = 0;
|
||||
float best_score = 0.f;
|
||||
int best_generation = 0;
|
||||
float avg_score = 0.f;
|
||||
{
|
||||
std::lock_guard lock{next_display_mutex_};
|
||||
simulated_generation = next_display_generation_ + 1;
|
||||
best_score = generation_best_score_;
|
||||
best_generation = generation_best_generation_;
|
||||
avg_score = generation_avg_score_;
|
||||
}
|
||||
|
||||
put_line(util::to_string("Simulating generation: ", simulated_generation));
|
||||
put_line(util::to_string("Best score: ", best_score, " (gen ", best_generation, ")"));
|
||||
put_line(util::to_string("Average score: ", avg_score));
|
||||
put_line(util::to_string("Display generation: ", display_generation_));
|
||||
put_line(util::to_string("Time: ", simulation_time_));
|
||||
|
||||
for (int c = 0; c < display_creatures_.size(); ++c)
|
||||
{
|
||||
float x = state().size[0] - 12.f;
|
||||
float y = (c * state().size[1] * 1.f) / display_creatures_.size() + 9.f;
|
||||
|
||||
auto text = util::to_string("Gen ", display_creatures_[c].generation);
|
||||
painter_.text({x + 1.f, y + 1.f}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
|
||||
painter_.text({x, y}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
|
||||
|
||||
if (display_creatures_[c].score)
|
||||
{
|
||||
y += 24.f;
|
||||
auto text = util::to_string("Score ", *display_creatures_[c].score);
|
||||
painter_.text({x + 1.f, y + 1.f}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
|
||||
painter_.text({x, y}, text, {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::right, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < display_creature_count; ++i)
|
||||
{
|
||||
float y = (state().size[1] * i * 1.f) / display_creature_count;
|
||||
painter_.line({0.f, y}, {state().size[0], y}, 4.f, {0, 0, 0, 255}, false);
|
||||
}
|
||||
|
||||
painter_.render(math::window_camera(state().size[0], state().size[1]).transform());
|
||||
}
|
||||
|
||||
private:
|
||||
gfx::painter painter_;
|
||||
|
||||
util::clock<> frame_clock_;
|
||||
|
||||
map map_;
|
||||
|
||||
util::thread simulator_thread_;
|
||||
std::vector<creature> population_;
|
||||
|
||||
std::mutex next_display_mutex_;
|
||||
std::vector<creature> next_display_creatures_;
|
||||
bool has_next_creatures_ = false;
|
||||
int next_display_generation_ = 0;
|
||||
float generation_best_score_ = 0.f;
|
||||
float generation_avg_score_ = 0.f;
|
||||
int generation_best_generation_ = 0;
|
||||
|
||||
float simulation_time_ = 0.f;
|
||||
float physics_lag_ = 0.f;
|
||||
bool paused_ = false;
|
||||
std::vector<creature> display_creatures_;
|
||||
int display_generation_ = 0;
|
||||
float const display_width = 70.f;
|
||||
float max_view_x_ = display_width - 5.f;
|
||||
|
||||
std::atomic<bool> stopping_{false};
|
||||
|
||||
void get_next_display_creatures()
|
||||
{
|
||||
bool new_creatures = false;
|
||||
|
||||
std::lock_guard lock{next_display_mutex_};
|
||||
if (has_next_creatures_)
|
||||
{
|
||||
display_creatures_ = std::move(next_display_creatures_);
|
||||
physics_lag_ = 0.f;
|
||||
simulation_time_ = 0.f;
|
||||
has_next_creatures_ = false;
|
||||
display_generation_ = next_display_generation_;
|
||||
max_view_x_ = display_width - 5.f;
|
||||
new_creatures = true;
|
||||
}
|
||||
|
||||
if (new_creatures)
|
||||
{
|
||||
for (auto & creature : display_creatures_)
|
||||
{
|
||||
auto center = creature.center();
|
||||
float radius = -std::numeric_limits<float>::infinity();
|
||||
for (auto const & pos : creature.positions)
|
||||
math::make_max(radius, math::distance(pos, center));
|
||||
|
||||
float angle = 0.f;
|
||||
|
||||
for (auto & pos : creature.positions)
|
||||
pos = math::vector{0.f, radius} + center + math::rotate(pos - center, angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run_simulation()
|
||||
{
|
||||
static thread_local random::generator rng{random::device{}};
|
||||
random::normal_distribution<float> randn;
|
||||
|
||||
int generation = 0;
|
||||
|
||||
while (!stopping_)
|
||||
{
|
||||
int const test_iterations = std::round(simulation_time / sim_dt);
|
||||
|
||||
++generation;
|
||||
|
||||
std::vector<creature> evaluated_creatures(population_.size());
|
||||
|
||||
#ifdef _OPENMP
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int i = 0; i < population_.size(); ++i)
|
||||
{
|
||||
auto & creature = population_[i];
|
||||
if (!creature.score)
|
||||
{
|
||||
auto sim_creature = creature;
|
||||
|
||||
float muscle_power = 0.f;
|
||||
for (auto const & cell : sim_creature.cells)
|
||||
if (cell.info.type == cell_type::muscle)
|
||||
muscle_power += std::pow(cell.info.period, -1.f);
|
||||
|
||||
auto center = sim_creature.center();
|
||||
float radius = -std::numeric_limits<float>::infinity();
|
||||
for (auto const & pos : sim_creature.positions)
|
||||
math::make_max(radius, math::distance(pos, center));
|
||||
|
||||
auto angle = random::uniform<float>(rng, -1.f, 1.f) * math::rad(30.f);
|
||||
angle = 0.f;
|
||||
|
||||
for (auto & pos : sim_creature.positions)
|
||||
pos = math::vector{0.f, radius} + center + math::rotate(pos - center, angle);
|
||||
|
||||
auto start_pos = sim_creature.center();
|
||||
float max_y = start_pos[1];
|
||||
float sum_speed_x = 0.f;
|
||||
float sum_speed_x2 = 0.f;
|
||||
|
||||
for (int iteration = 0; iteration < test_iterations; ++iteration)
|
||||
{
|
||||
sim_creature.update(sim_dt);
|
||||
sim_creature.collide(map_, sim_dt);
|
||||
math::make_max(max_y, sim_creature.center()[1]);
|
||||
|
||||
auto speed = sim_creature.center_velocity();
|
||||
sum_speed_x += speed[0];
|
||||
sum_speed_x2 += speed[0] * speed[0];
|
||||
}
|
||||
auto end_pos = sim_creature.center();
|
||||
|
||||
float avg_speed = sum_speed_x / test_iterations;
|
||||
float var_speed = sum_speed_x2 / test_iterations - avg_speed * avg_speed;
|
||||
|
||||
float total_speed = (end_pos[0] - start_pos[0]) / simulation_time;
|
||||
|
||||
(void)total_speed;
|
||||
(void)avg_speed;
|
||||
(void)var_speed;
|
||||
(void)muscle_power;
|
||||
|
||||
float score = muscle_power > 0.f ? (total_speed) : 0.f;
|
||||
// float score = muscle_power > 0.f ? (total_speed / std::sqrt(muscle_power)) : 0.f;
|
||||
// float score = muscle_power > 0.f ? (total_speed * (sim_creature.cells.size())) : 0.f;
|
||||
// float score = muscle_power > 0.f ? (total_speed / std::sqrt(muscle_power) * std::sqrt(sim_creature.cells.size())) : 0.f;
|
||||
// float score = muscle_power > 0.f ? (total_speed / muscle_power / std::sqrt(sim_creature.cells.size())) : 0.f;
|
||||
|
||||
if (sim_creature.dead())
|
||||
score = 0.f;
|
||||
|
||||
creature.score = score;
|
||||
}
|
||||
evaluated_creatures[i] = std::move(creature);
|
||||
}
|
||||
|
||||
if (stopping_)
|
||||
break;
|
||||
|
||||
int const best = population_.size() / 4;
|
||||
int const children_count = population_.size() - best;
|
||||
|
||||
population_.clear();
|
||||
|
||||
std::partial_sort(evaluated_creatures.begin(), evaluated_creatures.begin() + best, evaluated_creatures.end(), [](auto const & a, auto const & b){ return *a.score > *b.score; });
|
||||
evaluated_creatures.resize(best);
|
||||
population_ = std::move(evaluated_creatures);
|
||||
|
||||
float best_score = *population_.front().score;
|
||||
int best_generation = population_.front().generation;
|
||||
|
||||
float avg_score = 0.f;
|
||||
for (auto const & p : population_)
|
||||
avg_score += *p.score;
|
||||
avg_score /= population_.size();
|
||||
|
||||
float const mutation_boost = 1.f; //std::exp2(std::min(5.f, (generation - best_generation) / 10.f));
|
||||
|
||||
float const change_type_probability = 1.f / 8.f * mutation_boost;
|
||||
float const erase_probability = 1.f / 8.f * mutation_boost;
|
||||
float const grow_probability = 1.f / 8.f * mutation_boost;
|
||||
float const sex_probability = 0.9f * mutation_boost;
|
||||
|
||||
std::vector<creature> next_display;
|
||||
for (int i = 0; i < display_creature_count; ++i)
|
||||
// next_display.push_back(creatures_with_score[i * creatures_with_score.size() / display_creature_count].first);
|
||||
next_display.push_back(population_[i]);
|
||||
|
||||
std::vector<float> scores(population_.size());
|
||||
for (int i = 0; i < population_.size(); ++i)
|
||||
scores[i] = *population_[i].score;
|
||||
|
||||
random::weighted_distribution<float> random_parent(std::move(scores));
|
||||
|
||||
for (int i = 0; i < children_count; ++i)
|
||||
{
|
||||
auto const & parent1 = population_[random_parent(rng)];
|
||||
auto const & parent2 = population_[random_parent(rng)];
|
||||
|
||||
creature_blueprint blueprint = parent1.blueprint;
|
||||
|
||||
if (random::uniform<float>(rng) < sex_probability)
|
||||
{
|
||||
for (auto const & cell : parent2.blueprint)
|
||||
if (random::uniform<int>(rng, 0, 1) == 0)
|
||||
blueprint[cell.first] = cell.second;
|
||||
}
|
||||
|
||||
std::vector<math::point<int, 2>> erase_cells;
|
||||
for (auto const & cell : blueprint)
|
||||
{
|
||||
if (random::uniform<float>(rng) < erase_probability)
|
||||
erase_cells.push_back(cell.first);
|
||||
}
|
||||
|
||||
for (auto cell : erase_cells)
|
||||
blueprint.erase(cell);
|
||||
|
||||
std::vector<std::pair<math::point<int, 2>, cell_info>> grow_cells;
|
||||
for (auto const & cell : blueprint)
|
||||
{
|
||||
for (auto n : neighbours)
|
||||
{
|
||||
auto ncell = cell.first + n;
|
||||
|
||||
if (blueprint.contains(ncell))
|
||||
continue;
|
||||
|
||||
if (random::uniform<float>(rng) < grow_probability)
|
||||
grow_cells.push_back({ncell, random_cell(rng)});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto cell : grow_cells)
|
||||
blueprint.insert(cell);
|
||||
|
||||
creature new_creature;
|
||||
|
||||
{
|
||||
creature_builder builder(std::move(blueprint));
|
||||
if (builder.connected())
|
||||
new_creature = builder.build(generation);
|
||||
else
|
||||
new_creature = parent1;
|
||||
}
|
||||
|
||||
for (auto & cell : new_creature.cells)
|
||||
{
|
||||
if (random::uniform<float>(rng) < change_type_probability)
|
||||
{
|
||||
cell.info = random_cell(rng);
|
||||
new_creature.score = std::nullopt;
|
||||
new_creature.generation = generation;
|
||||
}
|
||||
|
||||
if (cell.info.type == cell_type::muscle)
|
||||
{
|
||||
cell.info.size += randn(rng) * 0.1f * mutation_boost;
|
||||
cell.info.phase += randn(rng) * 0.1f * mutation_boost;
|
||||
cell.info.period += randn(rng) * 0.1f * mutation_boost;
|
||||
|
||||
cell.info.size = math::clamp(cell.info.size, cell_size_range);
|
||||
cell.info.phase = std::fmod(cell.info.phase, 1.f);
|
||||
cell.info.period = math::clamp(cell.info.period, cell_period_range);
|
||||
|
||||
new_creature.score = std::nullopt;
|
||||
new_creature.generation = generation;
|
||||
}
|
||||
}
|
||||
|
||||
population_.push_back(std::move(new_creature));
|
||||
}
|
||||
|
||||
std::lock_guard lock{next_display_mutex_};
|
||||
next_display_creatures_ = std::move(next_display);
|
||||
next_display_generation_ = generation;
|
||||
has_next_creatures_ = true;
|
||||
generation_best_score_ = best_score;
|
||||
generation_avg_score_ = avg_score;
|
||||
generation_best_generation_ = best_generation;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<soft_creatures_2d_app>({.name = "Soft-body creatures", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
1071
examples/soft_plants_2d.cpp
Normal file
1071
examples/soft_plants_2d.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
|
|
@ -8,13 +8,13 @@
|
|||
#include <psemek/gfx/renderer/simple.hpp>
|
||||
#include <psemek/gfx/error.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/homogeneous.hpp>
|
||||
#include <psemek/geom/gauss.hpp>
|
||||
#include <psemek/geom/orientation.hpp>
|
||||
#include <psemek/geom/intersection.hpp>
|
||||
#include <psemek/geom/distance.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/math.hpp>
|
||||
#include <psemek/math/homogeneous.hpp>
|
||||
#include <psemek/math/gauss.hpp>
|
||||
#include <psemek/math/orientation.hpp>
|
||||
#include <psemek/math/intersection.hpp>
|
||||
#include <psemek/math/distance.hpp>
|
||||
|
||||
#include <psemek/cg/body/icosahedron.hpp>
|
||||
#include <psemek/cg/body/box.hpp>
|
||||
|
|
@ -83,7 +83,7 @@ static const float exaggeration = 1.f;
|
|||
|
||||
struct height_provider
|
||||
{
|
||||
float height_at(geom::vector<float, 3> const & v);
|
||||
float height_at(math::vector<float, 3> const & v);
|
||||
|
||||
struct datum_id_hash
|
||||
{
|
||||
|
|
@ -100,13 +100,13 @@ struct height_provider
|
|||
std::unordered_set<std::pair<int, int>, datum_id_hash> no_datums;
|
||||
};
|
||||
|
||||
float height_provider::height_at(geom::vector<float, 3> const & v)
|
||||
float height_provider::height_at(math::vector<float, 3> const & v)
|
||||
{
|
||||
static std::filesystem::path const data_path = "/home/lisyarus/data/srtm/dem";
|
||||
|
||||
float const lat = geom::deg(std::asin(v[2]));
|
||||
float const lat = math::deg(std::asin(v[2]));
|
||||
|
||||
float const lon = geom::deg(std::atan2(v[0], -v[1]));
|
||||
float const lon = math::deg(std::atan2(v[0], -v[1]));
|
||||
|
||||
int ilat = std::floor(lat);
|
||||
int ilon = std::floor(lon);
|
||||
|
|
@ -199,8 +199,8 @@ float height_provider::height_at(geom::vector<float, 3> const & v)
|
|||
return (int)values[(3600 - lat) * 3601 + lon] - 32768;
|
||||
};
|
||||
|
||||
int const tlat = geom::clamp<int>(std::floor((lat - ilat) * 3600), {0, 3600 - 1});
|
||||
int const tlon = geom::clamp<int>(std::floor((lon - ilon) * 3600), {0, 3600 - 1});
|
||||
int const tlat = math::clamp<int>(std::floor((lat - ilat) * 3600), {0, 3600 - 1});
|
||||
int const tlon = math::clamp<int>(std::floor((lon - ilon) * 3600), {0, 3600 - 1});
|
||||
|
||||
float const mlat = (lat - ilat) * 3600.f - tlat;
|
||||
float const mlon = (lon - ilon) * 3600.f - tlon;
|
||||
|
|
@ -210,7 +210,7 @@ float height_provider::height_at(geom::vector<float, 3> const & v)
|
|||
float h10 = at(tlat, tlon + 1);
|
||||
float h11 = at(tlat + 1, tlon + 1);
|
||||
|
||||
return geom::lerp(geom::lerp(h00, h01, mlat), geom::lerp(h10, h11, mlat), mlon);
|
||||
return math::lerp(math::lerp(h00, h01, mlat), math::lerp(h10, h11, mlat), mlon);
|
||||
}
|
||||
|
||||
static constexpr int node_size_log2 = 8;
|
||||
|
|
@ -256,8 +256,8 @@ void node_cache::store(std::size_t uid, std::vector<std::int16_t> const & data)
|
|||
|
||||
struct node
|
||||
{
|
||||
geom::vector<float, 3> v[3];
|
||||
geom::interval<float> height_range;
|
||||
math::vector<float, 3> v[3];
|
||||
math::interval<float> height_range;
|
||||
|
||||
virtual bool draw(int level) = 0;
|
||||
virtual node * child(int id) = 0;
|
||||
|
|
@ -312,7 +312,7 @@ private:
|
|||
};
|
||||
|
||||
node_controller::node_controller()
|
||||
: icosahedron_{geom::point<float, 3>::zero(), 1.f}
|
||||
: icosahedron_{math::point<float, 3>::zero(), 1.f}
|
||||
{
|
||||
std::vector<std::uint16_t> indices;
|
||||
index_counts_[0] = 0;
|
||||
|
|
@ -356,9 +356,9 @@ node * node_controller::root(int f)
|
|||
n->uid = (1 << 5) | f;
|
||||
|
||||
auto face = cg::faces(icosahedron_)[f];
|
||||
n->v[0] = icosahedron_.vertices[face[0]] - geom::point<float, 3>::zero();
|
||||
n->v[1] = icosahedron_.vertices[face[1]] - geom::point<float, 3>::zero();
|
||||
n->v[2] = icosahedron_.vertices[face[2]] - geom::point<float, 3>::zero();
|
||||
n->v[0] = icosahedron_.vertices[face[0]] - math::point<float, 3>::zero();
|
||||
n->v[1] = icosahedron_.vertices[face[1]] - math::point<float, 3>::zero();
|
||||
n->v[2] = icosahedron_.vertices[face[2]] - math::point<float, 3>::zero();
|
||||
|
||||
roots_[f] = std::move(n);
|
||||
}
|
||||
|
|
@ -450,7 +450,7 @@ node * node_controller::node_impl::child(int id)
|
|||
float t1 = (1.f * j) / (1.f * node_child_size);
|
||||
float t2 = 1.f - t0 - t1;
|
||||
|
||||
return geom::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
|
||||
return math::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
|
||||
};
|
||||
|
||||
n->v[0] = at(i0, j0);
|
||||
|
|
@ -487,7 +487,7 @@ void node_controller::node_impl::load_heights()
|
|||
float t1 = (1.f * j) / (1.f * node_size);
|
||||
float t2 = 1.f - t0 - t1;
|
||||
|
||||
return geom::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
|
||||
return math::normalized(v[0] * t0 + v[1] * t1 + v[2] * t2);
|
||||
};
|
||||
|
||||
for (int i = 0; i <= node_size; ++i)
|
||||
|
|
@ -601,19 +601,19 @@ void main()
|
|||
})";
|
||||
|
||||
struct srtm_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
srtm_app();
|
||||
srtm_app(options const &, context const &);
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override;
|
||||
void on_event(app::mouse_move_event const & event) override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
geom::free_camera camera;
|
||||
geom::matrix<float, 4, 4> camera_transform;
|
||||
math::free_camera camera;
|
||||
math::matrix<float, 4, 4> camera_transform;
|
||||
bool camera_forward = false;
|
||||
|
||||
node_controller nodes;
|
||||
|
|
@ -632,24 +632,23 @@ struct srtm_app
|
|||
gfx::painter painter;
|
||||
};
|
||||
|
||||
srtm_app::srtm_app()
|
||||
: app("SRTM", 4)
|
||||
srtm_app::srtm_app(options const &, context const & context)
|
||||
{
|
||||
vsync(true);
|
||||
show_cursor(false);
|
||||
context.vsync(true);
|
||||
context.show_cursor(false);
|
||||
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.fov_y = math::rad(45.f);
|
||||
camera.near_clip = 0.0001f;
|
||||
camera.far_clip = 10.f;
|
||||
camera.pos = {0.f, -10.f, 0.f};
|
||||
camera.rotateYZ(geom::rad(-90.f));
|
||||
camera.rotateYZ(math::rad(-90.f));
|
||||
|
||||
camera_transform = camera.transform();
|
||||
|
||||
selected_mesh.setup<geom::point<float, 3>>();
|
||||
selected_mesh.setup<math::point<float, 3>>();
|
||||
|
||||
{
|
||||
util::array<gfx::color_rgb, 1> colors({16});
|
||||
util::ndarray<gfx::color_rgb, 1> colors({16});
|
||||
|
||||
auto * c = colors.data();
|
||||
|
||||
|
|
@ -675,7 +674,7 @@ srtm_app::srtm_app()
|
|||
}
|
||||
|
||||
{
|
||||
util::array<gfx::color_rgb, 1> colors({5});
|
||||
util::ndarray<gfx::color_rgb, 1> colors({5});
|
||||
|
||||
colors(0) = {0, 63, 127};
|
||||
colors(1) = {0, 0, 127};
|
||||
|
|
@ -688,19 +687,23 @@ srtm_app::srtm_app()
|
|||
}
|
||||
}
|
||||
|
||||
void srtm_app::on_resize(int width, int height)
|
||||
void srtm_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
camera.set_fov(camera.fov_y, (1.f * width) / height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
camera.set_fov(camera.fov_y, (1.f * event.size[0]) / event.size[1]);
|
||||
camera_transform = camera.transform();
|
||||
}
|
||||
|
||||
void srtm_app::on_mouse_move(int x, int y, int dx, int dy)
|
||||
void srtm_app::on_event(app::mouse_move_event const & event)
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
auto const old_mouse = state().mouse;
|
||||
|
||||
camera.rotateZX(0.01f * dx);
|
||||
camera.rotateYZ(0.01f * dy);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
auto const delta = event.position - old_mouse;
|
||||
camera.rotateZX(0.01f * delta[0]);
|
||||
camera.rotateYZ(0.01f * delta[1]);
|
||||
}
|
||||
|
||||
void srtm_app::update()
|
||||
|
|
@ -708,48 +711,48 @@ void srtm_app::update()
|
|||
float dt = frame_clock.restart().count();
|
||||
frame_dt_average.push(dt);
|
||||
|
||||
if (is_key_down(SDLK_q))
|
||||
if (state().key_down.contains(app::keycode::Q))
|
||||
{
|
||||
camera.rotateXY(- 4.f * dt);
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_e))
|
||||
if (state().key_down.contains(app::keycode::E))
|
||||
{
|
||||
camera.rotateXY(4.f * dt);
|
||||
}
|
||||
|
||||
float const camera_speed = std::min(5.f, geom::distance(camera.pos, geom::point<float, 3>::zero()) - 1.f);
|
||||
float const camera_speed = std::min(5.f, math::distance(camera.pos, math::point<float, 3>::zero()) - 1.f);
|
||||
auto const camera_forward = camera.direction();
|
||||
|
||||
auto const camera_up = camera.axis_y();
|
||||
auto const camera_right = camera.axis_x();
|
||||
|
||||
if (is_key_down(SDLK_w))
|
||||
if (state().key_down.contains(app::keycode::W))
|
||||
{
|
||||
camera.pos += camera_speed * dt * camera_forward;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_s))
|
||||
if (state().key_down.contains(app::keycode::S))
|
||||
{
|
||||
camera.pos -= camera_speed * dt * camera_forward;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_d))
|
||||
if (state().key_down.contains(app::keycode::D))
|
||||
{
|
||||
camera.pos += camera_speed * dt * camera_right;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_a))
|
||||
if (state().key_down.contains(app::keycode::A))
|
||||
{
|
||||
camera.pos -= camera_speed * dt * camera_right;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_LSHIFT))
|
||||
if (state().key_down.contains(app::keycode::LSHIFT))
|
||||
{
|
||||
camera.pos += camera_speed * dt * camera_up;
|
||||
}
|
||||
|
||||
if (is_key_down(SDLK_LCTRL))
|
||||
if (state().key_down.contains(app::keycode::LCTRL))
|
||||
{
|
||||
camera.pos -= camera_speed * dt * camera_up;
|
||||
}
|
||||
|
|
@ -780,11 +783,11 @@ std::ostream & operator << (std::ostream & os, std::vector<T> const & v)
|
|||
|
||||
void srtm_app::present()
|
||||
{
|
||||
cg::icosahedron<float> icosahedron{geom::point<float, 3>::zero(), 1.f};
|
||||
cg::icosahedron<float> icosahedron{math::point<float, 3>::zero(), 1.f};
|
||||
|
||||
auto const & icosa_vertices = cg::vertices(icosahedron);
|
||||
auto const & icosa_faces = cg::faces(icosahedron);
|
||||
auto const icosa_side = geom::distance(icosa_vertices[icosa_faces[0][0]], icosa_vertices[icosa_faces[0][1]]);
|
||||
auto const icosa_side = math::distance(icosa_vertices[icosa_faces[0][0]], icosa_vertices[icosa_faces[0][1]]);
|
||||
|
||||
std::vector<std::string> info;
|
||||
|
||||
|
|
@ -803,7 +806,7 @@ void srtm_app::present()
|
|||
gl::PrimitiveRestartIndex(0xffffu);
|
||||
|
||||
{
|
||||
auto d = geom::distance(camera.pos, geom::point<float, 3>::zero());
|
||||
auto d = math::distance(camera.pos, math::point<float, 3>::zero());
|
||||
camera.far_clip = std::sqrt(d * d + 1.f);
|
||||
camera.near_clip = (d > 2.f) ? d - 2.f : 0.0001f;
|
||||
}
|
||||
|
|
@ -812,12 +815,12 @@ void srtm_app::present()
|
|||
auto const camera_pos = camera.position();
|
||||
auto const camera_direction = camera.direction();
|
||||
|
||||
info.push_back(util::to_string("Camera height: ", (geom::distance(camera_pos, geom::point<float, 3>::zero()) - 1.f) * 6400000.f, " m"));
|
||||
info.push_back(util::to_string("Camera height: ", (math::distance(camera_pos, math::point<float, 3>::zero()) - 1.f) * 6400000.f, " m"));
|
||||
|
||||
auto const frustum = cg::frustum(camera_transform);
|
||||
(void)frustum;
|
||||
|
||||
auto light = geom::normalized(geom::vector{camera_pos[0]-camera_pos[1], camera_pos[1]+camera_pos[0], 0.f});
|
||||
auto light = math::normalized(math::vector{camera_pos[0]-camera_pos[1], camera_pos[1]+camera_pos[0], 0.f});
|
||||
|
||||
tile_close_program.bind();
|
||||
tile_close_program["u_transform"] = camera_transform;
|
||||
|
|
@ -843,20 +846,20 @@ void srtm_app::present()
|
|||
|
||||
info.push_back(util::to_string("Camera pos: ", camera_pos));
|
||||
|
||||
std::vector<geom::point<float, 3>> selected_vertices;
|
||||
std::vector<math::point<float, 3>> selected_vertices;
|
||||
|
||||
std::size_t rendered_tiles = 0;
|
||||
std::vector<std::size_t> id;
|
||||
auto visit = util::recursive([&](auto & self, node * n, int level = 0) -> bool
|
||||
{
|
||||
auto const & v = n->v;
|
||||
auto const o = geom::point<float, 3>::zero();
|
||||
auto const o = math::point<float, 3>::zero();
|
||||
|
||||
{
|
||||
bool culled = true;
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
if (geom::dot(v[i], geom::normalized(camera_pos - o)) >= 0.f)
|
||||
if (math::dot(v[i], math::normalized(camera_pos - o)) >= 0.f)
|
||||
{
|
||||
culled = false;
|
||||
break;
|
||||
|
|
@ -870,34 +873,34 @@ void srtm_app::present()
|
|||
bool const flat = n->height_range.max == n->height_range.min;
|
||||
|
||||
auto const m3 = (v[0] + v[1] + v[2]) / 3.f;
|
||||
auto const m = geom::normalized(m3);
|
||||
auto const m = math::normalized(m3);
|
||||
auto const m0 = m * (n->height_range.empty() ? 0.f : n->height_range.min) / 6400000.f;
|
||||
auto const m1 = m * (n->height_range.empty() ? 1.f : flat ? n->height_range.min + 1.f : n->height_range.max) / 6400000.f + (m - m3);
|
||||
|
||||
geom::triangle<geom::point<float, 3>> t{o + v[0] + m0, o + v[1] + m0, o + v[2] + m0};
|
||||
math::triangle<math::point<float, 3>> t{o + v[0] + m0, o + v[1] + m0, o + v[2] + m0};
|
||||
cg::triangular_prism<float> body{t, m1 - m0};
|
||||
|
||||
// if (cg::separation(body, frustum).second > 0.f)
|
||||
// return true;
|
||||
|
||||
bool const selected = geom::intersect(geom::ray{camera_pos, camera_direction}, t);
|
||||
bool const selected = math::intersect(math::ray{camera_pos, camera_direction}, t);
|
||||
|
||||
float on_screen_unit;
|
||||
|
||||
{
|
||||
auto edge = [](auto const & v0, auto const & v1, auto const & u) -> std::optional<float>
|
||||
{
|
||||
auto const n = geom::normalized(geom::cross(v0, v1));
|
||||
auto v = geom::normalized(u - n * dot(u, n));
|
||||
if (geom::dot(geom::cross(v0, v), geom::cross(v, v1)) >= 0.f)
|
||||
return geom::length(v - u);
|
||||
auto const n = math::normalized(math::cross(v0, v1));
|
||||
auto v = math::normalized(u - n * dot(u, n));
|
||||
if (math::dot(math::cross(v0, v), math::cross(v, v1)) >= 0.f)
|
||||
return math::length(v - u);
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
float distance = std::numeric_limits<float>::infinity();
|
||||
auto c = camera_pos - o;
|
||||
if (geom::det(v[0], v[1], c) >= 0.f && geom::det(v[1], v[2], c) >= 0.f && geom::det(v[2], v[0], c) >= 0.f)
|
||||
distance = std::min(distance, geom::length(c) - 1.f);
|
||||
if (math::det(v[0], v[1], c) >= 0.f && math::det(v[1], v[2], c) >= 0.f && math::det(v[2], v[0], c) >= 0.f)
|
||||
distance = std::min(distance, math::length(c) - 1.f);
|
||||
else
|
||||
{
|
||||
if (auto d = edge(v[0], v[1], c); d)
|
||||
|
|
@ -907,11 +910,11 @@ void srtm_app::present()
|
|||
if (auto d = edge(v[2], v[0], c); d)
|
||||
distance = std::min(distance, *d);
|
||||
}
|
||||
distance = std::min(distance, geom::length(c - v[0]));
|
||||
distance = std::min(distance, geom::length(c - v[1]));
|
||||
distance = std::min(distance, geom::length(c - v[2]));
|
||||
distance = std::min(distance, math::length(c - v[0]));
|
||||
distance = std::min(distance, math::length(c - v[1]));
|
||||
distance = std::min(distance, math::length(c - v[2]));
|
||||
|
||||
on_screen_unit = width() / distance / std::tan(camera.fov_x / 2.f);
|
||||
on_screen_unit = state().size[0] / distance / std::tan(camera.fov_x / 2.f);
|
||||
}
|
||||
|
||||
assert(on_screen_unit > 0.f);
|
||||
|
|
@ -945,7 +948,7 @@ void srtm_app::present()
|
|||
|
||||
if (!should_draw_children || !all_children_drawn)
|
||||
{
|
||||
tile_n = geom::clamp(tile_n, {0, node_size_log2});
|
||||
tile_n = math::clamp(tile_n, {0, node_size_log2});
|
||||
|
||||
++rendered_tiles;
|
||||
|
||||
|
|
@ -983,11 +986,14 @@ void srtm_app::present()
|
|||
// simple_renderer.push(gfx::simple_renderer::render_state{&selected_mesh, gfx::white.as_color_rgba()});
|
||||
// simple_renderer.render(gfx::simple_renderer::render_options{camera_transform});
|
||||
|
||||
int const width = state().size[0];
|
||||
int const height = state().size[1];
|
||||
|
||||
info.push_back(util::to_string("Tiles: ", rendered_tiles));
|
||||
{
|
||||
float s = 10.f;
|
||||
painter.line({width() / 2.f - s, height() / 2.f}, {width() / 2.f + s, height() / 2.f}, 3.f, gfx::cyan, false);
|
||||
painter.line({width() / 2.f, height() / 2.f - s}, {width() / 2.f, height() / 2.f + s}, 3.f, gfx::cyan, false);
|
||||
painter.line({width / 2.f - s, height / 2.f}, {width / 2.f + s, height / 2.f}, 3.f, gfx::cyan, false);
|
||||
painter.line({width / 2.f, height / 2.f - s}, {width / 2.f, height / 2.f + s}, 3.f, gfx::cyan, false);
|
||||
}
|
||||
info.push_back(util::to_string("Nodes: ", nodes.node_count()));
|
||||
info.push_back(util::to_string("Tasks: ", nodes.loader_queue_size()));
|
||||
|
|
@ -1001,7 +1007,7 @@ void srtm_app::present()
|
|||
opts.y = gfx::painter::y_align::top;
|
||||
opts.f = gfx::painter::font::font_9x12;
|
||||
opts.c = gfx::gray;
|
||||
opts.scale = 2.f;
|
||||
opts.scale = {2.f, 2.f};
|
||||
|
||||
for (int l = 0; l < info.size(); ++l)
|
||||
{
|
||||
|
|
@ -1020,10 +1026,15 @@ void srtm_app::present()
|
|||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
gl::Disable(gl::DEPTH_TEST);
|
||||
painter.render(geom::window_camera{width(), height()}.transform());
|
||||
painter.render(math::window_camera{width, height}.transform());
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<srtm_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<srtm_app>({.name = "SRTM example", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,532 +0,0 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/gfx/mesh.hpp>
|
||||
#include <psemek/gfx/program.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/rotation.hpp>
|
||||
#include <psemek/geom/gram_schmidt.hpp>
|
||||
#include <psemek/gfx/color.hpp>
|
||||
#include <psemek/geom/math.hpp>
|
||||
#include <psemek/geom/interval.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/util/recursive.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
// All spacial parameters in meters
|
||||
// All angles in degrees
|
||||
struct tree_species_description
|
||||
{
|
||||
struct curve_description
|
||||
{
|
||||
geom::interval<float> base_radius;
|
||||
geom::interval<float> segment_length;
|
||||
std::function<geom::interval<int>(float)> segment_count;
|
||||
geom::interval<float> lean_angle;
|
||||
geom::interval<float> rotation_angle;
|
||||
float up_tendency;
|
||||
|
||||
float splitting_probability;
|
||||
geom::interval<float> splitting_angle;
|
||||
|
||||
geom::interval<float> initial_branching_distance;
|
||||
geom::interval<float> branching_distance;
|
||||
geom::interval<int> branching_children_count;
|
||||
geom::interval<float> branching_rotation;
|
||||
geom::interval<float> branching_angle;
|
||||
geom::interval<float> branching_size_multiplier;
|
||||
};
|
||||
|
||||
curve_description trunk;
|
||||
curve_description branch;
|
||||
};
|
||||
|
||||
auto fir_species()
|
||||
{
|
||||
tree_species_description desc;
|
||||
|
||||
desc.trunk.base_radius = {0.1f, 0.15f};
|
||||
desc.trunk.segment_length = {0.5f, 0.8f};
|
||||
desc.trunk.segment_count = [](float){
|
||||
return geom::interval{7, 10};
|
||||
};
|
||||
desc.trunk.lean_angle = {-5.f, 5.f};
|
||||
desc.trunk.rotation_angle = {-5.f, 5.f};
|
||||
desc.trunk.up_tendency = 1.f;
|
||||
desc.trunk.initial_branching_distance = {0.5f, 0.5f};
|
||||
desc.trunk.branching_distance = {0.4f, 0.5f};
|
||||
desc.trunk.branching_children_count = {4, 4};
|
||||
desc.trunk.branching_rotation = {30, 60.f};
|
||||
desc.trunk.branching_angle = {55.f, 65.f};
|
||||
desc.trunk.branching_size_multiplier = {1.f, 1.f};
|
||||
desc.trunk.splitting_probability = 0.f;
|
||||
desc.branch.base_radius = {0.01f, 0.05f};
|
||||
desc.branch.segment_length = {0.05f, 0.20f};
|
||||
desc.branch.segment_count = [](float h){
|
||||
int min = std::max<int>(1, 18 * (1.f - h));
|
||||
int max = std::min<int>(20, 20 * (1.f - h));
|
||||
|
||||
if (min > max)
|
||||
std::swap(min, max);
|
||||
|
||||
return geom::interval<int>{min, max};
|
||||
};
|
||||
desc.branch.lean_angle = {-5.f, 5.f};
|
||||
desc.branch.rotation_angle = {-1.f, 1.f};
|
||||
desc.branch.up_tendency = 0.0f;
|
||||
desc.branch.initial_branching_distance = {0.4f, 0.4f};
|
||||
desc.branch.branching_distance = {0.1f, 0.2f};
|
||||
desc.branch.branching_children_count = {1, 1};
|
||||
desc.branch.branching_rotation = {175.f, 185.f};
|
||||
desc.branch.branching_angle = {45.f, 45.f};
|
||||
desc.branch.branching_size_multiplier = {0.8f, 0.8f};
|
||||
desc.branch.splitting_probability = 0.f;
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
auto oak_species()
|
||||
{
|
||||
tree_species_description desc;
|
||||
|
||||
desc.trunk.base_radius = {0.2f, 0.3f};
|
||||
desc.trunk.segment_length = {0.5f, 0.8f};
|
||||
desc.trunk.segment_count = [](float){
|
||||
return geom::interval{4, 7};
|
||||
};
|
||||
desc.trunk.lean_angle = {-15.f, 15.f};
|
||||
desc.trunk.rotation_angle = {-180.f, 180.f};
|
||||
desc.trunk.up_tendency = 0.f;
|
||||
desc.trunk.initial_branching_distance = {20.f, 30.f};
|
||||
desc.trunk.branching_distance = {0.1f, 0.15f};
|
||||
desc.trunk.branching_children_count = {1, 2};
|
||||
desc.trunk.branching_rotation = {60, 180.f};
|
||||
desc.trunk.branching_angle = {30.f, 75.f};
|
||||
desc.trunk.branching_size_multiplier = {1.f, 1.f};
|
||||
desc.trunk.splitting_probability = 0.1f;
|
||||
desc.trunk.splitting_angle = {15.f, 60.f};
|
||||
|
||||
desc.branch.base_radius = {0.04f, 0.05f};
|
||||
desc.branch.segment_length = {0.1f, 0.15f};
|
||||
desc.branch.segment_count = [](float h){
|
||||
h = std::max(h / 4.f, 0.f);
|
||||
// h = std::min(h, 1.f);
|
||||
|
||||
// h = 2.f * h - 1.f;
|
||||
|
||||
// float t = std::sqrt(1.f - h * h);
|
||||
|
||||
return geom::interval<int>{6, 10};
|
||||
};
|
||||
desc.branch.lean_angle = {-15.f, 15.f};
|
||||
desc.branch.rotation_angle = {-30.f, 30.f};
|
||||
desc.branch.up_tendency = 0.f;
|
||||
desc.branch.initial_branching_distance = {0.4f, 0.5f};
|
||||
desc.branch.branching_distance = {0.4f, 0.5f};
|
||||
desc.branch.branching_children_count = {1, 1};
|
||||
desc.branch.branching_rotation = {175.f, 185.f};
|
||||
desc.branch.branching_angle = {30.f, 60.f};
|
||||
desc.branch.branching_size_multiplier = {0.8f, 0.8f};
|
||||
desc.branch.splitting_probability = 0.1f;
|
||||
desc.branch.splitting_angle = {15.f, 60.f};
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
struct tree_description
|
||||
{
|
||||
struct node
|
||||
{
|
||||
geom::point<float, 3> position;
|
||||
float radius;
|
||||
};
|
||||
|
||||
struct branch
|
||||
{
|
||||
std::size_t parent;
|
||||
std::vector<node> nodes;
|
||||
};
|
||||
|
||||
std::vector<branch> branches;
|
||||
};
|
||||
|
||||
template <typename RNG>
|
||||
tree_description generate(tree_species_description const & species, RNG && rng)
|
||||
{
|
||||
// warm up!
|
||||
for (int i = 0; i < 16; ++i)
|
||||
rng();
|
||||
|
||||
tree_description result;
|
||||
|
||||
struct frame
|
||||
{
|
||||
geom::point<float, 3> pos;
|
||||
geom::vector<float, 3> z;
|
||||
geom::vector<float, 3> x;
|
||||
};
|
||||
|
||||
auto generate_curve = util::recursive([&](auto & self, frame f, tree_species_description::curve_description const & curve_desc, tree_species_description::curve_description const & children_desc,
|
||||
int level, float min_radius, int max_segments, float h, float branching_distance, float branching_rotation) -> void
|
||||
{
|
||||
std::size_t id = result.branches.size();
|
||||
result.branches.emplace_back();
|
||||
|
||||
float base_radius = random::uniform_distribution<float>{curve_desc.base_radius}(rng);
|
||||
base_radius = std::min(base_radius, min_radius);
|
||||
|
||||
int segment_count = random::uniform_distribution<int>{curve_desc.segment_count(h)}(rng);
|
||||
segment_count = std::min(max_segments, segment_count);
|
||||
|
||||
float expected_height = segment_count * curve_desc.segment_length.center();
|
||||
|
||||
result.branches[id].parent = 0;
|
||||
result.branches[id].nodes.push_back({f.pos, base_radius});
|
||||
|
||||
for (int i = 0; i < segment_count; ++i)
|
||||
{
|
||||
float t = (i + 1.f) / segment_count;
|
||||
|
||||
float length = random::uniform_real_distribution<float>{curve_desc.segment_length}(rng);
|
||||
|
||||
float lean = geom::rad(random::uniform_real_distribution<float>{curve_desc.lean_angle}(rng));
|
||||
|
||||
f.z = geom::axis_rotation<float>{f.x, lean}(f.z);
|
||||
|
||||
if (curve_desc.up_tendency > 0.f)
|
||||
f.z = geom::slerp(f.z, geom::vector{0.f, 0.f, 1.f}, t * curve_desc.up_tendency);
|
||||
else if (curve_desc.up_tendency < 0.f)
|
||||
f.z = geom::slerp(f.z, geom::vector{0.f, 0.f, -1.f}, - t * curve_desc.up_tendency);
|
||||
|
||||
float rotation = geom::rad(random::uniform_real_distribution<float>{curve_desc.rotation_angle}(rng));
|
||||
|
||||
f.x = geom::axis_rotation<float>{f.z, rotation}(f.x);
|
||||
|
||||
auto new_pos = f.pos + f.z * length;
|
||||
|
||||
float new_radius = (1.f - t) * base_radius;
|
||||
|
||||
if (level < 3)
|
||||
{
|
||||
float available_branching_length = length;
|
||||
while (branching_distance < available_branching_length)
|
||||
{
|
||||
available_branching_length -= branching_distance;
|
||||
|
||||
int children_count = random::uniform_distribution<int>{curve_desc.branching_children_count}(rng);
|
||||
|
||||
for (int c = 0; c < children_count; ++c)
|
||||
{
|
||||
float angle = random::uniform_distribution<float>{curve_desc.branching_angle}(rng);
|
||||
|
||||
float rotation = 0;
|
||||
if (children_count > 1)
|
||||
rotation = c * 2.f * geom::pi / children_count;
|
||||
|
||||
float branch_t = 1.f - available_branching_length / length;
|
||||
|
||||
geom::vector y{0.f, 0.f, 1.f};
|
||||
|
||||
frame child_f;
|
||||
child_f.pos = geom::lerp(f.pos, new_pos, branch_t);
|
||||
child_f.z = geom::slerp(f.z, f.x, angle / 90.f);
|
||||
child_f.z = geom::axis_rotation<float>{f.z, branching_rotation + rotation}(child_f.z);
|
||||
child_f.x = geom::normalized(geom::cross(y, child_f.z));
|
||||
|
||||
float multiplier = random::uniform_distribution<float>{curve_desc.branching_size_multiplier}(rng);
|
||||
|
||||
float branching_distance = random::uniform_distribution<float>{children_desc.initial_branching_distance}(rng);
|
||||
|
||||
self(child_f, children_desc, children_desc, level + 1,
|
||||
geom::lerp(result.branches[id].nodes.back().radius, new_radius, branch_t) * multiplier,
|
||||
(level == 0) ? 1024 : (segment_count - i) * multiplier,
|
||||
(level == 0) ? child_f.pos[2] / expected_height : h,
|
||||
branching_distance, 0.f);
|
||||
}
|
||||
|
||||
branching_rotation += geom::rad(random::uniform_distribution<float>{curve_desc.branching_rotation}(rng));
|
||||
branching_distance = random::uniform_distribution<float>{curve_desc.branching_distance}(rng);
|
||||
}
|
||||
|
||||
branching_distance -= available_branching_length;
|
||||
}
|
||||
|
||||
f.pos = new_pos;
|
||||
|
||||
result.branches[id].nodes.push_back({f.pos, new_radius});
|
||||
|
||||
if (i + 1 < segment_count && random::uniform_distribution<float>{}(rng) < curve_desc.splitting_probability)
|
||||
{
|
||||
frame f1 = f;
|
||||
frame f2 = f;
|
||||
|
||||
float a1 = geom::rad(random::uniform_distribution<float>{curve_desc.splitting_angle}(rng));
|
||||
float a2 = geom::rad(random::uniform_distribution<float>{curve_desc.splitting_angle}(rng));
|
||||
|
||||
f1.z = geom::axis_rotation<float>{f.x, a1}(f1.z);
|
||||
f2.z = geom::axis_rotation<float>{f.x, -a2}(f2.z);
|
||||
|
||||
float h = f.pos[2] / expected_height;
|
||||
|
||||
self(f1, curve_desc, children_desc, level, new_radius, segment_count - i - 1, h, branching_distance, branching_rotation);
|
||||
self(f2, curve_desc, children_desc, level, new_radius, segment_count - i - 1, h, branching_distance, branching_rotation);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frame starting_frame;
|
||||
starting_frame.pos = {0.f, 0.f, 0.f};
|
||||
starting_frame.z = {0.f, 0.f, 1.f};
|
||||
starting_frame.x = {1.f, 0.f, 0.f};
|
||||
|
||||
float initial_orientation = random::uniform_distribution<float>{0.f, 2.f * geom::pi}(rng);
|
||||
starting_frame.x = geom::axis_rotation<float>{starting_frame.z, initial_orientation}(starting_frame.x);
|
||||
|
||||
generate_curve(starting_frame, species.trunk, species.branch, 0, std::numeric_limits<float>::infinity(), 1024, 0.f,
|
||||
random::uniform_distribution<float>{species.trunk.initial_branching_distance}(rng), 0.f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static char const vertex_source[] =
|
||||
R"(#version 330
|
||||
|
||||
uniform mat4 u_transform;
|
||||
|
||||
layout (location = 0) in vec4 in_position;
|
||||
layout (location = 1) in vec4 in_color;
|
||||
|
||||
out vec4 color;
|
||||
out vec3 pos;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_transform * in_position;
|
||||
color = in_color;
|
||||
pos = in_position.xyz;
|
||||
}
|
||||
)";
|
||||
|
||||
static char const fragment_source[] =
|
||||
R"(#version 330
|
||||
|
||||
in vec4 color;
|
||||
in vec3 pos;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 n = normalize(cross(dFdx(pos), dFdy(pos)));
|
||||
|
||||
vec3 light = normalize(vec3(1.0, 1.0, 1.0));
|
||||
|
||||
float l = 0.5 + 0.5 * dot(n, light);
|
||||
|
||||
out_color = vec4(l * color.rgb, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
struct tree_app
|
||||
: app::app
|
||||
{
|
||||
tree_species_description species = oak_species();
|
||||
|
||||
tree_description tree_desc;
|
||||
|
||||
gfx::mesh tree_mesh;
|
||||
|
||||
gfx::program tree_program{vertex_source, fragment_source};
|
||||
|
||||
geom::spherical_camera camera;
|
||||
|
||||
std::uint64_t seed = 0;
|
||||
|
||||
tree_app()
|
||||
: app("Tree")
|
||||
{
|
||||
tree_desc = generate(species, random::generator{seed, 0ull});
|
||||
update_mesh();
|
||||
|
||||
setup_camera();
|
||||
}
|
||||
|
||||
void update_mesh();
|
||||
void setup_camera();
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override;
|
||||
|
||||
void on_mouse_wheel(int delta) override;
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
|
||||
void on_key_down(SDL_Keycode key) override;
|
||||
|
||||
void present() override;
|
||||
};
|
||||
|
||||
struct vertex
|
||||
{
|
||||
geom::point<float, 3> position;
|
||||
gfx::color_rgba color;
|
||||
};
|
||||
|
||||
void tree_app::update_mesh()
|
||||
{
|
||||
tree_mesh.setup<geom::point<float, 3>, gfx::normalized<gfx::color_rgba>>();
|
||||
|
||||
std::vector<vertex> vertices;
|
||||
std::vector<std::uint32_t> indices;
|
||||
|
||||
{
|
||||
int const basement_size = 5;
|
||||
float basement_offset = 0.05f;
|
||||
gfx::color_rgba basement_color { 127, 127, 127, 255 };
|
||||
|
||||
for (int x = -basement_size; x < basement_size; ++x)
|
||||
{
|
||||
for (int y = -basement_size; y < basement_size; ++y)
|
||||
{
|
||||
auto base = vertices.size();
|
||||
|
||||
vertices.push_back({{x + basement_offset, y + basement_offset, 0.f}, basement_color});
|
||||
vertices.push_back({{x + 1 - basement_offset, y + basement_offset, 0.f}, basement_color});
|
||||
vertices.push_back({{x + basement_offset, y + 1 - basement_offset, 0.f}, basement_color});
|
||||
vertices.push_back({{x + 1 - basement_offset, y + 1 - basement_offset, 0.f}, basement_color});
|
||||
|
||||
indices.push_back(base + 0);
|
||||
indices.push_back(base + 1);
|
||||
indices.push_back(base + 2);
|
||||
indices.push_back(base + 2);
|
||||
indices.push_back(base + 1);
|
||||
indices.push_back(base + 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gfx::color_rgba trunk_color { 127, 63, 0, 255 };
|
||||
|
||||
auto build_branch = [&](std::vector<tree_description::node> const & nodes)
|
||||
{
|
||||
int const N = 6;
|
||||
|
||||
auto const base = vertices.size();
|
||||
|
||||
auto z = geom::vector{0.f, 0.f, 1.f};
|
||||
auto x = geom::ort(z);
|
||||
|
||||
for (std::size_t i = 0; i < nodes.size(); ++i)
|
||||
{
|
||||
if (i + 1 < nodes.size())
|
||||
z = geom::normalized(nodes[i + 1].position - nodes[i].position);
|
||||
else if (nodes.size() >= 2)
|
||||
z = geom::normalized(nodes[i].position - nodes[i - 1].position);
|
||||
|
||||
auto y = geom::cross(z, x);
|
||||
|
||||
geom::gram_schmidt(z, x, y);
|
||||
|
||||
for (int k = 0; k < N; ++k)
|
||||
{
|
||||
float a = (k * geom::pi * 2.f) / N;
|
||||
|
||||
vertices.push_back({nodes[i].position + (x * std::cos(a) + y * std::sin(a)) * nodes[i].radius, trunk_color});
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i + 1 < nodes.size(); ++i)
|
||||
{
|
||||
for (int k = 0; k < N; ++k)
|
||||
{
|
||||
int kk = (k + 1) % N;
|
||||
|
||||
indices.push_back(base + i * N + k);
|
||||
indices.push_back(base + i * N + kk);
|
||||
indices.push_back(base + i * N + k + N);
|
||||
|
||||
indices.push_back(base + i * N + k + N);
|
||||
indices.push_back(base + i * N + kk);
|
||||
indices.push_back(base + i * N + kk + N);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const & b : tree_desc.branches)
|
||||
build_branch(b.nodes);
|
||||
|
||||
tree_mesh.load(vertices, indices, gl::TRIANGLES, gl::STATIC_DRAW);
|
||||
}
|
||||
|
||||
void tree_app::setup_camera()
|
||||
{
|
||||
camera.fov_y = geom::rad(45.f);
|
||||
camera.near_clip = 0.1f;
|
||||
camera.far_clip = 100.f;
|
||||
camera.target = {0.f, 0.f, 0.f};
|
||||
camera.elevation_angle = geom::rad(45.f);
|
||||
camera.azimuthal_angle = 0.f;
|
||||
camera.distance = 10.f;
|
||||
}
|
||||
|
||||
void tree_app::on_mouse_move(int x, int y, int dx, int dy)
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
|
||||
if (is_middle_button_down())
|
||||
{
|
||||
camera.azimuthal_angle -= dx * 0.01f;
|
||||
camera.elevation_angle += dy * 0.01f;
|
||||
}
|
||||
|
||||
if (is_right_button_down())
|
||||
{
|
||||
camera.target[2] += dy * 0.001f * camera.distance;
|
||||
}
|
||||
}
|
||||
|
||||
void tree_app::on_mouse_wheel(int delta)
|
||||
{
|
||||
camera.distance *= std::pow(0.8f, delta);
|
||||
}
|
||||
|
||||
void tree_app::on_resize(int width, int height)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
camera.set_fov(camera.fov_y, (1.f * width) / height);
|
||||
}
|
||||
|
||||
void tree_app::on_key_down(SDL_Keycode key)
|
||||
{
|
||||
if (key == SDLK_SPACE)
|
||||
{
|
||||
++seed;
|
||||
|
||||
tree_desc = generate(species, random::generator{std::uint64_t{seed}, 0ull});
|
||||
update_mesh();
|
||||
}
|
||||
}
|
||||
|
||||
void tree_app::present()
|
||||
{
|
||||
gl::ClearColor(0.7f, 0.7f, 1.f, 1.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||||
|
||||
gl::Enable(gl::DEPTH_TEST);
|
||||
gl::DepthFunc(gl::LEQUAL);
|
||||
|
||||
gl::Enable(gl::CULL_FACE);
|
||||
|
||||
tree_program.bind();
|
||||
tree_program["u_transform"] = camera.transform();
|
||||
tree_mesh.draw();
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
return app::main<tree_app>();
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/geom/scale.hpp>
|
||||
#include <psemek/geom/camera.hpp>
|
||||
#include <psemek/geom/constants.hpp>
|
||||
#include <psemek/math/scale.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/constants.hpp>
|
||||
#include <psemek/cg/bbox.hpp>
|
||||
#include <psemek/cg/triangulation/ear_clipping.hpp>
|
||||
#include <psemek/cg/triangulation/monotone.hpp>
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
#include <psemek/prof/profiler.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/geom/homogeneous.hpp>
|
||||
#include <psemek/geom/swizzle.hpp>
|
||||
#include <psemek/math/homogeneous.hpp>
|
||||
#include <psemek/math/swizzle.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
|
||||
|
|
@ -23,16 +23,15 @@
|
|||
using namespace psemek;
|
||||
|
||||
struct triangulation_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
triangulation_app()
|
||||
: app("Triangulation example", 16)
|
||||
triangulation_app(options const &, context const &)
|
||||
{
|
||||
std::ifstream in(PSEMEK_EXAMPLES_DIR "/turkey");
|
||||
|
||||
while (true)
|
||||
{
|
||||
geom::point<float, 2> p;
|
||||
math::point<float, 2> p;
|
||||
in >> p[0] >> p[1];
|
||||
if (!in)
|
||||
break;
|
||||
|
|
@ -64,8 +63,8 @@ struct triangulation_app
|
|||
{
|
||||
prof::profiler prof("triangulate");
|
||||
|
||||
// auto dcel = cg::ear_clipping<std::uint16_t>(geom::fast, points_.begin(), points_.end());
|
||||
auto dcel = cg::monotone_triangulation<std::uint16_t>(geom::fast, points_.begin(), points_.end());
|
||||
// auto dcel = cg::ear_clipping<std::uint16_t>(math::fast, points_.begin(), points_.end());
|
||||
auto dcel = cg::monotone_triangulation<std::uint16_t>(math::fast, points_.begin(), points_.end());
|
||||
edges_ = cg::edge_mesh(dcel);
|
||||
triangles_ = cg::triangle_mesh(dcel);
|
||||
}
|
||||
|
|
@ -80,36 +79,32 @@ struct triangulation_app
|
|||
std::iota(closest_points_.begin(), closest_points_.end(), std::uint16_t{0});
|
||||
}
|
||||
|
||||
void on_left_button_down() override
|
||||
void on_event(app::mouse_button_event const & event) override
|
||||
{
|
||||
app::on_left_button_down();
|
||||
app::application_base::on_event(event);
|
||||
|
||||
if (mouse())
|
||||
drag_start_ = mouse();
|
||||
if (event.down && event.button == app::mouse_button::left)
|
||||
drag_start_ = state().mouse;
|
||||
|
||||
if (!event.down && event.button == app::mouse_button::left)
|
||||
drag_start_ = std::nullopt;
|
||||
}
|
||||
|
||||
void on_left_button_up() override
|
||||
void on_event(app::mouse_wheel_event const & event) override
|
||||
{
|
||||
app::on_left_button_up();
|
||||
|
||||
drag_start_ = std::nullopt;
|
||||
}
|
||||
|
||||
void on_mouse_wheel(int delta) override
|
||||
{
|
||||
camera_size_tgt_ *= std::pow(0.8f, delta);
|
||||
camera_size_tgt_ *= std::pow(0.8f, event.delta);
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
float const dt = clock_.restart().count();
|
||||
|
||||
if (drag_start_ && mouse())
|
||||
if (drag_start_)
|
||||
{
|
||||
auto delta = *mouse() - *drag_start_;
|
||||
auto delta = state().mouse - *drag_start_;
|
||||
delta[1] *= -1;
|
||||
camera_center_ -= geom::cast<float>(delta) * camera_size_ / (1.f * height());
|
||||
drag_start_ = mouse();
|
||||
camera_center_ -= math::cast<float>(delta) * camera_size_ / (1.f * state().size[1]);
|
||||
drag_start_ = state().mouse;
|
||||
}
|
||||
|
||||
camera_size_ += (camera_size_tgt_ - camera_size_) * (1.f - std::exp(- 20.f * dt));
|
||||
|
|
@ -120,10 +115,10 @@ struct triangulation_app
|
|||
gl::ClearColor(1.f, 1.f, 1.f, 1.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
float aspect_ratio = (width() * 1.f) / height();
|
||||
geom::box<float, 2> view_bbox = geom::expand(geom::box<float, 2>::singleton(camera_center_), geom::vector{camera_size_ * 0.5f * aspect_ratio, camera_size_ * 0.5f});
|
||||
float aspect_ratio = (state().size[0] * 1.f) / state().size[1];
|
||||
math::box<float, 2> view_bbox = math::expand(math::box<float, 2>::singleton(camera_center_), math::vector{camera_size_ * 0.5f * aspect_ratio, camera_size_ * 0.5f});
|
||||
|
||||
float line_width = 4.f * camera_size_ / height();
|
||||
float line_width = 4.f * camera_size_ / state().size[1];
|
||||
|
||||
auto edge = [this, line_width](auto i0, auto i1, gfx::color_rgba const & color)
|
||||
{
|
||||
|
|
@ -149,18 +144,17 @@ struct triangulation_app
|
|||
for (std::size_t i = 0; i < points_.size(); ++i)
|
||||
edge(i, (i + 1) % points_.size(), gfx::black);
|
||||
|
||||
auto camera_transform = geom::orthographic_camera{view_bbox}.transform();
|
||||
auto camera_transform = math::orthographic_camera{view_bbox}.transform();
|
||||
|
||||
painter_.render(camera_transform);
|
||||
|
||||
if (auto m = mouse(); m)
|
||||
{
|
||||
geom::point<float, 2> m_world;
|
||||
m_world[0] = geom::lerp(view_bbox[0], (*m)[0] * 1.f / width());
|
||||
m_world[1] = geom::lerp(view_bbox[1], 1.f - (*m)[1] * 1.f / height());
|
||||
math::point<float, 2> m_world;
|
||||
m_world[0] = math::lerp(view_bbox[0], state().mouse[0] * 1.f / state().size[0]);
|
||||
m_world[1] = math::lerp(view_bbox[1], 1.f - state().mouse[1] * 1.f / state().size[1]);
|
||||
|
||||
auto compare = [&](auto i, auto j){
|
||||
return geom::distance(points_[i], m_world) < geom::distance(points_[j], m_world);
|
||||
return math::distance(points_[i], m_world) < math::distance(points_[j], m_world);
|
||||
};
|
||||
|
||||
std::size_t const n_closest = 8;
|
||||
|
|
@ -173,34 +167,34 @@ struct triangulation_app
|
|||
{
|
||||
auto i = closest_points_[j];
|
||||
|
||||
if (geom::distance(points_[i], m_world) > max_distance)
|
||||
if (math::distance(points_[i], m_world) > max_distance)
|
||||
break;
|
||||
|
||||
gfx::painter::text_options opts;
|
||||
opts.c = {0, 0, 0, 255};
|
||||
opts.x = gfx::painter::x_align::left;
|
||||
opts.y = gfx::painter::y_align::bottom;
|
||||
opts.scale = 2.f;
|
||||
auto p = geom::swizzle<0, 1>(geom::as_point(camera_transform * geom::homogeneous(geom::swizzle<0, 1, -1>(points_[i]))));
|
||||
p[0] = std::round((p[0] * 0.5f + 0.5f) * width());
|
||||
p[1] = std::round((0.5f - p[1] * 0.5f) * height());
|
||||
opts.scale = {2.f, 2.f};
|
||||
auto p = math::swizzle<0, 1>(math::as_point(camera_transform * math::homogeneous(math::swizzle<0, 1, -1>(points_[i]))));
|
||||
p[0] = std::round((p[0] * 0.5f + 0.5f) * state().size[0]);
|
||||
p[1] = std::round((0.5f - p[1] * 0.5f) * state().size[1]);
|
||||
painter_.text(p, util::to_string(i), opts);
|
||||
}
|
||||
}
|
||||
|
||||
painter_.render(geom::window_camera{width(), height()}.transform());
|
||||
painter_.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<geom::point<float, 2>> points_;
|
||||
std::vector<geom::segment<std::uint16_t>> edges_;
|
||||
std::vector<geom::triangle<std::uint16_t>> triangles_;
|
||||
geom::box<float, 2> bbox_;
|
||||
geom::point<float, 2> camera_center_;
|
||||
std::vector<math::point<float, 2>> points_;
|
||||
std::vector<math::segment<std::uint16_t>> edges_;
|
||||
std::vector<math::triangle<std::uint16_t>> triangles_;
|
||||
math::box<float, 2> bbox_;
|
||||
math::point<float, 2> camera_center_;
|
||||
float camera_size_;
|
||||
float camera_size_tgt_;
|
||||
|
||||
std::optional<geom::point<int, 2>> drag_start_;
|
||||
std::optional<math::point<int, 2>> drag_start_;
|
||||
std::vector<std::uint16_t> closest_points_;
|
||||
|
||||
gfx::painter painter_;
|
||||
|
|
@ -208,7 +202,25 @@ private:
|
|||
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> clock_;
|
||||
};
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<triangulation_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<triangulation_app>({.name = "Triangulation example"
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""
|
||||
""});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
122
examples/ui.cpp
122
examples/ui.cpp
|
|
@ -1,122 +0,0 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
|
||||
#include <psemek/ui/controller.hpp>
|
||||
#include <psemek/ui/default_element_factory.hpp>
|
||||
#include <psemek/ui/font.hpp>
|
||||
#include <psemek/ui/frame.hpp>
|
||||
#include <psemek/ui/grid_layout.hpp>
|
||||
#include <psemek/ui/button.hpp>
|
||||
#include <psemek/ui/screen.hpp>
|
||||
#include <psemek/ui/window.hpp>
|
||||
|
||||
#include <psemek/async/event_loop.hpp>
|
||||
|
||||
#include <psemek/util/recursive.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <psemek/geom/camera.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
static std::string const lorem_ipsum =
|
||||
R"(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.)";
|
||||
|
||||
static std::string const to_be =
|
||||
R"(To be, or not to be, that is the question:
|
||||
Whether 'tis nobler in the mind to suffer
|
||||
The slings and arrows of outrageous fortune,
|
||||
Or to take Arms against a Sea of troubles,
|
||||
And by opposing end them: to die, to sleep;
|
||||
No more; and by a sleep, to say we end
|
||||
The heart-ache, and the thousand natural shocks
|
||||
That Flesh is heir to? 'Tis a consummation)";
|
||||
|
||||
struct ui_example
|
||||
: app::app
|
||||
{
|
||||
ui_example()
|
||||
: app("UI example", 1)
|
||||
, ui_controller(&loop)
|
||||
{
|
||||
auto style = std::make_shared<ui::style>();
|
||||
style->font = ui::make_default_9x12_font();
|
||||
style->text_scale = 2;
|
||||
|
||||
auto screen = element_factory.make_screen();
|
||||
screen->set_style(style);
|
||||
|
||||
auto window = element_factory.make_window("Settings");
|
||||
auto frame = element_factory.make_frame();
|
||||
{
|
||||
auto style = std::make_shared<ui::style>();
|
||||
style->border_width = 0;
|
||||
style->shadow_offset = {0, 0};
|
||||
frame->set_style(style);
|
||||
}
|
||||
auto label = element_factory.make_label(to_be);
|
||||
label->set_overflow(ui::label::overflow_mode::ellipsis);
|
||||
frame->set_child(label);
|
||||
window->set_child(frame);
|
||||
screen->add_child(window, ui::screen::x_policy::center, ui::screen::y_policy::center);
|
||||
|
||||
ui_controller.set_root(std::move(screen));
|
||||
}
|
||||
|
||||
void on_resize(int width, int height) override
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
ui_controller.reshape({{{0, width}, {0, height}}});
|
||||
}
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override
|
||||
{
|
||||
app::on_mouse_move(x, y, dx, dy);
|
||||
ui_controller.event(ui::mouse_move{{x, y}});
|
||||
}
|
||||
|
||||
void on_left_button_down() override
|
||||
{
|
||||
app::on_left_button_down();
|
||||
ui_controller.event(ui::mouse_click{ui::mouse_button::left, true});
|
||||
}
|
||||
|
||||
void on_left_button_up() override
|
||||
{
|
||||
app::on_left_button_up();
|
||||
ui_controller.event(ui::mouse_click{ui::mouse_button::left, false});
|
||||
}
|
||||
|
||||
void update() override;
|
||||
|
||||
void present() override;
|
||||
|
||||
async::event_loop loop;
|
||||
ui::controller ui_controller;
|
||||
ui::default_element_factory element_factory;
|
||||
};
|
||||
|
||||
void ui_example::update()
|
||||
{
|
||||
loop.pump();
|
||||
}
|
||||
|
||||
void ui_example::present()
|
||||
{
|
||||
gl::ClearColor(0.8f, 0.8f, 0.8f, 1.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
|
||||
|
||||
gfx::render_target rt;
|
||||
rt.framebuffer = &gfx::framebuffer::null();
|
||||
rt.draw_buffer = gl::BACK_LEFT;
|
||||
rt.viewport = {{{0, width()}, {0, height()}}};
|
||||
|
||||
ui_controller.render(rt);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
return app::main<ui_example>();
|
||||
}
|
||||
|
|
@ -1,24 +1,25 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/geom/orthographic.hpp>
|
||||
#include <psemek/math/orthographic.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
struct water_1d_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
water_1d_app();
|
||||
water_1d_app(options const &, context const &);
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
|
||||
geom::box<float, 2> simulation_area;
|
||||
math::box<float, 2> simulation_area;
|
||||
float aspect_ratio;
|
||||
|
||||
std::size_t const N = 50;
|
||||
std::size_t const N = 100;
|
||||
std::vector<float> bed;
|
||||
std::vector<float> height;
|
||||
std::vector<float> discharge;
|
||||
|
|
@ -28,8 +29,7 @@ struct water_1d_app
|
|||
gfx::painter painter;
|
||||
};
|
||||
|
||||
water_1d_app::water_1d_app()
|
||||
: app("Water 1D simulation", 4)
|
||||
water_1d_app::water_1d_app(options const &, context const &)
|
||||
{
|
||||
simulation_area[0] = {-1.f, 1.f};
|
||||
simulation_area[1] = {0.f, 1.f};
|
||||
|
|
@ -42,21 +42,25 @@ water_1d_app::water_1d_app()
|
|||
{
|
||||
bed[i] += (0.5f * (N - i)) / N;
|
||||
bed[i] += (100.f / (N - i)) / N;
|
||||
bed[i] += 0.125f * std::exp(- 1000.f * math::sqr(i * 1.f / N - 0.5f));
|
||||
|
||||
// bed[i] += (0.25f * i) / N;
|
||||
// height[i] += (0.3f * i) / N;
|
||||
// height[i] = 0.3f - bed[i];
|
||||
// height[i] = 0.25f;
|
||||
}
|
||||
|
||||
// bed[N * 3 / 4] = 0.2f;
|
||||
// bed[N * 3 / 4] += 0.2f;
|
||||
|
||||
discharge[0] = 0.04f;
|
||||
discharge[N] = 0.f;
|
||||
|
||||
// height[0] = 20.f;
|
||||
}
|
||||
|
||||
void water_1d_app::update()
|
||||
{
|
||||
float const dt = 0.01f;
|
||||
float const dt = 0.005f;
|
||||
float const dx = simulation_area[0].length() / N;
|
||||
float const g = 10.f;
|
||||
float const max_speed = 1.f;
|
||||
|
|
@ -134,7 +138,7 @@ void water_1d_app::present()
|
|||
gl::ClearColor(0.8f, 0.8f, 0.8f, 0.8f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
geom::box<float, 3> view_area;
|
||||
math::box<float, 3> view_area;
|
||||
{
|
||||
view_area[0] = simulation_area[0];
|
||||
view_area[1] = simulation_area[1];
|
||||
|
|
@ -148,16 +152,37 @@ void water_1d_app::present()
|
|||
view_area[0].max += extra_x / 2.f;
|
||||
}
|
||||
|
||||
geom::matrix<float, 4, 4> const transform = geom::orthographic{view_area}.homogeneous_matrix();
|
||||
math::matrix<float, 4, 4> const transform = math::orthographic{view_area}.homogeneous_matrix();
|
||||
|
||||
painter.rect(simulation_area, {255, 255, 255, 255});
|
||||
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
gfx::color_rgba const earth_color{127, 63, 0, 255};
|
||||
gfx::color_rgba const water_color{0, 0, 255, 255};
|
||||
|
||||
for (std::size_t i = 0; i + 1 < N; ++i)
|
||||
{
|
||||
painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min, simulation_area[1].min + bed[i]}}}, gfx::dark(gfx::yellow).as_color_rgba());
|
||||
painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min + bed[i], simulation_area[1].min + bed[i] + height[i]}}}, gfx::blue);
|
||||
math::point p0{x(i + 0) * 0.5f + x(i + 1) * 0.5f, simulation_area[1].min};
|
||||
math::point p1{x(i + 1) * 0.5f + x(i + 2) * 0.5f, simulation_area[1].min};
|
||||
|
||||
math::point q0{p0[0], simulation_area[1].min + bed[i]};
|
||||
math::point q1{p1[0], simulation_area[1].min + bed[i + 1]};
|
||||
|
||||
math::point w0{p0[0], simulation_area[1].min + bed[i] + height[i]};
|
||||
math::point w1{p1[0], simulation_area[1].min + bed[i + 1] + height[i + 1]};
|
||||
|
||||
painter.triangle(p0, p1, q0, earth_color);
|
||||
painter.triangle(q0, p1, q1, earth_color);
|
||||
|
||||
painter.triangle(q0, q1, w0, water_color);
|
||||
painter.triangle(w0, q1, w1, water_color);
|
||||
}
|
||||
|
||||
// for (std::size_t i = 0; i < N; ++i)
|
||||
// {
|
||||
// painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min, simulation_area[1].min + bed[i]}}}, gfx::dark(gfx::yellow).as_color_rgba());
|
||||
// painter.rect({{{x(i), x(i + 1)}, {simulation_area[1].min + bed[i], simulation_area[1].min + bed[i] + height[i]}}}, gfx::blue);
|
||||
// }
|
||||
|
||||
for (std::size_t i = 1; i < N; ++i)
|
||||
{
|
||||
painter.line({x(i), 0.f}, {x(i), discharge[i]}, 0.005f, gfx::red);
|
||||
|
|
@ -166,19 +191,24 @@ void water_1d_app::present()
|
|||
painter.render(transform);
|
||||
}
|
||||
|
||||
void water_1d_app::on_resize(int width, int height)
|
||||
void water_1d_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
aspect_ratio = (width * 1.f) / height;
|
||||
aspect_ratio = (event.size[0] * 1.f) / event.size[1];
|
||||
}
|
||||
|
||||
float water_1d_app::x(std::size_t i) const
|
||||
{
|
||||
return geom::lerp(simulation_area[0], (1.f * i) / N);
|
||||
return math::lerp(simulation_area[0], (1.f * i) / N);
|
||||
}
|
||||
|
||||
int main()
|
||||
namespace psemek::app
|
||||
{
|
||||
return app::main<water_1d_app>();
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<water_1d_app>({.name = "Water example", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,212 +1,409 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/app/main.hpp>
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/geom/orthographic.hpp>
|
||||
#include <psemek/geom/gauss.hpp>
|
||||
#include <psemek/math/orthographic.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/util/ndarray.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/uniform.hpp>
|
||||
#include <psemek/random/uniform_sphere.hpp>
|
||||
#include <psemek/pcg/perlin.hpp>
|
||||
|
||||
#include <format>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
static std::pair<int, int> unpack_triangular_id(int id)
|
||||
{
|
||||
int i = int(std::floor(0.5f * (sqrt(1.f + 8.f * id) - 1.f)));
|
||||
int j = id - (i * (i + 1)) / 2;
|
||||
return {i, j};
|
||||
}
|
||||
|
||||
static int pack_triangular_id(std::pair<int, int> const & p)
|
||||
{
|
||||
return (p.first * (p.first + 1)) / 2 + p.second;
|
||||
}
|
||||
|
||||
struct water_2d_app
|
||||
: app::app
|
||||
: app::application_base
|
||||
{
|
||||
water_2d_app();
|
||||
water_2d_app(options const &, context const &);
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
void on_event(app::resize_event const & event) override;
|
||||
void on_event(app::key_event const & event) override;
|
||||
|
||||
geom::box<float, 2> simulation_area;
|
||||
math::box<float, 2> simulation_area;
|
||||
math::box<float, 2> inner_area;
|
||||
float aspect_ratio;
|
||||
|
||||
int const N = 25;
|
||||
int const first_type_count = (N * (N + 1)) / 2;
|
||||
random::generator rng{random::device{}};
|
||||
|
||||
std::vector<float> water_height;
|
||||
std::vector<geom::vector<float, 2>> flux[3];
|
||||
int const N = 256;
|
||||
|
||||
geom::point<float, 2> p[3];
|
||||
float time = 0.f;
|
||||
|
||||
util::ndarray<float, 2> bed;
|
||||
util::ndarray<float, 2> water;
|
||||
util::ndarray<float, 2> flowx;
|
||||
util::ndarray<float, 2> flowy;
|
||||
util::ndarray<math::vector<float, 2>, 2> velocity;
|
||||
util::ndarray<float, 2> sediment;
|
||||
util::ndarray<float, 2> new_sediment;
|
||||
|
||||
gfx::painter painter;
|
||||
|
||||
bool paused = true;
|
||||
bool show_water = true;
|
||||
bool show_velocity = false;
|
||||
bool show_particles = false;
|
||||
bool erosion_on = false;
|
||||
bool rain_on = false;
|
||||
|
||||
struct particle
|
||||
{
|
||||
math::point<float, 2> position;
|
||||
int lifetime;
|
||||
};
|
||||
|
||||
std::vector<particle> particles;
|
||||
};
|
||||
|
||||
water_2d_app::water_2d_app()
|
||||
: app("Water 2D simulation", 4)
|
||||
water_2d_app::water_2d_app(options const &, context const & ctx)
|
||||
{
|
||||
p[0] = {- N / 2.f, 0.f};
|
||||
p[1] = { N / 2.f, 0.f};
|
||||
p[2] = {0.f, N * std::sqrt(0.75f)};
|
||||
(void)ctx;
|
||||
// ctx.vsync(true);
|
||||
|
||||
simulation_area |= p[0];
|
||||
simulation_area |= p[1];
|
||||
simulation_area |= p[2];
|
||||
simulation_area[0] = {-1.f, 1.f};
|
||||
simulation_area[1] = {-1.f, 1.f};
|
||||
|
||||
water_height.assign(N * N, 0.5f);
|
||||
flux[0].assign(first_type_count, geom::vector{0.f, -5.f});
|
||||
flux[1].assign(first_type_count, geom::vector{0.f, -5.f});
|
||||
flux[2].assign(first_type_count, geom::vector{0.f, -5.f});
|
||||
inner_area = math::shrink(simulation_area, simulation_area.dimensions() / (2.f * N));
|
||||
|
||||
for (int i = 0; i < N; ++i)
|
||||
bed.assign({N, N}, 0.f);
|
||||
water.assign({N, N}, 0.f);
|
||||
flowx.assign({N + 1, N}, 0.f);
|
||||
flowy.assign({N, N + 1}, 0.f);
|
||||
velocity.assign({N, N}, math::vector<float, 2>::zero());
|
||||
sediment.assign({N, N}, 0.f);
|
||||
new_sediment.assign({N, N}, 0.f);
|
||||
|
||||
// water(N / 2, N / 2) = 100.f;
|
||||
|
||||
random::uniform_sphere_vector_distribution<float, 2> d;
|
||||
util::ndarray<math::vector<float, 2>, 2> grads({9, 9});
|
||||
for (auto & v : grads)
|
||||
v = d(rng);
|
||||
|
||||
pcg::perlin<float, 2> noise(std::move(grads), pcg::seamless);
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
flux[0][first_type_count - i - 1] = geom::vector<float, 2>::zero();
|
||||
flux[1][first_type_count - i - 1] = geom::vector<float, 2>::zero();
|
||||
flux[2][first_type_count - i - 1] = geom::vector<float, 2>::zero();
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
// if (x == N / 2 && (y != N / 4 && y != 3 * N / 4))
|
||||
// bed(x, y) = 100.f;
|
||||
|
||||
[[maybe_unused]] float tx = (x + 0.5f) / N;
|
||||
[[maybe_unused]] float ty = (y + 0.5f) / N;
|
||||
|
||||
float n = 0.f;
|
||||
|
||||
// Archipelago
|
||||
n = 0.5f * noise(tx, ty) + 0.5f * noise(math::frac(2.f * tx), math::frac(2.f * ty));
|
||||
n -= 0.5f * math::distance(math::point{tx, ty}, math::point{0.5f, 0.5f});
|
||||
n = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, n), {0.f, 1.f}));
|
||||
|
||||
// Island
|
||||
// n = 4.f * std::max(0.f, 1.f - 3.f * math::distance(math::point{tx, ty}, math::point{0.5f, 0.5f}) + (2.f * noise(tx, ty) - 1.f) * 0.25f);
|
||||
|
||||
// Delta
|
||||
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * (1.f - tx);
|
||||
|
||||
// Double delta
|
||||
// n = 4.f * std::abs(2.f * noise(tx, ty) - 1.f) * std::pow(4.f * tx * (1.f - tx), 2.f);
|
||||
|
||||
// River
|
||||
// n = std::min(4.f, math::lerp(10.f, 20.f, noise(tx, ty)) * std::abs(ty - math::lerp({0.4f, 0.6f}, noise(tx, 0.f))));
|
||||
|
||||
// n = 0.f;
|
||||
|
||||
// Canyon
|
||||
// n = 10.f * math::clamp(10.f * math::lerp(-1.f, 1.f, noise(tx, ty)), {0.f, 1.f});
|
||||
|
||||
// Canyon v2
|
||||
// n = math::clamp(20.f * std::abs(2.f * noise(tx, ty) - 1.f) - 2.f, {0.f, 5.f});
|
||||
|
||||
// Shore
|
||||
// n = math::clamp(20.f * (ty - math::lerp(0.4f, 0.6f, noise(0.f, tx))), {-4.f, 4.f}) + 4.f;
|
||||
|
||||
// Circles
|
||||
// n = 20.f * std::max(0.f, 1.f - 25.f * math::length(math::pointwise_mult({1.f, 0.1f}, math::point{tx, std::fmod(10.f * ty, 1.f)} - math::point{0.5f, 0.5f})));
|
||||
|
||||
bed(x, y) = n;
|
||||
|
||||
// water(x, y) = std::max(0.f, 4.0f - bed(x, y));
|
||||
|
||||
// water(x, y) = 10.f - bed(x, y);
|
||||
|
||||
// if (x == 0)
|
||||
// water(x, y) = 100.f - bed(x, y);
|
||||
|
||||
water(x, y) = std::max(0.f, 1.f - bed(x, y)) * (0.5f + 0.5f * noise(tx, ty));
|
||||
|
||||
flowx(x, y) = (2.f * ty - 1.f) * water(x, y) * 10.f / N;
|
||||
}
|
||||
}
|
||||
|
||||
// water(N / 2, N / 2) = 10000.f;
|
||||
}
|
||||
|
||||
float const dt = 0.001f;
|
||||
|
||||
void water_2d_app::update()
|
||||
{
|
||||
float const dt = 0.01f;
|
||||
float const max_speed = 10.f;
|
||||
if (paused)
|
||||
return;
|
||||
|
||||
geom::vector<float, 2> const flux_normal[3] =
|
||||
float const dx = simulation_area[0].length() / N;
|
||||
float const dy = simulation_area[1].length() / N;
|
||||
float const g = 10.f;
|
||||
float const friction = std::pow(1.f, dt);
|
||||
float const viscosity = 0.f;
|
||||
float const sediment_capacity = 0.1f;
|
||||
float const erosion = 1.f;
|
||||
float const deposition = 10.f;
|
||||
float const coriolis = 0.01f;
|
||||
int const max_particles = 16 * 1024;
|
||||
int const particles_per_frame = 16;
|
||||
int const max_particle_lifetime = max_particles / particles_per_frame;
|
||||
|
||||
// Init boundary flows
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
geom::vector{ std::sqrt(3.f) / 2.f, 1.f / 2.f },
|
||||
geom::vector{ - std::sqrt(3.f) / 2.f, 1.f / 2.f },
|
||||
geom::vector{ 0.f, -1.f },
|
||||
};
|
||||
// flowy(x, 0) = -1.f / N;
|
||||
// flowy(x, N) = 1.f / N;
|
||||
|
||||
auto const rot1 = [this](std::pair<int, int> const & p)
|
||||
// if (x >= 3 * N / 4) flowy(x, 0) = 3.f / N;
|
||||
// if (x < N / 4) flowy(x, N) = -3.f / N;
|
||||
|
||||
// flowy(x, 0) = 5.f * std::sin(10.f * time) / N;
|
||||
|
||||
flowy(x, 0) = 0.f;
|
||||
flowy(x, N) = 0.f;
|
||||
}
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
return std::pair{N - p.second, p.first - p.second};
|
||||
};
|
||||
// flowx(0, y) = 5.f * std::sin(10.f * time) / N;
|
||||
|
||||
auto const rot2 = [rot1](std::pair<int, int> const & p)
|
||||
// flowx(0, y) = 10.f / N;
|
||||
// flowx(N, y) = 1.f / N;
|
||||
|
||||
// flowx(0, y) = 10.f * ((y + 0.5f) / N) / N;
|
||||
// flowx(N, y) = - (10.f / N - flowx(0, y));
|
||||
|
||||
// if (y < N / 4) flowx(0, y) = 3.f / N;
|
||||
// if (y >= 3 * N / 4) flowx(N, y) = -3.f / N;
|
||||
|
||||
// if (bed(0, y) < 0.75f)
|
||||
// flowx(0, y) = 4.f / N;
|
||||
// flowx(0, y) = 5.f * math::sqr(std::max(0.f, std::sin(15.f * time))) / N;
|
||||
|
||||
// if (bed(N - 1, y) < 0.75f)
|
||||
// flowx(N, y) = 5.f / N;
|
||||
}
|
||||
|
||||
// Rain :)
|
||||
if (rain_on)
|
||||
for (int y = 0; y < N; ++y)
|
||||
for (int x = 0; x < N; ++x)
|
||||
// if (random::uniform<float>(rng) < 0.1f)
|
||||
water(x, y) += random::uniform<float>(rng) * dt;
|
||||
// water(x, y) += dt;
|
||||
|
||||
// water(N/2, N/2) += 1000.f * dt;
|
||||
|
||||
// Update X flows
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
return rot1(rot1(p));
|
||||
};
|
||||
for (int x = 1; x < N; ++x)
|
||||
flowx(x, y) = friction * flowx(x, y) + g * dt * (water(x - 1, y) + bed(x - 1, y) - water(x, y) - bed(x, y));
|
||||
|
||||
std::vector<float> dh(water_height.size(), 0.f);
|
||||
std::vector<geom::vector<float, 2>> du[3];
|
||||
for (int s = 0; s < 3; ++s)
|
||||
du[s].assign(first_type_count, geom::vector{0.f, 0.f});
|
||||
flowx(0, y) = friction * flowx(0, y) + g * dt * (water(N - 1, y) + bed(N - 1, y) - water(0, y) - bed(0, y));
|
||||
}
|
||||
|
||||
for (int id = 0; id < N * N; ++id)
|
||||
// Update Y flows
|
||||
for (int y = 1; y < N; ++y)
|
||||
for (int x = 0; x < N; ++x)
|
||||
flowy(x, y) = friction * flowy(x, y) + g * dt * (water(x, y - 1) + bed(x, y - 1) - water(x, y) - bed(x, y));
|
||||
|
||||
// Apply viscosity to X flows
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
bool const first_type = (id < first_type_count);
|
||||
auto const [i, j] = first_type ? unpack_triangular_id(id) : unpack_triangular_id(id - first_type_count);
|
||||
|
||||
int e[3];
|
||||
|
||||
if (first_type)
|
||||
for (int x = 1; x < N; ++x)
|
||||
{
|
||||
e[0] = pack_triangular_id({i, j});
|
||||
e[1] = pack_triangular_id(rot1({i + 1, j + 1}));
|
||||
e[2] = pack_triangular_id(rot2({i + 1, j}));
|
||||
float h = (flowx(x, y) > 0.f) ? water(x - 1, y) : water(x, y);
|
||||
h *= h;
|
||||
|
||||
if (h > 0.f)
|
||||
flowx(x, y) *= h / (h + 3.f * dt * viscosity);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply viscosity to Y flows
|
||||
for (int y = 1; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
float h = (flowy(x, y) > 0.f) ? water(x, y - 1) : water(x, y);
|
||||
h *= h;
|
||||
|
||||
if (h > 0.f)
|
||||
flowy(x, y) *= h / (h + 3.f * dt * viscosity);
|
||||
}
|
||||
}
|
||||
|
||||
// Scale flows
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
float outflow = 0.f;
|
||||
outflow += std::max(0.f, - flowx(x , y ));
|
||||
outflow += std::max(0.f, flowx((x + 1) % N, y ));
|
||||
outflow += std::max(0.f, - flowy(x , y ));
|
||||
outflow += std::max(0.f, flowy(x , y + 1));
|
||||
|
||||
float max_outflow = water(x, y) * dx * dy / dt;
|
||||
|
||||
if (outflow > 0.f)
|
||||
{
|
||||
float scale = std::min(1.f, max_outflow / outflow);
|
||||
|
||||
if (flowx(x, y) < 0.f) flowx(x, y) *= scale;
|
||||
if (flowx((x + 1) % N, y) > 0.f) flowx((x + 1) % N, y) *= scale;
|
||||
|
||||
if (flowy(x, y) < 0.f) flowy(x, y) *= scale;
|
||||
if (flowy(x, y + 1) > 0.f) flowy(x, y + 1) *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update water and compute velocity
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
float w_old = water(x, y);
|
||||
|
||||
water(x, y) += dt / dx / dy * (flowx(x, y) + flowy(x, y) - flowx((x + 1) % N, y) - flowy(x, y + 1));
|
||||
|
||||
float w_avg = (w_old + water(x, y)) / 2.f;
|
||||
|
||||
math::vector v{0.f, 0.f};
|
||||
|
||||
if (w_avg > 0.f)
|
||||
{
|
||||
v[0] = (flowx(x, y) + flowx((x + 1) % N, y)) / 2.f / dx / w_avg;
|
||||
v[1] = (flowy(x, y) + flowy(x, y + 1)) / 2.f / dy / w_avg;
|
||||
}
|
||||
|
||||
velocity(x, y) = v;
|
||||
}
|
||||
}
|
||||
|
||||
// Add coriolis force
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
auto n = math::ort(velocity(x, y)) * coriolis * dt / 2.f * std::sin(float(math::pi) / 2.f * ((y + 0.5f) * 2.f / N - 1.f));
|
||||
|
||||
flowx(x, y) += n[0];
|
||||
flowx((x + 1) % N, y) += n[0];
|
||||
flowy(x, y) += n[1];
|
||||
flowy(x, y + 1) += n[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (erosion_on)
|
||||
{
|
||||
// Erosion-deposition
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
float c = sediment_capacity * water(x, y) * math::length(velocity(x, y));
|
||||
|
||||
if (c >= sediment(x, y))
|
||||
{
|
||||
float delta = std::min(bed(x, y), dt * erosion * (c - sediment(x, y)));
|
||||
bed(x, y) -= delta;
|
||||
sediment(x, y) += delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
float delta = std::min(sediment(x, y), dt * deposition * (sediment(x, y) - c));
|
||||
bed(x, y) += delta;
|
||||
sediment(x, y) -= delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sediment transport
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
math::point p = math::lerp(simulation_area, {(x + 0.5f) / N, (y + 0.5f) / N});
|
||||
p -= velocity(x, y) * dt;
|
||||
|
||||
p = math::clamp(p, inner_area);
|
||||
|
||||
auto q = math::unlerp(inner_area, p) * (N - 1.f);
|
||||
|
||||
int ix = std::min<int>(N - 2, std::floor(q[0]));
|
||||
int iy = std::min<int>(N - 2, std::floor(q[1]));
|
||||
|
||||
float tx = q[0] - ix;
|
||||
float ty = q[1] - iy;
|
||||
|
||||
new_sediment(x, y) = 0.f
|
||||
+ sediment(ix, iy) * (1.f - tx) * (1.f - ty)
|
||||
+ sediment(ix + 1, iy) * tx * (1.f - ty)
|
||||
+ sediment(ix, iy + 1) * (1.f - tx) * ty
|
||||
+ sediment(ix + 1, iy + 1) * tx * ty
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
std::swap(sediment, new_sediment);
|
||||
}
|
||||
|
||||
// Init particles
|
||||
for (int i = 0; i < particles_per_frame; ++i)
|
||||
if (particles.size() < max_particles)
|
||||
{
|
||||
math::point pos{random::uniform<float>(rng, 0.f, N), random::uniform<float>(rng, 0.f, N)};
|
||||
if (water(std::floor(pos[0]), std::floor(pos[1])) > 1e-3f)
|
||||
particles.push_back({pos, 0});
|
||||
}
|
||||
|
||||
// Update particles
|
||||
for (auto & p : particles)
|
||||
{
|
||||
p.position += velocity(std::min<int>(N - 1, std::floor(p.position[0])), std::min<int>(N - 1, std::floor(p.position[1]))) * (N * dt);
|
||||
p.position[0] = math::fmod(p.position[0], float(N));
|
||||
p.position[1] = math::clamp(p.position[1], {0.f, N});
|
||||
p.lifetime += 1;
|
||||
}
|
||||
|
||||
// Destroy particles
|
||||
for (int i = 0; i < particles.size();)
|
||||
{
|
||||
if (particles[i].lifetime >= max_particle_lifetime)
|
||||
{
|
||||
std::swap(particles[i], particles.back());
|
||||
particles.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
e[0] = pack_triangular_id({i, j});
|
||||
e[1] = pack_triangular_id(rot1({i + 2, j + 2}));
|
||||
e[2] = pack_triangular_id(rot2({i + 2, j}));
|
||||
}
|
||||
|
||||
float const flux_sign = first_type ? -1.f : 1.f;
|
||||
|
||||
for (int s = 0; s < 3; ++s)
|
||||
dh[id] += flux_sign * geom::dot(flux_normal[s], flux[s][e[s]]) * dt;
|
||||
++i;
|
||||
}
|
||||
|
||||
for (int id = 0; id < N * N; ++id)
|
||||
{
|
||||
water_height[id] += dh[id];
|
||||
water_height[id] = std::max(0.f, water_height[id]);
|
||||
}
|
||||
|
||||
for (int s = 0; s < 3; ++s)
|
||||
{
|
||||
for (int id = 0; id < first_type_count - N; ++id)
|
||||
{
|
||||
auto const [i, j] = unpack_triangular_id(id);
|
||||
|
||||
std::pair<int, int> tin, tout;
|
||||
|
||||
if (s == 0)
|
||||
{
|
||||
tout = {i, j};
|
||||
tin = {i, j};
|
||||
}
|
||||
else if (s == 1)
|
||||
{
|
||||
tout = rot2({i + 1, j});
|
||||
tin = rot2({i + 2, j});
|
||||
}
|
||||
else // if (s == 2)
|
||||
{
|
||||
tout = rot1({i + 1, j + 1});
|
||||
tin = rot1({i + 2, j + 2});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (int s = 0; s < 3; ++s)
|
||||
{
|
||||
for (int id = 0; id < first_type_count - N; ++id)
|
||||
{
|
||||
flux[s][id] += du[s][id];
|
||||
|
||||
auto const [i, j] = unpack_triangular_id(id);
|
||||
|
||||
std::pair<int, int> tid;
|
||||
bool first_type;
|
||||
|
||||
float const nflux = geom::dot(flux[s][id], flux_normal[s]);
|
||||
bool const forward = nflux > 0.f;
|
||||
|
||||
if (forward)
|
||||
{
|
||||
first_type = true;
|
||||
if (s == 0)
|
||||
tid = {i, j};
|
||||
else if (s == 1)
|
||||
tid = rot2({i + 1, j});
|
||||
else // if (s == 2)
|
||||
tid = rot1({i + 1, j + 1});
|
||||
}
|
||||
else
|
||||
{
|
||||
first_type = false;
|
||||
if (s == 0)
|
||||
tid = {i, j};
|
||||
else if (s == 1)
|
||||
tid = rot2({i + 2, j});
|
||||
else // if (s == 2)
|
||||
tid = rot1({i + 2, j + 2});
|
||||
}
|
||||
|
||||
auto const h = water_height[pack_triangular_id(tid) + (first_type ? 0 : first_type_count)];
|
||||
float const max_flux = h * max_speed;
|
||||
|
||||
if (forward && nflux > max_flux)
|
||||
{
|
||||
flux[s][id] -= flux_normal[s] * (nflux - max_flux);
|
||||
}
|
||||
|
||||
if (!forward && (-nflux > max_flux))
|
||||
{
|
||||
flux[s][id] += flux_normal[s] * ((-nflux) - max_flux);
|
||||
}
|
||||
|
||||
// if (forward)
|
||||
// flux[s][id] = std::min(flux[s][id], h * max_speed);
|
||||
// else
|
||||
// flux[s][id] = std::max(flux[s][id], - h * max_speed);
|
||||
}
|
||||
}
|
||||
time += dt;
|
||||
}
|
||||
|
||||
void water_2d_app::present()
|
||||
|
|
@ -214,16 +411,13 @@ void water_2d_app::present()
|
|||
gl::ClearColor(0.8f, 0.8f, 0.8f, 0.8f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
gl::Enable(gl::BLEND);
|
||||
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
geom::box<float, 3> view_area;
|
||||
math::box<float, 3> view_area;
|
||||
{
|
||||
view_area[0] = simulation_area[0];
|
||||
view_area[1] = simulation_area[1];
|
||||
view_area[2] = {-1.f, 1.f};
|
||||
|
||||
float extra_y = view_area[1].length() * 0.1f;
|
||||
float extra_y = view_area[1].length() * 0.f;
|
||||
view_area[1].min -= extra_y;
|
||||
view_area[1].max += extra_y;
|
||||
float extra_x = view_area[1].length() * aspect_ratio - view_area[0].length();
|
||||
|
|
@ -231,96 +425,153 @@ void water_2d_app::present()
|
|||
view_area[0].max += extra_x / 2.f;
|
||||
}
|
||||
|
||||
geom::matrix<float, 4, 4> const transform = geom::orthographic{view_area}.homogeneous_matrix();
|
||||
math::matrix<float, 4, 4> const transform = math::orthographic{view_area}.homogeneous_matrix();
|
||||
|
||||
float const pixel = 2.f / std::sqrt(width() * height() * geom::det(transform));
|
||||
painter.rect(simulation_area, {0, 0, 0, 255});
|
||||
|
||||
// Background
|
||||
painter.triangle(p[0], p[1], p[2], gfx::white);
|
||||
float invN = 1.f / N;
|
||||
|
||||
// Water tiles
|
||||
for (int id = 0; id < N * N; ++id)
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
bool const first_type = (id < first_type_count);
|
||||
auto const [i, j] = first_type ? unpack_triangular_id(id) : unpack_triangular_id(id - first_type_count);
|
||||
(void)pack_triangular_id;
|
||||
|
||||
auto at = [this](int i, int j)
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
float const u = (i * 1.f) / N;
|
||||
float const v = (j * 1.f) / N;
|
||||
return geom::lerpn(p[0], 1.f - u, p[1], v, p[2], u - v);
|
||||
};
|
||||
auto min = simulation_area.corner(x * invN, y * invN);
|
||||
auto max = simulation_area.corner((x + 1) * invN, (y + 1) * invN);
|
||||
|
||||
auto f = [](float h)
|
||||
{
|
||||
float const C = 1.f;
|
||||
float const e = std::exp(- C * h);
|
||||
return (1.f - e) / (1.f + e);
|
||||
};
|
||||
float b = 1.f - std::exp(- 1.f * bed(x, y));
|
||||
float w = 1.f - std::exp(- 1.f * water(x, y));
|
||||
float s = 1.f - std::exp(- 1.f * sediment(x, y));
|
||||
|
||||
auto color = gfx::to_coloru8(gfx::color_4f{0.f, 0.f, 1.f, f(water_height[id])});
|
||||
// if (water(x, y) > 1e-3f)
|
||||
// w = 0.5f + 0.5f * w;
|
||||
|
||||
if (water_height[id] < 0.f)
|
||||
color = gfx::magenta;
|
||||
auto bed_color = gfx::to_coloru8(gfx::color_4f{0.96f, 0.72f, 0.53f, b});
|
||||
// auto bed_color = gfx::to_coloru8(gfx::color_4f{0.3f, 0.5f, 0.1f, b});
|
||||
auto color = gfx::to_coloru8(gfx::color_4f{0.f, 0.25f, 1.f, w});
|
||||
auto sediment_color = gfx::to_coloru8(gfx::color_4f{1.f, 0.25f, 0.f, s * w});
|
||||
|
||||
if (first_type)
|
||||
{
|
||||
painter.triangle(at(i, j), at(i + 1, j + 1), at(i + 1, j), color);
|
||||
}
|
||||
else
|
||||
{
|
||||
painter.triangle(at(i + 1, j), at(i + 1, j + 1), at(i + 2, j + 1), color);
|
||||
auto box = math::box<float, 2>::singleton(min) | math::box<float, 2>::singleton(max);
|
||||
|
||||
painter.rect(box, bed_color);
|
||||
if (show_water)
|
||||
{
|
||||
painter.rect(box, color);
|
||||
painter.rect(box, sediment_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edges
|
||||
// if(false)
|
||||
for (int i = 1; i <= N; ++i)
|
||||
if (show_velocity)
|
||||
{
|
||||
float t = (i * 1.f) / N;
|
||||
painter.line(geom::lerp(p[0], p[1], t), geom::lerp(p[0], p[2], t), 2.f * pixel, gfx::black, false);
|
||||
painter.line(geom::lerp(p[1], p[0], t), geom::lerp(p[1], p[2], t), 2.f * pixel, gfx::black, false);
|
||||
painter.line(geom::lerp(p[2], p[0], t), geom::lerp(p[2], p[1], t), 2.f * pixel, gfx::black, false);
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
auto center = simulation_area.corner((x + 0.5f) * invN, (y + 0.5f) * invN);
|
||||
|
||||
auto v = water(x, y) * velocity(x, y) / 40.f;
|
||||
|
||||
float max_length = 0.05f;
|
||||
auto l = math::length(v);
|
||||
|
||||
// auto color = gfx::to_coloru8(gfx::color_4f{1.f, std::exp(- 10.f * l), 0.f, 1.f});
|
||||
auto color = gfx::color_rgba{255, 255, 255, 255};
|
||||
|
||||
float s = std::min(1.f, l / max_length);
|
||||
// s = 1.f;
|
||||
|
||||
auto d = math::normalized(v) * max_length * s / 2.f;
|
||||
|
||||
painter.line(center - d, center + d, s * 0.001f, 0.f, color, color, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flux
|
||||
// if(false)
|
||||
for (int side = 0; side < 3; ++side)
|
||||
if (show_particles)
|
||||
{
|
||||
auto at = [this, side](int i, int j)
|
||||
for (auto const & p : particles)
|
||||
{
|
||||
float const u = (i * 1.f) / N;
|
||||
float const v = (j * 1.f) / N;
|
||||
return geom::lerpn(p[side], 1.f - u, p[(side + 1) % 3], v, p[(side + 2) % 3], u - v);
|
||||
auto pos = math::lerp(simulation_area, math::vector{p.position[0] / N, p.position[1] / N});
|
||||
painter.circle(pos, 0.005f, {255, 255, 255, 127}, 6);
|
||||
}
|
||||
}
|
||||
|
||||
auto mouse = math::cast<float>(state().mouse);
|
||||
|
||||
mouse[0] = math::lerp(view_area[0], mouse[0] / state().size[0]);
|
||||
mouse[1] = math::lerp(view_area[1], 1.f - mouse[1] / state().size[1]);
|
||||
|
||||
int mx = std::floor(N * math::unlerp(simulation_area[0], mouse[0]));
|
||||
int my = std::floor(N * math::unlerp(simulation_area[1], mouse[1]));
|
||||
|
||||
if (mx >= 0 && mx < N && my >= 0 && my < N)
|
||||
{
|
||||
gfx::painter::text_options opts
|
||||
{
|
||||
.scale = {0.004f, -0.004f},
|
||||
.c = {255, 0, 255, 255},
|
||||
};
|
||||
painter.text({view_area[0].center(), math::lerp(view_area[1], 0.03f)}, std::format("b {:.3f} + w {:.3f} = h {:.3f}", bed(mx, my), water(mx, my), bed(mx, my) + water(mx, my)), opts);
|
||||
painter.text({view_area[0].center(), math::lerp(view_area[1], 0.05f)}, std::format("s {:.3f}", sediment(mx, my)), opts);
|
||||
|
||||
for (int id = 0; id < first_type_count; ++id)
|
||||
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||
{
|
||||
float const scale = 0.1f;
|
||||
for (int dy = -2; dy <= 2; ++dy)
|
||||
{
|
||||
for (int dx = -2; dx <= 2; ++dx)
|
||||
{
|
||||
int x = mx + dx;
|
||||
int y = my + dy;
|
||||
|
||||
auto const [i, j] = unpack_triangular_id(id);
|
||||
|
||||
auto const p0 = at(i + 1, j);
|
||||
auto const p1 = at(i + 1, j + 1);
|
||||
|
||||
auto const p = geom::lerp(p0, p1, 0.5f);
|
||||
|
||||
painter.line(p, p + flux[side][id] * scale, pixel * 2.f, gfx::red, false);
|
||||
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||
water(x, y) += 1000.f * dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
painter.render(transform);
|
||||
|
||||
{
|
||||
painter.text({20.f, 20.f}, std::format("{} particles", particles.size()), {.scale = {2.f, 2.f}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
|
||||
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
||||
}
|
||||
}
|
||||
|
||||
void water_2d_app::on_resize(int width, int height)
|
||||
void water_2d_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
app::on_resize(width, height);
|
||||
app::application_base::on_event(event);
|
||||
|
||||
aspect_ratio = (width * 1.f) / height;
|
||||
aspect_ratio = (event.size[0] * 1.f) / event.size[1];
|
||||
}
|
||||
|
||||
int main()
|
||||
void water_2d_app::on_event(app::key_event const & event)
|
||||
{
|
||||
return app::main<water_2d_app>();
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
paused ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::W)
|
||||
show_water ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::V)
|
||||
show_velocity ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::P)
|
||||
show_particles ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::E)
|
||||
erosion_on ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::R)
|
||||
rain_on ^= true;
|
||||
}
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<water_2d_app>({.name = "Water 2D example", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
349
examples/water_2d_hex.cpp
Normal file
349
examples/water_2d_hex.cpp
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/math/box.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/gauss.hpp>
|
||||
#include <psemek/util/ndarray.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/uniform_sphere.hpp>
|
||||
#include <psemek/pcg/perlin.hpp>
|
||||
#include <psemek/prof/profiler.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
static const math::vector x_axis {1.f, 0.f};
|
||||
static const math::vector y_axis {0.5f, std::sqrt(0.75f)};
|
||||
static const math::vector z_axis = y_axis - x_axis;
|
||||
static const int N = 64;
|
||||
|
||||
static const float dt = 0.1f;
|
||||
static const float dx = 1.f;
|
||||
static float const g = 10.f;
|
||||
static float const friction = std::pow(0.875f, dt);
|
||||
|
||||
math::point<float, 2> to_world(int x, int y)
|
||||
{
|
||||
return math::point{0.f, 0.f} + x_axis * (x - N / 2.f) + y_axis * (y - N / 2.f);
|
||||
}
|
||||
|
||||
math::point<float, 2> to_grid(math::point<float, 2> const & p)
|
||||
{
|
||||
static auto matrix = *math::inverse(math::by_columns(x_axis, y_axis));
|
||||
static auto zero = math::point{0.f, 0.f};
|
||||
|
||||
return zero + matrix * (p - zero) + math::vector{N / 2.f, N / 2.f};
|
||||
}
|
||||
|
||||
struct water_2d_hex_app
|
||||
: app::application_base
|
||||
{
|
||||
water_2d_hex_app(options const &, context const &);
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
void on_event(app::resize_event const & event) override;
|
||||
void on_event(app::key_event const & event) override;
|
||||
|
||||
void stop() override;
|
||||
|
||||
private:
|
||||
bool vsync_on_ = true;
|
||||
std::function<void(bool)> set_vsync_;
|
||||
|
||||
random::generator rng_{random::device{}};
|
||||
|
||||
float aspect_ratio_ = 1.f;
|
||||
math::vector<int, 2> screen_size_;
|
||||
math::box<float, 2> view_area_;
|
||||
gfx::painter painter_;
|
||||
|
||||
bool paused_ = false;
|
||||
float time_ = 0.f;
|
||||
|
||||
bool show_velocity_ = false;
|
||||
|
||||
util::ndarray<float, 2> bed_;
|
||||
util::ndarray<float, 2> water_;
|
||||
util::ndarray<float, 2> flow_x_; // flow_x(x, y) is (x-1, y) => (x, y)
|
||||
util::ndarray<float, 2> flow_y_; // flow_y(x, y) is (x, y-1) => (x, y)
|
||||
util::ndarray<float, 2> flow_z_; // flow_z(x, y) is (x, y-1) => (x-1, y)
|
||||
};
|
||||
|
||||
water_2d_hex_app::water_2d_hex_app(options const &, context const & ctx)
|
||||
: set_vsync_(ctx.vsync)
|
||||
{
|
||||
set_vsync_(vsync_on_);
|
||||
|
||||
bed_.resize({N + 1, N + 1}, 0.f);
|
||||
water_.resize({N + 1, N + 1}, 0.f);
|
||||
flow_x_.resize({N + 2, N + 1}, 0.f);
|
||||
flow_y_.resize({N + 1, N + 2}, 0.f);
|
||||
flow_z_.resize({N + 2, N + 2}, 0.f);
|
||||
|
||||
random::uniform_sphere_vector_distribution<float, 2> d_grad;
|
||||
|
||||
util::ndarray<math::vector<float, 2>, 2> perlin_grad({17, 17});
|
||||
for (auto & v : perlin_grad)
|
||||
v = d_grad(rng_);
|
||||
|
||||
pcg::perlin<float, 2> noise(std::move(perlin_grad));
|
||||
|
||||
for (int y = 0; y <= N; ++y)
|
||||
{
|
||||
for (int x = 0; x <= N; ++x)
|
||||
{
|
||||
auto q = (to_world(x, y) - math::point{0.f, 0.f}) / (1.f * N);
|
||||
auto p = q + math::vector{0.5f, 0.5f};
|
||||
|
||||
(void)p;
|
||||
|
||||
// Canyon
|
||||
bed_(x, y) = std::max(0.f, 10.f * std::abs(2.f * noise(p) - 1.f) - 1.5f);
|
||||
|
||||
// Islands
|
||||
// bed_(x, y) = 5.f * math::smoothstep(math::clamp(math::unlerp({0.45f, 0.55f}, noise(p) - 1.f * math::length(q)), {0.f, 1.f}));
|
||||
|
||||
water_(x, y) = 0.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void water_2d_hex_app::update()
|
||||
{
|
||||
{
|
||||
float y_extent = (N / 2) * y_axis[1];
|
||||
view_area_[1] = {- y_extent, y_extent};
|
||||
view_area_[0] = {- y_extent * aspect_ratio_, y_extent * aspect_ratio_};
|
||||
}
|
||||
|
||||
if (state().mouse_button_down.contains(app::mouse_button::left))
|
||||
{
|
||||
auto m = math::lerp(view_area_, math::vector{state().mouse[0] * 1.f / screen_size_[0], 1.f - state().mouse[1] * 1.f / screen_size_[1]});
|
||||
auto p = to_grid(m);
|
||||
|
||||
int const R = 4;
|
||||
|
||||
for (int dy = -R; dy <= R; ++dy)
|
||||
{
|
||||
for (int dx = -R; dx <= R; ++dx)
|
||||
{
|
||||
if (dx + dy < -R || dx + dy > R) continue;
|
||||
|
||||
int mx = std::round(p[0]) + dx;
|
||||
int my = std::round(p[1]) + dy;
|
||||
|
||||
if (mx >= 0 && mx <= N && my >= 0 && my <= N)
|
||||
{
|
||||
auto c = to_world(mx, my);
|
||||
water_(mx, my) += 10.f * std::exp(- 0.25f * math::distance_sqr(c, m)) * dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paused_)
|
||||
return;
|
||||
|
||||
prof::profiler prof("update");
|
||||
|
||||
time_ += dt;
|
||||
|
||||
// Init boundary flows
|
||||
for (int i = 0; i <= N / 2; ++i)
|
||||
if (bed_(N / 2 - i, i) < 0.5f)
|
||||
flow_x_(N / 2 - i, i) = 300.f / N;
|
||||
// flow_x_(N / 2 - i, i) = 0*std::pow(std::sin(0.5f * time_) * std::sin(math::pi * (i * 4.f / N)), 5.f) * 300.f / N;
|
||||
|
||||
for (int i = 0; i <= N / 2; ++i)
|
||||
if (bed_(N - i, N / 2 + i) < 0.5f)
|
||||
flow_x_(N - i + 1, N / 2 + i) = 300.f / N;
|
||||
|
||||
// Update X flows
|
||||
for (int y = 0; y <= N; ++y)
|
||||
// for (int x = 1; x <= N; ++x)
|
||||
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
|
||||
flow_x_(x, y) = friction * flow_x_(x, y) + g * dt * (water_(x - 1, y) + bed_(x - 1, y) - water_(x, y) - bed_(x, y));
|
||||
|
||||
// Update Y flows
|
||||
for (int y = 1; y <= N; ++y)
|
||||
// for (int x = 0; x <= N; ++x)
|
||||
for (int x = std::max(0, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||
// if (x + y - 1 >= N / 2 && x + y <= 3 * N / 2)
|
||||
flow_y_(x, y) = friction * flow_y_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x, y) - bed_(x, y));
|
||||
|
||||
// Update Z flows
|
||||
for (int y = 1; y <= N; ++y)
|
||||
// for (int x = 1; x <= N; ++x)
|
||||
for (int x = std::max(1, N / 2 + 1 - y); x <= std::min(N, 3 * N / 2 + 1 - y); ++x)
|
||||
// if (x + y - 1 >= N / 2 && x + y - 1 <= 3 * N / 2)
|
||||
flow_z_(x, y) = friction * flow_z_(x, y) + g * dt * (water_(x, y - 1) + bed_(x, y - 1) - water_(x - 1, y) - bed_(x - 1, y));
|
||||
|
||||
// Scale flows
|
||||
for (int y = 0; y <= N; ++y)
|
||||
{
|
||||
// for (int x = 0; x <= N; ++x)
|
||||
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||
{
|
||||
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
|
||||
{
|
||||
float outflow = 0.f;
|
||||
|
||||
float & fin1 = flow_x_(x , y );
|
||||
float & fin2 = flow_y_(x , y );
|
||||
float & fin3 = flow_z_(x + 1, y );
|
||||
|
||||
float & fout1 = flow_x_(x + 1, y );
|
||||
float & fout2 = flow_y_(x , y + 1);
|
||||
float & fout3 = flow_z_(x , y + 1);
|
||||
|
||||
outflow += std::max(0.f, -fin1);
|
||||
outflow += std::max(0.f, -fin2);
|
||||
outflow += std::max(0.f, -fin3);
|
||||
outflow += std::max(0.f, fout1);
|
||||
outflow += std::max(0.f, fout2);
|
||||
outflow += std::max(0.f, fout3);
|
||||
|
||||
if (outflow > 0.f)
|
||||
{
|
||||
float max_outflow = water_(x, y) * dx * dx / dt;
|
||||
|
||||
float scale = std::min(1.f, max_outflow / outflow);
|
||||
|
||||
fin1 *= (fin1 < 0.f ? scale : 1.f);
|
||||
fin2 *= (fin2 < 0.f ? scale : 1.f);
|
||||
fin3 *= (fin3 < 0.f ? scale : 1.f);
|
||||
|
||||
fout1 *= (fout1 > 0.f ? scale : 1.f);
|
||||
fout2 *= (fout2 > 0.f ? scale : 1.f);
|
||||
fout3 *= (fout3 > 0.f ? scale : 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update water
|
||||
for (int y = 0; y <= N; ++y)
|
||||
// for (int x = 0; x <= N; ++x)
|
||||
for (int x = std::max(0, N / 2 - y); x <= std::min(N, 3 * N / 2 - y); ++x)
|
||||
// if (x + y >= N / 2 && x + y <= 3 * N / 2)
|
||||
water_(x, y) += dt / dx / dx * (flow_x_(x, y) + flow_y_(x, y) + flow_z_(x + 1,y) - flow_x_(x + 1, y) - flow_y_(x, y + 1) - flow_z_(x, y + 1));
|
||||
}
|
||||
|
||||
void water_2d_hex_app::present()
|
||||
{
|
||||
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
auto color = [this](int x, int y)
|
||||
{
|
||||
auto bed = gfx::color_4f{0.9f, 0.7f, 0.5f, - std::expm1(- bed_(x, y))};
|
||||
auto water = gfx::color_4f{0.0f, 0.25f, 1.f, - std::expm1(- water_(x, y))};
|
||||
|
||||
auto color = gfx::color_4f{0.f, 0.f, 0.f, 0.f};
|
||||
color = gfx::blend(color, bed);
|
||||
color = gfx::blend(color, water);
|
||||
return gfx::to_coloru8(color);
|
||||
};
|
||||
|
||||
auto p00 = to_world(x, y);
|
||||
auto p01 = to_world(x + 1, y);
|
||||
auto p10 = to_world(x, y + 1);
|
||||
auto p11 = to_world(x + 1, y + 1);
|
||||
|
||||
auto b00 = color(x, y);
|
||||
auto b01 = color(x + 1, y);
|
||||
auto b10 = color(x, y + 1);
|
||||
auto b11 = color(x + 1, y + 1);
|
||||
|
||||
if (x + y >= N / 2 && x + y < (3 * N) / 2)
|
||||
painter_.triangle(p00, p01, p10, b00, b01, b10);
|
||||
|
||||
if (x + y + 1 >= N / 2 && x + y + 1 < (3 * N) / 2)
|
||||
painter_.triangle(p10, p01, p11, b10, b01, b11);
|
||||
}
|
||||
}
|
||||
|
||||
if (show_velocity_)
|
||||
for (int y = 0; y <= N; ++y)
|
||||
{
|
||||
for (int x = 0; x <= N; ++x)
|
||||
{
|
||||
if (x + y >= N / 2 && x + y <= (3 * N) / 2)
|
||||
{
|
||||
auto p = to_world(x, y);
|
||||
|
||||
auto vx = (flow_x_(x, y) + flow_x_(x + 1, y)) * 0.5f * x_axis;
|
||||
auto vy = (flow_y_(x, y) + flow_y_(x, y + 1)) * 0.5f * y_axis;
|
||||
auto vz = (flow_z_(x + 1, y) + flow_z_(x, y + 1)) * 0.5f * z_axis;
|
||||
|
||||
auto v = (vx + vy + vz);
|
||||
|
||||
auto M = 1.f;
|
||||
auto l = math::length(v);
|
||||
float s = std::min(1.f, l / M);
|
||||
float c = -std::expm1(- 0.1f * l);
|
||||
|
||||
auto color = gfx::to_coloru8(gfx::color_4f{1.f, 1.f - c, 1.f - c, 1.f});
|
||||
|
||||
painter_.line(p, p + v * (s / l), 0.25f * s, 0.f, color, color, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
painter_.render(math::orthographic_camera{view_area_}.transform());
|
||||
}
|
||||
|
||||
void water_2d_hex_app::on_event(app::resize_event const & event)
|
||||
{
|
||||
gl::Viewport(0, 0, event.size[0], event.size[1]);
|
||||
screen_size_ = event.size;
|
||||
aspect_ratio_ = (event.size[0] * 1.f) / event.size[1];
|
||||
}
|
||||
|
||||
void water_2d_hex_app::on_event(app::key_event const & event)
|
||||
{
|
||||
if (event.down)
|
||||
{
|
||||
switch (event.key)
|
||||
{
|
||||
case app::keycode::V:
|
||||
vsync_on_ ^= true;
|
||||
set_vsync_(vsync_on_);
|
||||
break;
|
||||
case app::keycode::C:
|
||||
show_velocity_ ^= true;
|
||||
break;
|
||||
case app::keycode::SPACE:
|
||||
paused_ ^= true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void water_2d_hex_app::stop()
|
||||
{
|
||||
app::application_base::stop();
|
||||
prof::dump();
|
||||
}
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<water_2d_hex_app>({.name = "Water 2D hex example", .multisampling = 4});
|
||||
}
|
||||
|
||||
}
|
||||
881
examples/weather.cpp
Normal file
881
examples/weather.cpp
Normal file
|
|
@ -0,0 +1,881 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/default_application_factory.hpp>
|
||||
#include <psemek/gfx/painter.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/math/camera.hpp>
|
||||
#include <psemek/math/gradient.hpp>
|
||||
#include <psemek/random/generator.hpp>
|
||||
#include <psemek/random/device.hpp>
|
||||
#include <psemek/random/uniform_ball.hpp>
|
||||
#include <psemek/pcg/perlin.hpp>
|
||||
#include <psemek/pcg/fractal.hpp>
|
||||
#include <psemek/util/ndarray.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/io/file_stream.hpp>
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
auto make_perlin(random::generator & rng, int min_octave, int max_octave, float power)
|
||||
{
|
||||
std::vector<pcg::perlin<float, 2>> octaves;
|
||||
std::vector<float> weights;
|
||||
|
||||
random::uniform_sphere_vector_distribution<float, 2> random_vector{};
|
||||
|
||||
for (int octave = min_octave; octave < max_octave; ++octave)
|
||||
{
|
||||
int size = 1 << octave;
|
||||
util::ndarray<math::vector<float, 2>, 2> gradients({size + 1, size + 1});
|
||||
for (auto & g : gradients)
|
||||
g = random_vector(rng);
|
||||
octaves.emplace_back(std::move(gradients));
|
||||
weights.push_back(std::pow(power, - octave));
|
||||
}
|
||||
|
||||
float weight_sum = 0.f;
|
||||
for (auto w : weights)
|
||||
weight_sum += w;
|
||||
for (auto & w : weights)
|
||||
w /= weight_sum;
|
||||
|
||||
return pcg::fractal<pcg::perlin<float, 2>>(std::move(octaves), std::move(weights));
|
||||
}
|
||||
|
||||
void make_force_field(random::generator & rng, util::ndarray<math::vector<float, 2>, 2> & result, float scale)
|
||||
{
|
||||
auto noise_1 = make_perlin(rng, 4, 6, 2.f);
|
||||
auto noise_2 = make_perlin(rng, 4, 6, 2.f);
|
||||
|
||||
for (int y = 0; y < result.height(); ++y)
|
||||
{
|
||||
for (int x = 0; x < result.width(); ++x)
|
||||
{
|
||||
math::point p{(x + 0.5f) / result.height(), (y + 0.5f) / result.width()};
|
||||
result(x, y) = scale * (math::vector{noise_1(p), noise_2(p)} * 2.f - math::vector{1.f, 1.f});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct weather_app
|
||||
: app::application_base
|
||||
{
|
||||
static constexpr int N = 128;
|
||||
|
||||
const bool static_mode = false;
|
||||
|
||||
const float dt = 20.f;
|
||||
const float viscosity = static_mode ? 0.01f : 0.f;
|
||||
const bool temperature_advection = true;
|
||||
const float advection_magnification = 1.f;
|
||||
const float temperature_diffusion = 0.0004f;
|
||||
const float cooling = 0.01f / 300.f;
|
||||
const float cooling_factor = std::exp(- cooling * dt);
|
||||
const float heating = 323.f * (std::exp(cooling * dt) - 1.f) / dt;
|
||||
const float water_heating_factor = 0.9f;
|
||||
const float coriolis = 0.001f;
|
||||
const float coriolis_bands = 6.f;
|
||||
const float band_force = 0.00001f;
|
||||
const float friction = 0.f;
|
||||
const float slope_friction = 1.f;
|
||||
const float slope_force = 0.001f;
|
||||
const float land_force = 0.05f;
|
||||
const float buoyancy_factor = 0.0002f * 0;
|
||||
const float vorticity_confinement = 0.f;
|
||||
const float elevation_temperature_drop = 30.f;
|
||||
const float evaporation = 1.0f;
|
||||
const float max_humidity_factor = 1.f;
|
||||
const float precipitation_factor = 0.0001f;
|
||||
const float force_field_amplitude = 0.00005f;
|
||||
const float random_forces = 0.25f * (static_mode ? 0.f : 1.f);
|
||||
const float force_field_switch_duration = 720.f * 7.5f; // 7.5 days
|
||||
const int force_field_switch_frames = std::round(force_field_switch_duration / dt);
|
||||
// const float friction_factor = 1.f - std::exp(- friction * dt);
|
||||
|
||||
const bool periodic_x = true;
|
||||
|
||||
// random::generator rng{random::device{}};
|
||||
random::generator rng{0, 0};
|
||||
|
||||
gfx::pixmap_rgba biomes_map;
|
||||
|
||||
float expected_temperature_at(int y, bool water) const
|
||||
{
|
||||
// float latitude = (y - N * 0.5f) * 2.f / N;
|
||||
// return std::cos(latitude * float(math::pi));
|
||||
|
||||
return temperature_income_at(y) * (water ? water_heating_factor : 1.f) * dt / (std::exp(cooling * dt) - 1.f);
|
||||
}
|
||||
|
||||
float temperature_income_at(int y) const
|
||||
{
|
||||
// float latitude = (y - N * 0.5f) * 2.f / N;
|
||||
float latitude = y * 1.f / N;
|
||||
return heating * math::lerp(0.75f, 1.f, std::cos(latitude * float(math::pi) / 2.f));
|
||||
// return heating * math::lerp(0.8f, 1.f, 1.f - std::abs(latitude));
|
||||
}
|
||||
|
||||
int wrap(int i) const
|
||||
{
|
||||
return (i + N) % N;
|
||||
}
|
||||
|
||||
weather_app(options const &, context const &)
|
||||
{
|
||||
simulation_box_ = {{{0.f, N}, {0.f, N}}};
|
||||
|
||||
terrain_.resize({N, N}, 0.f);
|
||||
velocity_.resize({N, N});
|
||||
new_velocity_.resize({N, N});
|
||||
pressure_.resize({N, N}, 0.f);
|
||||
temperature_.resize({N, N}, -1.f);
|
||||
new_temperature_.resize({N, N});
|
||||
average_temperature_.resize({N, N}, 0.f);
|
||||
force_field_main_.resize({N, N});
|
||||
force_field_current_.resize({N, N});
|
||||
force_field_next_.resize({N, N});
|
||||
vorticity_.resize({N, N});
|
||||
humidity_.resize({N, N});
|
||||
new_humidity_.resize({N, N});
|
||||
precipitation_.resize({N, N});
|
||||
average_precipitation_.resize({N, N});
|
||||
|
||||
|
||||
auto terrain_noise = make_perlin(rng, 2, 10, 1.6f);
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
auto d = math::length(math::vector{x - N / 2.f, y - N / 2.f}) / (N / 2.f);
|
||||
(void)d;
|
||||
float value = terrain_noise((x + 0.5f) / N, (y + 0.5f) / N);
|
||||
value = pow(value, 4.f) - d / 4.f;
|
||||
value = math::lerp(1.f, 16.f, value);
|
||||
terrain_(x, y) = value;
|
||||
}
|
||||
}
|
||||
|
||||
auto heightmap = gfx::read_image<std::uint8_t>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "heightmap_seed_1.png"});
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
terrain_(x, y) = ((heightmap(x, y) / 255.f) * 2048.f - 512.f) / 1024.f;
|
||||
}
|
||||
}
|
||||
|
||||
random::uniform_ball_vector_distribution<float, 2> random_velocity{};
|
||||
|
||||
// for (auto & v : velocity_)
|
||||
// {
|
||||
// v = random_velocity(rng) * 0.01f;
|
||||
// v += std::cos(0.5f * float(math::pi) * latitude * coriolis_bands
|
||||
// }
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
float latitude = (N * 0.5f - y) * 2.f / N;
|
||||
velocity_(x, y) = random_velocity(rng) * 0.f + 0.f * math::vector{-std::cos(0.5f * float(math::pi) * latitude * coriolis_bands), 0.f};
|
||||
temperature_(x, y) = expected_temperature_at(y, terrain_(x, y) <= 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
if (terrain_(x, y) > 0.f)
|
||||
continue;
|
||||
|
||||
float max_humidity = std::max(0.f, temperature_(x, y) - 223.f) * max_humidity_factor;
|
||||
humidity_(x, y) = max_humidity + dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f) * (1.f - precipitation_factor * dt) / precipitation_factor / dt;
|
||||
humidity_(x, y) = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
make_force_field(rng, force_field_main_, 0.5f);
|
||||
make_force_field(rng, force_field_next_, 0.5f);
|
||||
|
||||
biomes_map = gfx::read_image<gfx::color_rgba>(io::file_istream{std::filesystem::path{PSEMEK_EXAMPLES_DIR} / "biomes.png"});
|
||||
}
|
||||
|
||||
void on_event(app::key_event const & event) override
|
||||
{
|
||||
if (event.down && event.key == app::keycode::SPACE)
|
||||
paused_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::V)
|
||||
show_velocity_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::T)
|
||||
show_temperature_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::D)
|
||||
show_temperature_delta_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::A)
|
||||
show_average_temperature_delta_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::P)
|
||||
show_pressure_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::H)
|
||||
show_land_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::W)
|
||||
show_water_vapor_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::R)
|
||||
show_precipitation_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::Q)
|
||||
show_average_precipitation_ ^= true;
|
||||
|
||||
if (event.down && event.key == app::keycode::B)
|
||||
show_biomes_ ^= true;
|
||||
}
|
||||
|
||||
void update() override
|
||||
{
|
||||
if (paused_)
|
||||
return;
|
||||
|
||||
// Update force field
|
||||
if ((frame_ % force_field_switch_frames) == 0)
|
||||
{
|
||||
std::swap(force_field_current_, force_field_next_);
|
||||
make_force_field(rng, force_field_next_, 0.5f);
|
||||
}
|
||||
[[maybe_unused]] float const force_field_t = ((frame_ % force_field_switch_frames) + 0.5f) / force_field_switch_frames;
|
||||
|
||||
int xmin = periodic_x ? 0 : 1;
|
||||
int xmax = periodic_x ? N : N - 1; // exclusive
|
||||
|
||||
// Temperature source
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
bool const is_water = terrain_(x, y) <= 0.f;
|
||||
// temperature_(x, y) = math::lerp(temperature_(x, y), expected_temperature_at(y), 1.f - std::exp(- heating * dt));
|
||||
temperature_(x, y) += dt * temperature_income_at(y) * (is_water ? water_heating_factor : 1.f);
|
||||
temperature_(x, y) *= cooling_factor;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaporation
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
if (terrain_(x, y) <= 0.f)
|
||||
humidity_(x, y) += dt * evaporation * std::max(0.f, temperature_(x, y) - 273.f);
|
||||
|
||||
// float discharge = std::min(humidity_(x, y), precipitation_factor * dt);
|
||||
// float discharge = humidity_(x, y) * precipitation_factor * dt;
|
||||
// float max_humidity = temperature_(x, y) * max_humidity_factor;
|
||||
float max_humidity = temperature_(x, y) * temperature_(x, y) * max_humidity_factor;
|
||||
float discharge = std::max(0.f, humidity_(x, y) - max_humidity) * precipitation_factor * dt;
|
||||
humidity_(x, y) -= discharge;
|
||||
precipitation_(x, y) = discharge / dt;
|
||||
}
|
||||
}
|
||||
|
||||
// Velocity & temperature advection
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
new_temperature_(i, 0) = temperature_(i, 0);
|
||||
new_temperature_(i, N - 1) = temperature_(i, N - 1);
|
||||
|
||||
new_humidity_(i, 0) = humidity_(i, 0);
|
||||
new_humidity_(i, N - 1) = humidity_(i, N - 1);
|
||||
|
||||
if (!periodic_x)
|
||||
{
|
||||
new_temperature_(0, i) = temperature_(0, i);
|
||||
new_temperature_(N - 1, i) = temperature_(N - 1, i);
|
||||
|
||||
new_humidity_(0, i) = humidity_(0, i);
|
||||
new_humidity_(N - 1, i) = humidity_(N - 1, i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 1; y < N - 1; ++y)
|
||||
{
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
{
|
||||
auto v = velocity_(x, y);
|
||||
auto p = math::point{x + 0.5f, y + 0.5f} - (advection_magnification * dt) * v;
|
||||
p[0] = p[0] - 0.5f;
|
||||
p[1] = math::clamp(p[1] - 0.5f, {0.f, N - 1.f});
|
||||
|
||||
if (!periodic_x)
|
||||
p[0] = math::clamp(p[0], {0.f, N - 1.f});
|
||||
|
||||
int ix = std::floor(p[0]);
|
||||
int iy = std::min<int>(N - 1, std::floor(p[1]));
|
||||
|
||||
if (!periodic_x)
|
||||
ix = std::min(N - 1, ix);
|
||||
|
||||
float tx = p[0] - ix;
|
||||
float ty = p[1] - iy;
|
||||
|
||||
new_velocity_(x, y) = math::lerp(
|
||||
math::lerp(velocity_(wrap(ix + 0), iy + 0), velocity_(wrap(ix + 1), iy + 0), tx),
|
||||
math::lerp(velocity_(wrap(ix + 0), iy + 1), velocity_(wrap(ix + 1), iy + 1), tx),
|
||||
ty
|
||||
);
|
||||
|
||||
new_temperature_(x, y) = math::lerp(
|
||||
math::lerp(temperature_(wrap(ix + 0), iy + 0), temperature_(wrap(ix + 1), iy + 0), tx),
|
||||
math::lerp(temperature_(wrap(ix + 0), iy + 1), temperature_(wrap(ix + 1), iy + 1), tx),
|
||||
ty
|
||||
);
|
||||
|
||||
new_humidity_(x, y) = math::lerp(
|
||||
math::lerp(humidity_(wrap(ix + 0), iy + 0), humidity_(wrap(ix + 1), iy + 0), tx),
|
||||
math::lerp(humidity_(wrap(ix + 0), iy + 1), humidity_(wrap(ix + 1), iy + 1), tx),
|
||||
ty
|
||||
);
|
||||
}
|
||||
}
|
||||
std::swap(velocity_, new_velocity_);
|
||||
if (temperature_advection) std::swap(temperature_, new_temperature_);
|
||||
std::swap(humidity_, new_humidity_);
|
||||
|
||||
// Apply velocity diffusion
|
||||
for (int y = 0; y < N; ++y)
|
||||
for (int x = 0; x < N; ++x)
|
||||
new_velocity_(x, y) = velocity_(x, y);
|
||||
for (int y = 1; y < N - 1; ++y)
|
||||
{
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
{
|
||||
// Velocity Laplacian
|
||||
auto laplacian = velocity_(wrap(x + 1), y) + velocity_(wrap(x - 1), y) + velocity_(x, y + 1) + velocity_(x, y - 1) - 4.f * velocity_(x, y);
|
||||
new_velocity_(x, y) = velocity_(x, y) + viscosity * dt * laplacian;
|
||||
}
|
||||
}
|
||||
std::swap(velocity_, new_velocity_);
|
||||
|
||||
// Apply temperature diffusion
|
||||
for (int y = 0; y < N; ++y)
|
||||
for (int x = 0; x < N; ++x)
|
||||
new_temperature_(x, y) = temperature_(x, y);
|
||||
for (int y = 1; y < N - 1; ++y)
|
||||
{
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
{
|
||||
// Temperature Laplacian
|
||||
auto laplacian = temperature_(wrap(x + 1), y) + temperature_(wrap(x - 1), y) + temperature_(x, y + 1) + temperature_(x, y - 1) - 4.f * temperature_(x, y);
|
||||
new_temperature_(x, y) = temperature_(x, y) + temperature_diffusion * dt * laplacian;
|
||||
}
|
||||
}
|
||||
std::swap(temperature_, new_temperature_);
|
||||
|
||||
// Compute vorticity
|
||||
for (int y = 1; y < N - 1; ++y)
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
vorticity_(x, y) = (velocity_(x, y + 1)[0] - velocity_(x, y - 1)[0]) / 2.f - (velocity_(wrap(x + 1), y)[1] - velocity_(wrap(x - 1), y)[1]) / 2;
|
||||
|
||||
// Apply forces & friction
|
||||
for (int y = 1; y < N - 1; ++y)
|
||||
{
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
{
|
||||
// float latitude = (N * 0.5f - y) * 2.f / N;
|
||||
[[maybe_unused]] float latitude = (N - y) * 1.f / N;
|
||||
// velocity_(x, y) += math::ort(velocity_(x, y)) * (coriolis * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands));
|
||||
velocity_(x, y) = math::rotate(velocity_(x, y), coriolis * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands));
|
||||
|
||||
// auto force = force_field_main_(x, y) + random_forces * math::lerp(force_field_current_(x, y), force_field_next_(x, y), force_field_t);
|
||||
// velocity_(x, y) += (dt * force_field_amplitude) * force;
|
||||
|
||||
// velocity_(x, y)[0] += dt * 0.000001f * std::cos(0.5f * float(math::pi) * latitude * 4.f);
|
||||
// velocity_(x, y)[0] += dt * 0.000001f;
|
||||
|
||||
velocity_(x, y)[0] += dt * band_force * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
|
||||
|
||||
[[maybe_unused]] math::vector terrain_gradient
|
||||
{
|
||||
(std::max(0.f, terrain_(x + 1, y)) - std::max(0.f, terrain_(x - 1, y))) / 2.f,
|
||||
(std::max(0.f, terrain_(x, y + 1)) - std::max(0.f, terrain_(x, y - 1))) / 2.f,
|
||||
};
|
||||
|
||||
[[maybe_unused]] math::vector temperature_gradient
|
||||
{
|
||||
(temperature_(x + 1, y) - temperature_(x - 1, y)) / 2.f,
|
||||
(temperature_(x, y + 1) - temperature_(x, y - 1)) / 2.f,
|
||||
};
|
||||
|
||||
velocity_(x, y) += temperature_gradient * buoyancy_factor * dt;
|
||||
|
||||
[[maybe_unused]] float slope_factor = std::exp(- dt * slope_friction * math::dot(math::normalized(velocity_(x, y)), terrain_gradient));
|
||||
velocity_(x, y) *= std::min(1.f, slope_factor);
|
||||
// velocity_(x, y) -= terrain_gradient * slope_force * dt;
|
||||
|
||||
// [[maybe_unused]] float slope_factor = std::exp(- dt * slope_force * std::pow(math::length(terrain_gradient), 4.f));
|
||||
[[maybe_unused]] float land_factor = std::exp(- dt * land_force * std::pow(std::max(0.f, terrain_(x, y)), 1.f));
|
||||
velocity_(x, y) *= land_factor;
|
||||
|
||||
// Directional external force
|
||||
// velocity_(x, y)[1] += 0.001f * dt * std::sin(0.5f * float(math::pi) * latitude * coriolis_bands);
|
||||
// velocity_(x, y) += math::direction(frame_ * dt * 2.f * float(math::pi) / 10080.f) * 0.001f * dt;
|
||||
|
||||
math::vector vorticity_gradient
|
||||
{
|
||||
(vorticity_(wrap(x + 1), y) - vorticity_(wrap(x - 1), y)) / 2.f,
|
||||
(vorticity_(x, y + 1) - vorticity_(x, y - 1)) / 2.f,
|
||||
};
|
||||
if (auto l = math::length(vorticity_gradient); l > 0.f)
|
||||
vorticity_gradient /= l;
|
||||
|
||||
velocity_(x, y) += vorticity_confinement * dt * vorticity_(x, y) * math::ort(vorticity_gradient);
|
||||
|
||||
float local_friction = friction * terrain_(x, y);
|
||||
// velocity_(x, y) -= local_friction * velocity_(x, y) * math::length(velocity_(x, y));
|
||||
[[maybe_unused]] float local_friction_factor = std::exp(- local_friction * dt);
|
||||
// velocity_(x, y) *= local_friction_factor;
|
||||
}
|
||||
}
|
||||
|
||||
// Solve Poisson equation for pressure
|
||||
for (int iteration = 0; iteration < 16; ++iteration)
|
||||
{
|
||||
int ymin = ((iteration % 2) == 0) ? 1 : N - 2;
|
||||
int ymax = ((iteration % 2) == 0) ? N - 1 : 0;
|
||||
int ystep = ((iteration % 2) == 0) ? 1 : -1;
|
||||
|
||||
for (int y = ymin; y != ymax; y += ystep)
|
||||
{
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
{
|
||||
// Velocity divergence
|
||||
float divergence = (velocity_(wrap(x + 1), y)[0] - velocity_(wrap(x - 1), y)[0] + velocity_(x, y + 1)[1] - velocity_(x, y - 1)[1]) / 2.f;
|
||||
|
||||
// Gauss-Seidel iteration step
|
||||
pressure_(x, y) = (pressure_(wrap(x - 1), y) + pressure_(wrap(x + 1), y) + pressure_(x, y - 1) + pressure_(x, y + 1) - divergence) / 4.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply boundary conditions for pressure
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
if (!periodic_x)
|
||||
{
|
||||
pressure_(0, i) = pressure_(1, i);
|
||||
pressure_(N - 1, i) = pressure_(N - 2, i);
|
||||
}
|
||||
pressure_(i, 0) = pressure_(i, 1);
|
||||
pressure_(i, N - 1) = pressure_(i, N - 2);
|
||||
}
|
||||
if (!periodic_x)
|
||||
{
|
||||
pressure_(0, 0) = (pressure_(0, 1) + pressure_(1, 0)) / 2.f;
|
||||
pressure_(N-1, 0) = (pressure_(N-1, 1) + pressure_(N-2, 0)) / 2.f;
|
||||
pressure_(0, N-1) = (pressure_(0, N-2) + pressure_(1, N-2)) / 2.f;
|
||||
pressure_(N-1, N-1) = (pressure_(N-1, N-2) + pressure_(N-2, N-1)) / 2.f;
|
||||
}
|
||||
|
||||
// Normalize pressure
|
||||
float average_pressure = 0.f;
|
||||
for (auto const & value : pressure_)
|
||||
average_pressure += value;
|
||||
average_pressure /= (1.f * N * N);
|
||||
for (auto & value : pressure_)
|
||||
value -= average_pressure;
|
||||
|
||||
// Project velocity into divergence-free space
|
||||
// by subtracting pressure gradient
|
||||
for (int y = 1; y < N - 1; ++y)
|
||||
{
|
||||
for (int x = xmin; x < xmax; ++x)
|
||||
{
|
||||
// Pressure gradient
|
||||
math::vector gradient{
|
||||
(pressure_(wrap(x + 1), y) - pressure_(wrap(x - 1), y)) / 2.f,
|
||||
(pressure_(x, y + 1) - pressure_(x, y - 1)) / 2.f
|
||||
};
|
||||
velocity_(x, y) -= gradient;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply boundary conditions for velocity
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
if (!periodic_x)
|
||||
{
|
||||
float left_boundary_flow = 0.f;//0.01f * std::sin((i * 1.f / N) * float(math::pi) * 4.f);
|
||||
float right_boundary_flow = -left_boundary_flow;
|
||||
|
||||
velocity_(1, i)[0] = left_boundary_flow;
|
||||
velocity_(N-2, i)[0] = right_boundary_flow;
|
||||
|
||||
velocity_(0, i)[0] = - velocity_(1, i)[0];
|
||||
velocity_(N-1, i)[0] = - velocity_(N-2, i)[0];
|
||||
}
|
||||
velocity_(i, 0)[1] = -velocity_(i, 1)[1];
|
||||
velocity_(i, N-2)[1] = -velocity_(i, N-2)[1];
|
||||
|
||||
// velocity_(i, 1)[0] = 0.01f;
|
||||
// velocity_(i, N-2)[0] = 0.01f;
|
||||
}
|
||||
|
||||
// Uncomment to visualize the force field
|
||||
// for (int y = 0; y < N; ++y)
|
||||
// for (int x = 0; x < N; ++x)
|
||||
// velocity_(x, y) = 100000.f * force_field_(x, y);
|
||||
|
||||
// Uncomment to visualize the terrain gradient field
|
||||
// for (int y = 1; y < N - 1; ++y)
|
||||
// {
|
||||
// for (int x = 1; x < N - 1; ++x)
|
||||
// {
|
||||
// math::vector terrain_gradient
|
||||
// {
|
||||
// (terrain_(x + 1, y) - terrain_(x - 1, y)) / 2.f,
|
||||
// (terrain_(x, y + 1) - terrain_(x, y - 1)) / 2.f,
|
||||
// };
|
||||
|
||||
// velocity_(x, y) = terrain_gradient;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Apply boundary conditions for humidity
|
||||
for (int i = 0; i < N; ++i)
|
||||
{
|
||||
if (!periodic_x)
|
||||
{
|
||||
humidity_(0, i) = 0.f;
|
||||
humidity_(N - 1, i) = 0.f;
|
||||
}
|
||||
humidity_(i, 0) = 0.f;
|
||||
humidity_(i, N - 1) = 0.f;
|
||||
}
|
||||
|
||||
++frame_;
|
||||
|
||||
// Update all-time average temperature & precipitation
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
// float t = 1.f / frame_;
|
||||
// float t = 1.f / std::min(8192, frame_);
|
||||
float t = 1.f / std::min<float>(86400.f / dt, frame_); // year average
|
||||
|
||||
if (static_mode)
|
||||
t = 1.f;
|
||||
|
||||
average_temperature_(x, y) = math::lerp(average_temperature_(x, y), temperature_(x, y), t);
|
||||
average_precipitation_(x, y) = math::lerp(average_precipitation_(x, y), precipitation_(x, y), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void present() override
|
||||
{
|
||||
gl::ClearColor(0.f, 0.f, 0.f, 0.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
|
||||
float const aspect_ratio = state().size[0] * 1.f / state().size[1];
|
||||
math::box<float, 2> view_box = math::expand(simulation_box_, 1.f);
|
||||
if (view_box[0].length() / view_box[1].length() > aspect_ratio)
|
||||
view_box[1] = math::expand(view_box[1], (view_box[0].length() / aspect_ratio - view_box[1].length()) / 2.f);
|
||||
else
|
||||
view_box[0] = math::expand(view_box[0], (view_box[1].length() * aspect_ratio - view_box[0].length()) / 2.f);
|
||||
|
||||
std::optional<math::vector<int, 2>> mouseover_cell;
|
||||
{
|
||||
auto mouse = math::lerp(view_box, math::vector{state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]});
|
||||
int x = std::floor(mouse[0]);
|
||||
int y = std::floor(mouse[1]);
|
||||
|
||||
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||
mouseover_cell = {x, y};
|
||||
}
|
||||
|
||||
if (mouseover_cell && (state().mouse_button_down.contains(app::mouse_button::left) || state().mouse_button_down.contains(app::mouse_button::right)))
|
||||
{
|
||||
float delta = state().mouse_button_down.contains(app::mouse_button::left) ? 1.f : -1.f;
|
||||
|
||||
int R = 4;
|
||||
|
||||
for (int iy = -R; iy <= R; ++iy)
|
||||
{
|
||||
for (int ix = -R; ix <= R; ++ix)
|
||||
{
|
||||
int x = (*mouseover_cell)[0] + ix;
|
||||
int y = (*mouseover_cell)[1] + iy;
|
||||
|
||||
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||
{
|
||||
auto d = math::vector<float, 2>{ix, iy} / 2.f;
|
||||
terrain_(x, y) += delta * 0.05f * std::exp(- math::dot(d, d));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mouseover_cell && state().mouse_button_down.contains(app::mouse_button::middle))
|
||||
{
|
||||
int R = 4;
|
||||
|
||||
float average = 0.f;
|
||||
int count = 0;
|
||||
|
||||
for (int iy = -R; iy <= R; ++iy)
|
||||
{
|
||||
for (int ix = -R; ix <= R; ++ix)
|
||||
{
|
||||
int x = (*mouseover_cell)[0] + ix;
|
||||
int y = (*mouseover_cell)[1] + iy;
|
||||
|
||||
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||
{
|
||||
average += terrain_(x, y);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
average /= count;
|
||||
|
||||
for (int iy = -R; iy <= R; ++iy)
|
||||
{
|
||||
for (int ix = -R; ix <= R; ++ix)
|
||||
{
|
||||
int x = (*mouseover_cell)[0] + ix;
|
||||
int y = (*mouseover_cell)[1] + iy;
|
||||
|
||||
if (x >= 0 && x < N && y >= 0 && y < N)
|
||||
{
|
||||
auto d = math::vector<float, 2>{ix, iy} / 2.f;
|
||||
terrain_(x, y) += (average - terrain_(x, y)) * 0.05f * std::exp(- math::dot(d, d));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] float const pixel_size = view_box[0].length() / state().size[0];
|
||||
|
||||
auto map_color = [](float value, gfx::color_4f const & negative, gfx::color_4f const & positive){
|
||||
return math::lerp(negative, positive, 1.f/ (1.f + std::exp(- value)));
|
||||
};
|
||||
|
||||
auto map_temperature = [&](float value) {
|
||||
return map_color(2.f * std::round(value / 20.f), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f});
|
||||
};
|
||||
|
||||
auto map_biome = [this](float temperature, float precipitation)
|
||||
{
|
||||
auto x = math::clamp<int>(math::unlerp({ -3.f, 5.f}, precipitation) * biomes_map.width() , {0, biomes_map.width() - 1});
|
||||
auto y = math::clamp<int>(math::unlerp({-10.f, 30.f}, temperature ) * biomes_map.height(), {0, biomes_map.height() - 1});
|
||||
|
||||
return gfx::to_colorf(biomes_map(x, y));
|
||||
};
|
||||
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
gfx::color_4f color = gfx::color_4f::zero();
|
||||
|
||||
if (show_land_ || show_biomes_)
|
||||
{
|
||||
if (!show_biomes_)
|
||||
{
|
||||
if (terrain_(x, y) <= 0.f)
|
||||
color = {0.5f, 0.5f, 1.f, 1.f};
|
||||
else
|
||||
color = {1.f, 1.f, 1.f, 1.f};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (terrain_(x, y) <= 0.f)
|
||||
color = map_color(8.f * terrain_(x, y), {0.f, 0.f, 0.125f, 1.f}, {0.f, 1.f, 1.5f, 1.f});
|
||||
else
|
||||
{
|
||||
float temperature = average_temperature_(x, y) - 273.f - std::max(0.f, terrain_(x, y)) * elevation_temperature_drop;
|
||||
// float precipitation = (std::log10(std::max(1e-9f, average_precipitation_(x, y))) + 3.f) * 2.f;
|
||||
// float precipitation = std::pow(average_precipitation_(x, y) * 125.f, 2.f) * 8.f;
|
||||
float precipitation = std::log2(std::max(1e-9f, average_precipitation_(x, y)));
|
||||
color = map_biome(temperature, precipitation);
|
||||
}
|
||||
}
|
||||
|
||||
if (show_land_ && x > 0 && x + 1 < N && y > 0 && y + 1 < N)
|
||||
{
|
||||
math::vector terrain_gradient
|
||||
{
|
||||
(terrain_(x + 1, y) - terrain_(x - 1, y)) / 2.f,
|
||||
(terrain_(x, y + 1) - terrain_(x, y - 1)) / 2.f,
|
||||
};
|
||||
|
||||
auto terrain_normal = math::normalized(math::vector{-terrain_gradient[0], -terrain_gradient[1], 0.125f});
|
||||
|
||||
float lightness = 0.5f + 0.5f * math::dot(terrain_normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
|
||||
|
||||
color = gfx::dark(color, 1.f - lightness);
|
||||
}
|
||||
}
|
||||
|
||||
bool const is_water = terrain_(x, y) <= 0.f;
|
||||
|
||||
if (show_temperature_)
|
||||
color = map_temperature(temperature_(x, y) - 273.f);
|
||||
|
||||
if (show_temperature_delta_)
|
||||
color = gfx::blend(color, map_color((temperature_(x, y) - expected_temperature_at(y, is_water)), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f}));
|
||||
|
||||
if (show_average_temperature_delta_)
|
||||
color = gfx::blend(color, map_color((average_temperature_(x, y) - expected_temperature_at(y, is_water)), {0.125f, 0.5f, 1.f, 0.75f}, {1.f, 0.5f, 0.125f, 0.75f}));
|
||||
|
||||
if (show_pressure_)
|
||||
color = gfx::blend(color, map_color(10000.f * pressure_(x, y), {0.f, 0.f, 1.f, 0.75f}, {1.f, 0.f, 0.f, 0.75f}));
|
||||
|
||||
if (show_water_vapor_)
|
||||
color = gfx::blend(color, map_color(humidity_(x, y) * 0.00001f, {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
|
||||
|
||||
if (show_precipitation_)
|
||||
{
|
||||
// color = gfx::blend(color, map_color(precipitation_(x, y), {1.f, 1.f, 1.f, -1.f}, {1.f, 1.f, 1.f, 1.f}));
|
||||
|
||||
float alpha = 2.f / (1.f + std::exp(- 0.1f * precipitation_(x, y))) - 1.f;
|
||||
|
||||
gfx::color_4f cloud_color{1.f, 1.f, 1.f, alpha * 0.875f};
|
||||
|
||||
if (y > 0 && y + 1 < N)
|
||||
{
|
||||
if (periodic_x || (x > 0 && x + 1 < N))
|
||||
{
|
||||
math::vector gradient
|
||||
{
|
||||
(precipitation_(wrap(x + 1), y) - precipitation_(wrap(x - 1), y)) / 2.f,
|
||||
(precipitation_(x, y + 1) - precipitation_(x, y - 1)) / 2.f,
|
||||
};
|
||||
|
||||
auto normal = math::normalized(math::vector{-gradient[0], -gradient[1], 2.f});
|
||||
auto lightness = 0.5f + 0.5f * math::dot(normal, math::normalized(math::vector{1.f, 2.f, 3.f}));
|
||||
cloud_color = gfx::dark(cloud_color, 1.f - lightness);
|
||||
}
|
||||
}
|
||||
|
||||
color = gfx::blend(color, cloud_color);
|
||||
}
|
||||
|
||||
if (show_average_precipitation_)
|
||||
color = gfx::blend(color, map_color(average_precipitation_(x, y), {0.f, 1.f, 1.f, -0.75f}, {0.f, 1.f, 1.f, 0.75f}));
|
||||
|
||||
painter_.rect({{{x, x + 1.f}, {y, y + 1.f}}}, gfx::to_coloru8(color));
|
||||
}
|
||||
}
|
||||
|
||||
if (show_velocity_)
|
||||
{
|
||||
for (int y = 0; y < N; ++y)
|
||||
{
|
||||
for (int x = 0; x < N; ++x)
|
||||
{
|
||||
math::point center{x + 0.5f, y + 0.5f};
|
||||
auto v = velocity_(x, y);
|
||||
auto color = gfx::color_4f::zero();
|
||||
if (auto l = math::length(v); l > 0.f)
|
||||
{
|
||||
float const magnification = 200.f;
|
||||
float const max_length = 1.5f;
|
||||
v *= 0.5f * max_length * (1.f - std::exp(- magnification * l)) / l;
|
||||
|
||||
// color = gfx::lerp(gfx::color_4f{0.5f, 1.f, 0.f, 1.f}, gfx::color_4f{1.f, 0.f, 0.f, 1.f}, 1.f - std::exp(- 0.25f * magnification * l));
|
||||
color = map_color(0.1f * (temperature_(x, y) - 273.f), {0.125f, 0.5f, 1.f, 1.f}, {1.f, 0.5f, 0.125f, 1.f});
|
||||
}
|
||||
auto n = math::ort(v) * 0.3f;
|
||||
|
||||
painter_.triangle(center - v - n, center - v + n, center + v, gfx::to_coloru8(color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto push_text = [&, row = 0](std::string const & text) mutable
|
||||
{
|
||||
painter_.text(view_box.corner(0.f, 1.f) - math::vector{0.f, row * pixel_size * 2.f * 12.f}, text, {.scale = {2.f * pixel_size, - 2.f * pixel_size}, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
|
||||
++row;
|
||||
};
|
||||
|
||||
push_text(std::format("Frame {}", frame_));
|
||||
push_text(std::format("Day {:.2f}", frame_ / (720.f / dt)));
|
||||
|
||||
if (mouseover_cell)
|
||||
{
|
||||
int x = (*mouseover_cell)[0];
|
||||
int y = (*mouseover_cell)[1];
|
||||
painter_.rect({{{x, x + 1.f}, {y, y + 1.f}}}, {255, 255, 255, 127});
|
||||
|
||||
push_text(std::format("{} {}", x, y));
|
||||
push_text(std::format("V = {:.3f} {:.3f}", velocity_(x, y)[0] * 1000.f, velocity_(x, y)[1] * 1000.f));
|
||||
push_text(std::format("P = {:.3f}", pressure_(x, y) * 1000.f));
|
||||
push_text(std::format("T = {:.3f}", temperature_(x, y) - 273.f));
|
||||
push_text(std::format("A = {:.3f}", average_temperature_(x, y) - 273.f));
|
||||
push_text(std::format("E = {:.3f}", expected_temperature_at(y, terrain_(x, y) <= 0.f) - 273.f));
|
||||
push_text(std::format("H = {:.3f}", terrain_(x, y)));
|
||||
push_text(std::format("W = {:.3f}", humidity_(x, y)));
|
||||
push_text(std::format("R = {:.3f}", precipitation_(x, y)));
|
||||
push_text(std::format("AR= {:.3f}", average_precipitation_(x, y)));
|
||||
}
|
||||
|
||||
painter_.render(math::orthographic_camera{view_box}.transform());
|
||||
}
|
||||
|
||||
private:
|
||||
gfx::painter painter_;
|
||||
|
||||
math::box<float, 2> simulation_box_;
|
||||
|
||||
bool paused_ = true;
|
||||
bool show_velocity_ = true;
|
||||
bool show_temperature_ = false;
|
||||
bool show_temperature_delta_ = false;
|
||||
bool show_average_temperature_delta_ = false;
|
||||
bool show_pressure_ = false;
|
||||
bool show_land_ = true;
|
||||
bool show_biomes_ = true;
|
||||
bool show_water_vapor_ = false;
|
||||
bool show_precipitation_ = false;
|
||||
bool show_average_precipitation_ = false;
|
||||
|
||||
util::ndarray<float, 2> terrain_;
|
||||
util::ndarray<math::vector<float, 2>, 2> velocity_;
|
||||
util::ndarray<math::vector<float, 2>, 2> new_velocity_;
|
||||
util::ndarray<float, 2> pressure_;
|
||||
util::ndarray<float, 2> vorticity_;
|
||||
|
||||
util::ndarray<float, 2> temperature_;
|
||||
util::ndarray<float, 2> new_temperature_;
|
||||
util::ndarray<float, 2> average_temperature_;
|
||||
|
||||
util::ndarray<float, 2> humidity_;
|
||||
util::ndarray<float, 2> new_humidity_;
|
||||
util::ndarray<float, 2> precipitation_;
|
||||
util::ndarray<float, 2> average_precipitation_;
|
||||
|
||||
util::ndarray<math::vector<float, 2>, 2> force_field_main_;
|
||||
util::ndarray<math::vector<float, 2>, 2> force_field_current_;
|
||||
util::ndarray<math::vector<float, 2>, 2> force_field_next_;
|
||||
|
||||
int frame_ = 0;
|
||||
};
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<application::factory> make_application_factory()
|
||||
{
|
||||
return default_application_factory<weather_app>({.name = "Weather simulation test"});
|
||||
}
|
||||
|
||||
}
|
||||
1265
examples/weather_v2.cpp
Normal file
1265
examples/weather_v2.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,38 @@
|
|||
file(GLOB PSEMEK_LIB_DIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/*")
|
||||
list(REMOVE_ITEM PSEMEK_LIB_DIRS "CMakeLists.txt")
|
||||
set(PSEMEK_LIBRARIES)
|
||||
|
||||
foreach(lib ${PSEMEK_LIB_DIRS})
|
||||
if(PSEMEK_LEGACY_UI)
|
||||
list(REMOVE_ITEM PSEMEK_LIB_DIRS "ui")
|
||||
else()
|
||||
list(REMOVE_ITEM PSEMEK_LIB_DIRS "ui_legacy")
|
||||
endif()
|
||||
|
||||
if(NOT (PSEMEK_GRAPHICS_API STREQUAL WEBGPU))
|
||||
list(REMOVE_ITEM PSEMEK_LIB_DIRS "wgpu")
|
||||
endif()
|
||||
|
||||
list(REMOVE_ITEM PSEMEK_LIB_DIRS sdl2 android)
|
||||
set(PSEMEK_BACKEND_LIB_DIR)
|
||||
if(PSEMEK_BACKEND STREQUAL "SDL2")
|
||||
set(PSEMEK_BACKEND_LIB_DIR "sdl2")
|
||||
elseif(PSEMEK_BACKEND STREQUAL "ANDROID")
|
||||
set(PSEMEK_BACKEND_LIB_DIR "android")
|
||||
endif()
|
||||
|
||||
set(PSEMEK_LIBRARIES)
|
||||
set(PSEMEK_BACKEND_LIBRARY)
|
||||
|
||||
foreach(lib ${PSEMEK_LIB_DIRS} ${PSEMEK_BACKEND_LIB_DIR})
|
||||
add_subdirectory(${lib})
|
||||
target_compile_definitions(psemek-${lib} PUBLIC ${PSEMEK_DEFINITIONS})
|
||||
target_compile_options(psemek-${lib} PUBLIC ${PSEMEK_CXX_FLAGS})
|
||||
set_target_properties(psemek-${lib} PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
list(APPEND PSEMEK_LIBRARIES psemek-${lib})
|
||||
if(lib STREQUAL PSEMEK_BACKEND_LIB_DIR)
|
||||
set(PSEMEK_BACKEND_LIBRARY psemek-${lib})
|
||||
else()
|
||||
list(APPEND PSEMEK_LIBRARIES psemek-${lib})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(PSEMEK_LIBRARIES ${PSEMEK_LIBRARIES} PARENT_SCOPE)
|
||||
set(PSEMEK_BACKEND_LIBRARY ${PSEMEK_BACKEND_LIBRARY} CACHE INTERNAL "Application backend library target")
|
||||
|
|
|
|||
6
libs/android/CMakeLists.txt
Normal file
6
libs/android/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
file(GLOB_RECURSE PSEMEK_ANDROID_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
|
||||
file(GLOB_RECURSE PSEMEK_ANDROID_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
|
||||
|
||||
psemek_add_library(psemek-android ${PSEMEK_ANDROID_HEADERS} ${PSEMEK_ANDROID_SOURCES})
|
||||
target_include_directories(psemek-android PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-android PUBLIC psemek-log psemek-util psemek-gfx psemek-audio psemek-app android log)
|
||||
21
libs/android/app/AndroidManifest.xml.in
Normal file
21
libs/android/app/AndroidManifest.xml.in
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version='1.0'?>
|
||||
<manifest xmlns:a='http://schemas.android.com/apk/res/android' package='psemek.app' a:versionCode='0' a:versionName='0'>
|
||||
<uses-sdk
|
||||
a:minSdkVersion="26"
|
||||
a:targetSdkVersion="34"/>
|
||||
<uses-feature
|
||||
a:glEsVersion="0x00030002"
|
||||
a:required="true"/>
|
||||
<application
|
||||
a:label='APPLICATION_NAME'>
|
||||
<activity
|
||||
a:name='psemek.app.MainActivity'
|
||||
a:exported="true"
|
||||
a:screenOrientation="landscape">
|
||||
<intent-filter>
|
||||
<category a:name='android.intent.category.LAUNCHER'/>
|
||||
<action a:name='android.intent.action.MAIN'/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
BIN
libs/android/app/psemek.keystore
Normal file
BIN
libs/android/app/psemek.keystore
Normal file
Binary file not shown.
104
libs/android/app/src/psemek/app/MainActivity.java.in
Normal file
104
libs/android/app/src/psemek/app/MainActivity.java.in
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package psemek.app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActionBar;
|
||||
import android.content.Context;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.opengl.GLSurfaceView.Renderer;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowInsets.Type;
|
||||
import android.view.MotionEvent;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import android.opengl.GLES30;
|
||||
import android.os.Bundle;
|
||||
import java.lang.System;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
class RendererImpl implements Renderer {
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl10, EGLConfig config) {
|
||||
MainActivity.this.nativeApp.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 gl10, int width, int height) {
|
||||
MainActivity.this.nativeApp.resize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl10) {
|
||||
MainActivity.this.nativeApp.drawFrame();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ViewImpl extends GLSurfaceView {
|
||||
|
||||
private final RendererImpl renderer;
|
||||
|
||||
public ViewImpl(Context context) {
|
||||
super(context);
|
||||
setEGLContextClientVersion(3);
|
||||
setEGLConfigChooser(8, 8, 8, 8, 24, 8);
|
||||
renderer = new RendererImpl();
|
||||
setRenderer(renderer);
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent e) {
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
MainActivity.this.nativeApp.touchDown((int)e.getX(), (int)e.getY());
|
||||
return true;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
MainActivity.this.nativeApp.touchUp((int)e.getX(), (int)e.getY());
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
MainActivity.this.nativeApp.touchMove((int)e.getX(), (int)e.getY());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private PsemekApplication nativeApp;
|
||||
private ViewImpl view;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
System.loadLibrary("boost_random");
|
||||
System.loadLibrary("TARGET_NAME");
|
||||
|
||||
ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
|
||||
nativeApp = new PsemekApplication(this.getAssets());
|
||||
|
||||
view = new ViewImpl(this);
|
||||
setContentView(view);
|
||||
|
||||
WindowInsetsController windowInsetsController = view.getWindowInsetsController();
|
||||
if (windowInsetsController != null) {
|
||||
windowInsetsController.hide(Type.systemBars());
|
||||
windowInsetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
nativeApp.destroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
}
|
||||
150
libs/android/app/src/psemek/app/PsemekApplication.java
Normal file
150
libs/android/app/src/psemek/app/PsemekApplication.java
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
package psemek.app;
|
||||
import android.util.Log;
|
||||
import android.content.res.AssetManager;
|
||||
import android.media.AudioTrack;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioFormat;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.Arrays;
|
||||
import java.lang.Thread;
|
||||
|
||||
public class PsemekApplication {
|
||||
|
||||
private AudioTrack audioTrack;
|
||||
private Thread audioThread;
|
||||
private long nativeApp;
|
||||
|
||||
private static native void setupLogging();
|
||||
private static native void setAssetManager(AssetManager assetManager);
|
||||
|
||||
private static native long createNativeApp();
|
||||
private static native void destroyNativeApp(long ptr);
|
||||
private static native void resizeNative(long ptr, int width, int height);
|
||||
private static native void touchDownNative(long ptr, int x, int y);
|
||||
private static native void touchUpNative(long ptr, int x, int y);
|
||||
private static native void touchMoveNative(long ptr, int x, int y);
|
||||
private static native void drawFrameNative(long ptr);
|
||||
|
||||
private static native int audioFrequencyNative();
|
||||
private static native int audioGetSamples(float buffer[], int sampleOffset, int sampleCount);
|
||||
|
||||
private class StreamEventCallbackImpl extends AudioTrack.StreamEventCallback {
|
||||
|
||||
private float buffer[];
|
||||
|
||||
public StreamEventCallbackImpl() {
|
||||
super();
|
||||
buffer = new float[1024];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataRequest(AudioTrack track, int sizeInFrames) {
|
||||
Log.e("psemek", "Requested audio " + sizeInFrames);
|
||||
int sizeInSamples = sizeInFrames * 2;
|
||||
if (buffer.length < sizeInSamples) {
|
||||
buffer = Arrays.copyOf(buffer, sizeInSamples);
|
||||
}
|
||||
|
||||
int samples = PsemekApplication.audioGetSamples(buffer, 0, buffer.length);
|
||||
track.write(buffer, 0, samples, AudioTrack.WRITE_BLOCKING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class AudioThreadImpl extends Thread {
|
||||
|
||||
private float buffer[];
|
||||
|
||||
public AudioThreadImpl(int bufferSizeInFrames) {
|
||||
super("audio");
|
||||
|
||||
buffer = new float[bufferSizeInFrames * 2];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!interrupted()) {
|
||||
int samples = PsemekApplication.audioGetSamples(buffer, 0, buffer.length);
|
||||
PsemekApplication.this.audioTrack.write(buffer, 0, samples, AudioTrack.WRITE_BLOCKING);
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PsemekApplication(AssetManager assetManager) {
|
||||
setupLogging();
|
||||
setAssetManager(assetManager);
|
||||
|
||||
AudioAttributes audioAttributes = new AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_GAME)
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build();
|
||||
|
||||
AudioFormat audioFormat = new AudioFormat.Builder()
|
||||
.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
|
||||
.setSampleRate(audioFrequencyNative())
|
||||
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
|
||||
.build();
|
||||
|
||||
int bufferSize = AudioTrack.getMinBufferSize(audioFrequencyNative(), AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
|
||||
|
||||
audioTrack = new AudioTrack.Builder()
|
||||
.setAudioAttributes(audioAttributes)
|
||||
.setAudioFormat(audioFormat)
|
||||
.setBufferSizeInBytes(bufferSize)
|
||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
|
||||
.build();
|
||||
|
||||
audioThread = new AudioThreadImpl(audioTrack.getBufferCapacityInFrames());
|
||||
audioThread.start();
|
||||
|
||||
audioTrack.play();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
nativeApp = createNativeApp();
|
||||
}
|
||||
|
||||
public void resize(int width, int height) {
|
||||
resizeNative(nativeApp, width, height);
|
||||
}
|
||||
|
||||
public void touchDown(int x, int y)
|
||||
{
|
||||
touchDownNative(nativeApp, x, y);
|
||||
}
|
||||
|
||||
public void touchUp(int x, int y)
|
||||
{
|
||||
touchUpNative(nativeApp, x, y);
|
||||
}
|
||||
|
||||
public void touchMove(int x, int y)
|
||||
{
|
||||
touchMoveNative(nativeApp, x, y);
|
||||
}
|
||||
|
||||
public void drawFrame() {
|
||||
if (nativeApp != 0) {
|
||||
drawFrameNative(nativeApp);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
audioThread.interrupt();
|
||||
if (nativeApp != 0)
|
||||
destroyNativeApp(nativeApp);
|
||||
nativeApp = 0;
|
||||
}
|
||||
|
||||
}
|
||||
57
libs/android/source/audio_engine.cpp
Normal file
57
libs/android/source/audio_engine.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include <psemek/audio/engine.hpp>
|
||||
#include <psemek/audio/constants.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/prof/profiler.hpp>
|
||||
#include <psemek/util/at_scope_exit.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace psemek::android
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
audio::channel_ptr output_channel = std::make_shared<audio::channel>();
|
||||
|
||||
struct audio_engine_impl
|
||||
: audio::engine
|
||||
{
|
||||
audio::channel_ptr output() override
|
||||
{
|
||||
return output_channel;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
std::unique_ptr<engine> make_engine()
|
||||
{
|
||||
return std::make_unique<android::audio_engine_impl>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" int Java_psemek_app_PsemekApplication_audioFrequencyNative(JNIEnv *, jclass)
|
||||
{
|
||||
return psemek::audio::frequency;
|
||||
}
|
||||
|
||||
extern "C" int Java_psemek_app_PsemekApplication_audioGetSamples(JNIEnv * env, jclass, jfloatArray buffer, jint sampleOffset, jint sampleCount)
|
||||
{
|
||||
jfloat * data = env->GetFloatArrayElements(buffer, nullptr);
|
||||
|
||||
int result = 0;
|
||||
if (auto stream = psemek::android::output_channel->stream())
|
||||
result = stream->read({data + sampleOffset, sampleCount});
|
||||
|
||||
std::fill(data + sampleOffset + result, data + sampleOffset + sampleCount, 0.f);
|
||||
|
||||
return sampleCount;
|
||||
}
|
||||
127
libs/android/source/native_app.cpp
Normal file
127
libs/android/source/native_app.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <psemek/app/application.hpp>
|
||||
#include <psemek/gfx/init.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using namespace psemek;
|
||||
|
||||
android_LogPriority log_priority(log::level level)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case log::level::debug:
|
||||
return ANDROID_LOG_DEBUG;
|
||||
case log::level::info:
|
||||
return ANDROID_LOG_INFO;
|
||||
case log::level::warning:
|
||||
return ANDROID_LOG_WARN;
|
||||
case log::level::error:
|
||||
return ANDROID_LOG_ERROR;
|
||||
default:
|
||||
return ANDROID_LOG_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
struct sink_impl
|
||||
: log::sink
|
||||
{
|
||||
void put_message(log::message const & msg) override
|
||||
{
|
||||
__android_log_write(log_priority(msg.level), "psemek", msg.message.data());
|
||||
}
|
||||
|
||||
void flush() override
|
||||
{}
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
auto try_native(JNIEnv * env, F && f) try
|
||||
{
|
||||
return f();
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
env->ThrowNew(env->FindClass("java/lang/Exception"), e.what());
|
||||
__builtin_unreachable();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
env->ThrowNew(env->FindClass("java/lang/Exception"), "Unknown exception");
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_setupLogging(JNIEnv *, jclass)
|
||||
{
|
||||
psemek::log::add_sink(std::make_unique<sink_impl>());
|
||||
}
|
||||
|
||||
extern "C" jlong Java_psemek_app_PsemekApplication_createNativeApp(JNIEnv * env, jclass)
|
||||
{
|
||||
return try_native(env, [&]{
|
||||
auto factory = psemek::app::make_application_factory();
|
||||
|
||||
psemek::gfx::init();
|
||||
|
||||
psemek::app::application::context context;
|
||||
context.show_cursor = [](bool){};
|
||||
context.vsync = [](bool){};
|
||||
context.relative_mouse_mode = [](bool){};
|
||||
context.windowed = [](bool){};
|
||||
auto app = factory->create(factory->options(), context).release();
|
||||
|
||||
return reinterpret_cast<jlong>(app);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_destroyNativeApp(JNIEnv *, jclass, jlong ptr)
|
||||
{
|
||||
delete reinterpret_cast<psemek::app::application *>(ptr);
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_resizeNative(JNIEnv * env, jclass, jlong ptr, jint width, jint height)
|
||||
{
|
||||
try_native(env, [&]{
|
||||
auto app = reinterpret_cast<psemek::app::application *>(ptr);
|
||||
app->on_event(psemek::app::resize_event{{width, height}});
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_touchDownNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
|
||||
{
|
||||
try_native(env, [&]{
|
||||
auto app = reinterpret_cast<psemek::app::application *>(ptr);
|
||||
app->on_event(psemek::app::touch_down_event{{x, y}});
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_touchUpNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
|
||||
{
|
||||
try_native(env, [&]{
|
||||
auto app = reinterpret_cast<psemek::app::application *>(ptr);
|
||||
app->on_event(psemek::app::touch_up_event{{x, y}});
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_touchMoveNative(JNIEnv * env, jclass, jlong ptr, jint x, jint y)
|
||||
{
|
||||
try_native(env, [&]{
|
||||
auto app = reinterpret_cast<psemek::app::application *>(ptr);
|
||||
app->on_event(psemek::app::touch_move_event{{x, y}});
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_drawFrameNative(JNIEnv * env, jclass, jlong ptr)
|
||||
{
|
||||
try_native(env, [&]{
|
||||
auto app = reinterpret_cast<psemek::app::application *>(ptr);
|
||||
app->update();
|
||||
app->present();
|
||||
});
|
||||
}
|
||||
64
libs/android/source/resource.cpp
Normal file
64
libs/android/source/resource.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <psemek/app/resource.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
jobject assetManagerRef;
|
||||
AAssetManager * assetManager;
|
||||
|
||||
struct stream_impl
|
||||
: psemek::io::istream
|
||||
{
|
||||
stream_impl(AAsset * asset)
|
||||
: asset_(asset)
|
||||
{}
|
||||
|
||||
~stream_impl()
|
||||
{
|
||||
AAsset_close(asset_);
|
||||
}
|
||||
|
||||
std::size_t read(char * p, std::size_t size) override
|
||||
{
|
||||
int result = AAsset_read(asset_, p, size);
|
||||
if (result < 0)
|
||||
throw std::runtime_error("Failed to read from resource");
|
||||
return result;
|
||||
}
|
||||
|
||||
bool finished() const override
|
||||
{
|
||||
return AAsset_getRemainingLength64(asset_) == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
AAsset * asset_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::unique_ptr<io::istream> open_resource(std::filesystem::path const & relative_path)
|
||||
{
|
||||
log::info() << "Opening resource " << relative_path;
|
||||
|
||||
auto asset = AAssetManager_open(assetManager, relative_path.c_str(), AASSET_MODE_STREAMING);
|
||||
if (!asset)
|
||||
throw std::runtime_error(util::to_string("Failed to open resource ", relative_path));
|
||||
|
||||
return std::make_unique<stream_impl>(asset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" void Java_psemek_app_PsemekApplication_setAssetManager(JNIEnv * env, jclass, jobject manager)
|
||||
{
|
||||
assetManagerRef = env->NewLocalRef(manager);
|
||||
assetManager = AAssetManager_fromJava(env, manager);
|
||||
}
|
||||
|
|
@ -3,4 +3,7 @@ file(GLOB_RECURSE PSEMEK_APP_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "sou
|
|||
|
||||
psemek_add_library(psemek-app ${PSEMEK_APP_HEADERS} ${PSEMEK_APP_SOURCES})
|
||||
target_include_directories(psemek-app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-app PUBLIC psemek-log psemek-util psemek-gfx psemek-ui psemek-sdl2)
|
||||
target_link_libraries(psemek-app PUBLIC psemek-log psemek-util psemek-gfx)
|
||||
if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU)
|
||||
target_link_libraries(psemek-app PUBLIC psemek-wgpu)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/scene.hpp>
|
||||
#include <psemek/app/scene_manager.hpp>
|
||||
|
||||
#include <psemek/geom/vector.hpp>
|
||||
|
||||
#include <psemek/util/pimpl.hpp>
|
||||
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
static const geom::vector<int, 2> common_resolutions[] =
|
||||
{
|
||||
{1024, 768},
|
||||
{1280, 720},
|
||||
{1280, 1024},
|
||||
{1366, 768},
|
||||
{1440, 900},
|
||||
{1536, 864},
|
||||
{1600, 900},
|
||||
{1920, 1080},
|
||||
};
|
||||
|
||||
struct app
|
||||
: scene_base, scene_manager
|
||||
{
|
||||
struct options
|
||||
{
|
||||
int multisampling = 0;
|
||||
std::optional<geom::vector<int, 2>> fixed_resolution = std::nullopt;
|
||||
bool highdpi = false;
|
||||
};
|
||||
|
||||
app(std::string const & name);
|
||||
app(std::string const & name, int multisampling);
|
||||
app(std::string const & name, options const & opts);
|
||||
~app() override;
|
||||
|
||||
virtual bool running() const;
|
||||
virtual void stop();
|
||||
virtual void on_quit();
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
|
||||
void present() override;
|
||||
|
||||
void poll_events();
|
||||
void run();
|
||||
|
||||
void push_scene(std::shared_ptr<scene> s) override;
|
||||
std::shared_ptr<scene> pop_scene() override;
|
||||
|
||||
void show_cursor(bool show);
|
||||
bool vsync() const;
|
||||
void vsync(bool on);
|
||||
|
||||
float time() const;
|
||||
|
||||
private:
|
||||
psemek_declare_pimpl
|
||||
};
|
||||
|
||||
}
|
||||
71
libs/app/include/psemek/app/application.hpp
Normal file
71
libs/app/include/psemek/app/application.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/event_handler.hpp>
|
||||
|
||||
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
||||
#include <psemek/wgpu/instance.hpp>
|
||||
#include <psemek/wgpu/adapter.hpp>
|
||||
#include <psemek/wgpu/surface.hpp>
|
||||
#include <psemek/wgpu/device.hpp>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct application
|
||||
: event_handler
|
||||
{
|
||||
// Data sent to platform backend for initialization
|
||||
struct options
|
||||
{
|
||||
std::string name;
|
||||
int multisampling = 4;
|
||||
bool highdpi = false;
|
||||
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
||||
std::vector<wgpu::feature> required_features = {};
|
||||
std::optional<wgpu::limits> required_limits = {};
|
||||
std::optional<wgpu::native_limits> required_native_limits = {};
|
||||
#endif
|
||||
};
|
||||
|
||||
// Data received from platform backend after initialization
|
||||
struct context
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
std::function<void(bool)> show_cursor;
|
||||
std::function<void(bool)> relative_mouse_mode;
|
||||
std::function<void(bool)> windowed;
|
||||
std::function<void(bool)> text_input;
|
||||
#if defined(PSEMEK_GRAPHICS_API_OPENGL)
|
||||
std::function<void(bool)> vsync;
|
||||
#endif
|
||||
#if defined(PSEMEK_GRAPHICS_API_WEBGPU)
|
||||
wgpu::instance instance;
|
||||
wgpu::adapter adapter;
|
||||
wgpu::surface surface;
|
||||
wgpu::device device;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct factory
|
||||
{
|
||||
virtual application::options options() = 0;
|
||||
virtual std::unique_ptr<application> create(struct application::options const & options, context const & context) = 0;
|
||||
|
||||
virtual ~factory() {}
|
||||
};
|
||||
|
||||
virtual bool running() const = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual void update() = 0;
|
||||
virtual void present() = 0;
|
||||
};
|
||||
|
||||
// Implemented by the user, called by platform backends
|
||||
std::unique_ptr<application::factory> make_application_factory();
|
||||
|
||||
}
|
||||
33
libs/app/include/psemek/app/application_base.hpp
Normal file
33
libs/app/include/psemek/app/application_base.hpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application.hpp>
|
||||
#include <psemek/app/event_state.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct application_base
|
||||
: application
|
||||
{
|
||||
void on_event(resize_event const &) override;
|
||||
void on_event(focus_event const &) override;
|
||||
void on_event(mouse_move_event const &) override;
|
||||
void on_event(mouse_wheel_event const &) override;
|
||||
void on_event(mouse_button_event const &) override;
|
||||
void on_event(touch_down_event const &) override;
|
||||
void on_event(touch_up_event const &) override;
|
||||
void on_event(touch_move_event const &) override;
|
||||
void on_event(key_event const &) override;
|
||||
void on_event(text_input_event const &) override;
|
||||
|
||||
void stop() override;
|
||||
bool running() const override;
|
||||
|
||||
event_state const & state() const { return state_; }
|
||||
|
||||
private:
|
||||
bool running_ = true;
|
||||
event_state state_;
|
||||
};
|
||||
|
||||
}
|
||||
47
libs/app/include/psemek/app/default_application_factory.hpp
Normal file
47
libs/app/include/psemek/app/default_application_factory.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
template <typename Factory>
|
||||
std::unique_ptr<application::factory> default_application_factory(application::options const & options, Factory && factory)
|
||||
{
|
||||
struct factory_impl
|
||||
: application::factory
|
||||
{
|
||||
application::options opts;
|
||||
Factory factory;
|
||||
|
||||
factory_impl(application::options const & options, Factory && factory)
|
||||
: opts(options)
|
||||
, factory(std::move(factory))
|
||||
{}
|
||||
|
||||
application::options options() override
|
||||
{
|
||||
return opts;
|
||||
}
|
||||
|
||||
std::unique_ptr<application> create(application::options const & options, application::context const & context) override
|
||||
{
|
||||
return factory(options, context);
|
||||
}
|
||||
};
|
||||
|
||||
return std::make_unique<factory_impl>(options, std::move(factory));
|
||||
}
|
||||
|
||||
template <typename Application>
|
||||
std::unique_ptr<application::factory> default_application_factory(application::options const & options)
|
||||
{
|
||||
return default_application_factory(options, [](auto const & options, auto const & context){
|
||||
return std::make_unique<Application>(options, context);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
24
libs/app/include/psemek/app/event_handler.hpp
Normal file
24
libs/app/include/psemek/app/event_handler.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/events.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct event_handler
|
||||
{
|
||||
virtual void on_event(resize_event const &) {}
|
||||
virtual void on_event(focus_event const &) {}
|
||||
virtual void on_event(mouse_move_event const &) {}
|
||||
virtual void on_event(mouse_wheel_event const &) {}
|
||||
virtual void on_event(mouse_button_event const &) {}
|
||||
virtual void on_event(touch_down_event const &) {}
|
||||
virtual void on_event(touch_up_event const &) {}
|
||||
virtual void on_event(touch_move_event const &) {}
|
||||
virtual void on_event(key_event const &) {}
|
||||
virtual void on_event(text_input_event const &) {}
|
||||
|
||||
virtual ~event_handler() {}
|
||||
};
|
||||
|
||||
}
|
||||
67
libs/app/include/psemek/app/event_state.hpp
Normal file
67
libs/app/include/psemek/app/event_state.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/events.hpp>
|
||||
#include <psemek/util/hash_table.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct event_state
|
||||
{
|
||||
math::vector<int, 2> size = {0, 0};
|
||||
bool focus = true;
|
||||
math::point<int, 2> mouse = {0, 0};
|
||||
int wheel = 0;
|
||||
util::hash_set<mouse_button> mouse_button_down;
|
||||
util::hash_set<keycode> key_down;
|
||||
};
|
||||
|
||||
inline void apply(event_state & state, resize_event const & event)
|
||||
{
|
||||
state.size = event.size;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, focus_event const & event)
|
||||
{
|
||||
state.focus = event.gained;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, mouse_move_event const & event)
|
||||
{
|
||||
state.mouse = event.position;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, mouse_wheel_event const & event)
|
||||
{
|
||||
state.wheel += event.delta;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, mouse_button_event const & event)
|
||||
{
|
||||
if (event.down)
|
||||
state.mouse_button_down.insert(event.button);
|
||||
else
|
||||
state.mouse_button_down.erase(event.button);
|
||||
}
|
||||
|
||||
inline void apply(event_state &, touch_down_event const &)
|
||||
{}
|
||||
|
||||
inline void apply(event_state &, touch_up_event const &)
|
||||
{}
|
||||
|
||||
inline void apply(event_state &, touch_move_event const &)
|
||||
{}
|
||||
|
||||
inline void apply(event_state & state, key_event const & event)
|
||||
{
|
||||
if (event.down)
|
||||
state.key_down.insert(event.key);
|
||||
else
|
||||
state.key_down.erase(event.key);
|
||||
}
|
||||
|
||||
inline void apply(event_state &, text_input_event const &)
|
||||
{}
|
||||
|
||||
}
|
||||
182
libs/app/include/psemek/app/events.hpp
Normal file
182
libs/app/include/psemek/app/events.hpp
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/math/point.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct resize_event
|
||||
{
|
||||
math::vector<int, 2> size;
|
||||
};
|
||||
|
||||
struct focus_event
|
||||
{
|
||||
bool gained;
|
||||
};
|
||||
|
||||
struct mouse_move_event
|
||||
{
|
||||
math::point<int, 2> position;
|
||||
math::vector<int, 2> relative;
|
||||
};
|
||||
|
||||
struct mouse_wheel_event
|
||||
{
|
||||
int delta;
|
||||
};
|
||||
|
||||
enum class mouse_button
|
||||
{
|
||||
left,
|
||||
middle,
|
||||
right,
|
||||
};
|
||||
|
||||
struct mouse_button_event
|
||||
{
|
||||
mouse_button button;
|
||||
bool down;
|
||||
};
|
||||
|
||||
struct touch_down_event
|
||||
{
|
||||
math::point<int, 2> position;
|
||||
};
|
||||
|
||||
struct touch_up_event
|
||||
{
|
||||
math::point<int, 2> position;
|
||||
};
|
||||
|
||||
struct touch_move_event
|
||||
{
|
||||
math::point<int, 2> position;
|
||||
};
|
||||
|
||||
enum class keycode
|
||||
{
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
NUM_1,
|
||||
NUM_2,
|
||||
NUM_3,
|
||||
NUM_4,
|
||||
NUM_5,
|
||||
NUM_6,
|
||||
NUM_7,
|
||||
NUM_8,
|
||||
NUM_9,
|
||||
NUM_0,
|
||||
RETURN,
|
||||
ESCAPE,
|
||||
BACKSPACE,
|
||||
TAB,
|
||||
SPACE,
|
||||
MINUS,
|
||||
EQUALS,
|
||||
LEFTBRACKET,
|
||||
RIGHTBRACKET,
|
||||
BACKSLASH,
|
||||
NONUSHASH,
|
||||
SEMICOLON,
|
||||
APOSTROPHE,
|
||||
BACKQUOTE,
|
||||
COMMA,
|
||||
PERIOD,
|
||||
SLASH,
|
||||
CAPSLOCK,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
PRINTSCREEN,
|
||||
SCROLLLOCK,
|
||||
PAUSE,
|
||||
INSERT,
|
||||
HOME,
|
||||
PAGEUP,
|
||||
DELETE,
|
||||
END,
|
||||
PAGEDOWN,
|
||||
RIGHT,
|
||||
LEFT,
|
||||
DOWN,
|
||||
UP,
|
||||
NUMLOCKCLEAR,
|
||||
KP_DIVIDE,
|
||||
KP_MULTIPLY,
|
||||
KP_MINUS,
|
||||
KP_PLUS,
|
||||
KP_ENTER,
|
||||
KP_1,
|
||||
KP_2,
|
||||
KP_3,
|
||||
KP_4,
|
||||
KP_5,
|
||||
KP_6,
|
||||
KP_7,
|
||||
KP_8,
|
||||
KP_9,
|
||||
KP_0,
|
||||
KP_PERIOD,
|
||||
APPLICATION,
|
||||
MUTE,
|
||||
VOLUMEUP,
|
||||
VOLUMEDOWN,
|
||||
LCTRL,
|
||||
LSHIFT,
|
||||
LALT,
|
||||
LGUI,
|
||||
RCTRL,
|
||||
RSHIFT,
|
||||
RALT,
|
||||
RGUI,
|
||||
};
|
||||
|
||||
struct key_event
|
||||
{
|
||||
keycode key;
|
||||
bool down;
|
||||
};
|
||||
|
||||
struct text_input_event
|
||||
{
|
||||
std::string input;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/util/pretty_print.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
template <typename App, typename ... Args>
|
||||
int main(Args && ... args) try
|
||||
{
|
||||
util::clock<std::chrono::milliseconds, std::chrono::high_resolution_clock> clock;
|
||||
|
||||
#ifdef PSEMEK_PACKAGE_MODE
|
||||
log::level stdio_log_level = log::level::info;
|
||||
#else
|
||||
log::level stdio_log_level = log::level::debug;
|
||||
#endif
|
||||
|
||||
log::add_sink(log::default_sink(io::std_out(), stdio_log_level));
|
||||
log::register_thread("main");
|
||||
|
||||
App app(std::forward<Args>(args)...);
|
||||
|
||||
log::info() << "Started in " << util::pretty(clock.duration(), std::chrono::milliseconds{1});
|
||||
|
||||
log::info() << "Running";
|
||||
app.run();
|
||||
log::info() << "Quitting";
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
log::error() << e.what();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log::error() << "Unknown exception";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
17
libs/app/include/psemek/app/resource.hpp
Normal file
17
libs/app/include/psemek/app/resource.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/io/stream.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
std::filesystem::path resource_root();
|
||||
void set_resource_root(std::filesystem::path const & root);
|
||||
|
||||
// Implemented by a backend library
|
||||
std::unique_ptr<io::istream> open_resource(std::filesystem::path const & relative_path);
|
||||
|
||||
}
|
||||
|
|
@ -1,93 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/geom/point.hpp>
|
||||
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <psemek/app/event_handler.hpp>
|
||||
#include <psemek/app/event_state.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct app;
|
||||
|
||||
struct scene
|
||||
: event_handler
|
||||
{
|
||||
virtual ~scene() = 0;
|
||||
|
||||
virtual void on_scene_enter(app * /* parent */) {}
|
||||
virtual void on_scene_exit() {}
|
||||
|
||||
virtual void on_resize(int /* width */, int /* height */) {}
|
||||
virtual void on_focus_gained() {}
|
||||
virtual void on_focus_lost() {}
|
||||
|
||||
virtual void on_mouse_move(int /* x */, int /* y */, int /* dx */, int /* dy */) {}
|
||||
virtual void on_mouse_wheel(int /* delta */) {}
|
||||
virtual void on_left_button_down() {}
|
||||
virtual void on_left_button_up() {}
|
||||
virtual void on_middle_button_down() {}
|
||||
virtual void on_middle_button_up() {}
|
||||
virtual void on_right_button_down() {}
|
||||
virtual void on_right_button_up() {}
|
||||
|
||||
virtual void on_key_down(SDL_Keycode /* key */) {}
|
||||
virtual void on_key_up(SDL_Keycode /* key */) {}
|
||||
|
||||
virtual void on_text_input(std::string_view /* text */) {}
|
||||
virtual void on_enter(event_state const &) {}
|
||||
virtual void on_exit() {}
|
||||
|
||||
virtual void update() {}
|
||||
virtual void present() {}
|
||||
};
|
||||
|
||||
inline scene::~scene() = default;
|
||||
|
||||
struct scene_base
|
||||
: scene
|
||||
{
|
||||
void on_scene_enter(app * parent) override { parent_ = parent; }
|
||||
void on_scene_exit() override { parent_ = nullptr; }
|
||||
|
||||
void on_resize(int width, int height) override { width_ = width; height_ = height; }
|
||||
|
||||
void on_mouse_move(int x, int y, int, int) override { mouse_ = geom::point{x, y}; }
|
||||
void on_left_button_down() override { left_button_down_ = true; }
|
||||
void on_left_button_up() override { left_button_down_ = false; }
|
||||
void on_middle_button_down() override { middle_button_down_ = true; }
|
||||
void on_middle_button_up() override { middle_button_down_ = false; }
|
||||
void on_right_button_down() override { right_button_down_ = true; }
|
||||
void on_right_button_up() override { right_button_down_ = false; }
|
||||
void on_key_down(SDL_Keycode key) override { keys_.insert(key); }
|
||||
void on_key_up(SDL_Keycode key) override { keys_.erase(key); }
|
||||
|
||||
bool active() const { return parent_ != nullptr; }
|
||||
app * parent() const { return parent_; }
|
||||
|
||||
bool is_left_button_down() const { return left_button_down_; }
|
||||
bool is_middle_button_down() const { return middle_button_down_; }
|
||||
bool is_right_button_down() const { return right_button_down_; }
|
||||
|
||||
std::optional<geom::point<int, 2>> mouse() const { return mouse_; }
|
||||
|
||||
bool is_key_down(SDL_Keycode key) const { return keys_.count(key) > 0; }
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
|
||||
private:
|
||||
app * parent_ = nullptr;
|
||||
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
|
||||
bool left_button_down_ = false;
|
||||
bool middle_button_down_ = false;
|
||||
bool right_button_down_ = false;
|
||||
|
||||
std::optional<geom::point<int, 2>> mouse_;
|
||||
|
||||
std::set<SDL_Keycode> keys_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
35
libs/app/include/psemek/app/scene_application.hpp
Normal file
35
libs/app/include/psemek/app/scene_application.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/scene.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct scene_application
|
||||
: application_base
|
||||
{
|
||||
void on_event(resize_event const &) override;
|
||||
void on_event(focus_event const &) override;
|
||||
void on_event(mouse_move_event const &) override;
|
||||
void on_event(mouse_wheel_event const &) override;
|
||||
void on_event(mouse_button_event const &) override;
|
||||
void on_event(touch_down_event const &) override;
|
||||
void on_event(touch_up_event const &) override;
|
||||
void on_event(touch_move_event const &) override;
|
||||
void on_event(key_event const &) override;
|
||||
void on_event(text_input_event const &) override;
|
||||
|
||||
void stop() override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
virtual std::shared_ptr<scene> current_scene() = 0;
|
||||
|
||||
private:
|
||||
template <typename Event>
|
||||
void on_event_impl(Event const & event);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/scene.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct scene_manager
|
||||
{
|
||||
virtual void push_scene(std::shared_ptr<scene>) = 0;
|
||||
virtual std::shared_ptr<scene> pop_scene() = 0;
|
||||
|
||||
virtual ~scene_manager() {}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/scene.hpp>
|
||||
|
||||
#include <psemek/ui/controller.hpp>
|
||||
|
||||
#include <psemek/util/clock.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct ui_scene
|
||||
: scene_base
|
||||
{
|
||||
ui_scene(ui::controller & controller);
|
||||
|
||||
void on_scene_enter(app * parent) override;
|
||||
void on_scene_exit() override;
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
|
||||
void on_mouse_move(int x, int y, int dx, int dy) override;
|
||||
void on_mouse_wheel(int delta) override;
|
||||
void on_left_button_down() override;
|
||||
void on_left_button_up() override;
|
||||
void on_middle_button_down() override;
|
||||
void on_middle_button_up() override;
|
||||
void on_right_button_down() override;
|
||||
void on_right_button_up() override;
|
||||
|
||||
void on_key_down(SDL_Keycode key) override;
|
||||
void on_key_up(SDL_Keycode key) override;
|
||||
|
||||
void on_text_input(std::string_view text) override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
ui::controller & controller() const { return controller_; }
|
||||
|
||||
protected:
|
||||
|
||||
std::optional<std::size_t> max_events_per_frame_ = 64;
|
||||
|
||||
std::shared_ptr<ui::element> get_ui() { return ui_; }
|
||||
void set_ui(std::shared_ptr<ui::element> ui);
|
||||
|
||||
private:
|
||||
ui::controller & controller_;
|
||||
std::shared_ptr<ui::element> ui_;
|
||||
|
||||
util::clock<std::chrono::duration<float>, std::chrono::high_resolution_clock> update_clock_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
#include <psemek/app/app.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/sdl2/init.hpp>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
|
||||
struct app::impl
|
||||
{
|
||||
app * parent;
|
||||
|
||||
std::shared_ptr<void> sdl_init;
|
||||
SDL_Window * window = nullptr;
|
||||
SDL_GLContext gl_context = nullptr;
|
||||
|
||||
std::vector<std::shared_ptr<scene>> scene_stack;
|
||||
|
||||
bool running = false;
|
||||
|
||||
bool had_initial_resize = false;
|
||||
bool had_scene_exit = false;
|
||||
|
||||
clock::time_point start_time;
|
||||
|
||||
impl(app * parent)
|
||||
: parent(parent)
|
||||
, sdl_init(sdl2::init(SDL_INIT_VIDEO))
|
||||
{}
|
||||
|
||||
~impl()
|
||||
{
|
||||
if (gl_context) SDL_GL_DeleteContext(gl_context);
|
||||
if (window) SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
std::shared_ptr<scene> get_scene()
|
||||
{
|
||||
if (!scene_stack.empty())
|
||||
return scene_stack.back();
|
||||
return std::shared_ptr<scene>(parent, [](scene *){});
|
||||
}
|
||||
};
|
||||
|
||||
app::app(std::string const & name)
|
||||
: app(name, 0)
|
||||
{}
|
||||
|
||||
app::app(std::string const & name, int multisampling)
|
||||
: app(name, options{multisampling})
|
||||
{}
|
||||
|
||||
app::app(std::string const & name, options const & opts)
|
||||
: pimpl_{make_impl(this)}
|
||||
{
|
||||
impl().start_time = clock::now();
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl::sys::major_version());
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl::sys::minor_version());
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
if (opts.multisampling == 0)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, opts.multisampling);
|
||||
}
|
||||
|
||||
std::uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN;
|
||||
if (!opts.fixed_resolution)
|
||||
flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED;
|
||||
if (opts.highdpi) flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
|
||||
int width = opts.fixed_resolution ? (*opts.fixed_resolution)[0] : 1024;
|
||||
int height = opts.fixed_resolution ? (*opts.fixed_resolution)[1] : 768;
|
||||
|
||||
impl().window = SDL_CreateWindow(name.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags);
|
||||
if (!impl().window)
|
||||
sdl2::fail("Failed to create window: ");
|
||||
|
||||
impl().gl_context = SDL_GL_CreateContext(impl().window);
|
||||
if (!impl().gl_context)
|
||||
sdl2::fail("Failed to create OpenGL context: ");
|
||||
|
||||
SDL_GL_MakeCurrent(impl().window, impl().gl_context);
|
||||
|
||||
if (!gl::sys::initialize())
|
||||
throw std::runtime_error("Failed to load OpenGL functions");
|
||||
|
||||
auto vendor = gl::GetString(gl::VENDOR);
|
||||
auto renderer = gl::GetString(gl::RENDERER);
|
||||
|
||||
int major, minor;
|
||||
gl::GetIntegerv(gl::MAJOR_VERSION, &major);
|
||||
gl::GetIntegerv(gl::MINOR_VERSION, &minor);
|
||||
log::info() << "Initialized OpenGL " << major << '.' << minor << ", " << vendor << ", " << renderer;
|
||||
|
||||
SDL_GL_GetDrawableSize(impl().window, &width, &height);
|
||||
scene_base::on_resize(width, height);
|
||||
|
||||
log::info() << "Initial window size: " << width << 'x' << height;
|
||||
|
||||
SDL_StopTextInput();
|
||||
}
|
||||
|
||||
app::~app()
|
||||
{}
|
||||
|
||||
bool app::running() const
|
||||
{
|
||||
return impl().running;
|
||||
}
|
||||
|
||||
void app::stop()
|
||||
{
|
||||
impl().running = false;
|
||||
}
|
||||
|
||||
void app::on_resize(int width, int height)
|
||||
{
|
||||
scene_base::on_resize(width, height);
|
||||
gl::Viewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
void app::on_quit()
|
||||
{
|
||||
impl().running = false;
|
||||
}
|
||||
|
||||
void app::poll_events()
|
||||
{
|
||||
auto handler = [this]{ return impl().get_scene(); };
|
||||
|
||||
for (SDL_Event e; SDL_PollEvent(&e);) switch (e.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
on_quit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT: switch (e.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
impl().had_initial_resize = true;
|
||||
{
|
||||
int width, height;
|
||||
SDL_GL_GetDrawableSize(impl().window, &width, &height);
|
||||
handler()->on_resize(width, height);
|
||||
log::info() << "Window resized to " << width << 'x' << height;
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
handler()->on_focus_gained();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
handler()->on_focus_lost();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
handler()->on_mouse_move(e.motion.x, e.motion.y, e.motion.xrel, e.motion.yrel);
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
handler()->on_mouse_wheel(e.wheel.y);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
switch (e.button.button)
|
||||
{
|
||||
case SDL_BUTTON_LEFT:
|
||||
handler()->on_left_button_down();
|
||||
break;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
handler()->on_middle_button_down();
|
||||
break;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
handler()->on_right_button_down();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
switch (e.button.button)
|
||||
{
|
||||
case SDL_BUTTON_LEFT:
|
||||
handler()->on_left_button_up();
|
||||
break;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
handler()->on_middle_button_up();
|
||||
break;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
handler()->on_right_button_up();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
handler()->on_key_down(e.key.keysym.sym);
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
handler()->on_key_up(e.key.keysym.sym);
|
||||
break;
|
||||
case SDL_TEXTINPUT:
|
||||
handler()->on_text_input(e.text.text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app::present()
|
||||
{
|
||||
gl::ClearColor(0.7f, 0.7f, 1.f, 1.f);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void app::run()
|
||||
{
|
||||
SDL_ShowWindow(impl().window);
|
||||
impl().running = true;
|
||||
if (impl().get_scene().get() == this)
|
||||
on_scene_enter(this);
|
||||
while (running())
|
||||
{
|
||||
poll_events();
|
||||
|
||||
auto handler = [this]{ return impl().get_scene(); };
|
||||
|
||||
if (!impl().had_initial_resize)
|
||||
{
|
||||
int w, h;
|
||||
SDL_GetWindowSize(impl().window, &w, &h);
|
||||
impl().had_initial_resize = true;
|
||||
handler()->on_resize(w, h);
|
||||
}
|
||||
if (!running()) break;
|
||||
handler()->update();
|
||||
handler()->present();
|
||||
|
||||
SDL_GL_SwapWindow(impl().window);
|
||||
}
|
||||
if (!impl().had_scene_exit)
|
||||
{
|
||||
impl().had_scene_exit = true;
|
||||
impl().get_scene()->on_scene_exit();
|
||||
}
|
||||
}
|
||||
|
||||
void app::push_scene(std::shared_ptr<scene> s)
|
||||
{
|
||||
if (!impl().had_scene_exit)
|
||||
{
|
||||
impl().had_scene_exit = true;
|
||||
impl().get_scene()->on_scene_exit();
|
||||
}
|
||||
|
||||
impl().had_initial_resize = false;
|
||||
impl().had_scene_exit = false;
|
||||
impl().scene_stack.push_back(std::move(s));
|
||||
|
||||
impl().get_scene()->on_scene_enter(this);
|
||||
}
|
||||
|
||||
std::shared_ptr<scene> app::pop_scene()
|
||||
{
|
||||
if (impl().scene_stack.empty())
|
||||
return nullptr;
|
||||
|
||||
if (!impl().had_scene_exit)
|
||||
{
|
||||
impl().had_scene_exit = true;
|
||||
impl().get_scene()->on_scene_exit();
|
||||
}
|
||||
|
||||
impl().had_initial_resize = false;
|
||||
auto s = std::move(impl().scene_stack.back());
|
||||
impl().scene_stack.pop_back();
|
||||
|
||||
impl().get_scene()->on_scene_enter(this);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void app::show_cursor(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show ? SDL_TRUE : SDL_FALSE);
|
||||
SDL_SetRelativeMouseMode(show ? SDL_FALSE : SDL_TRUE);
|
||||
}
|
||||
|
||||
float app::time() const
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::duration<float>>(clock::now() - impl().start_time).count();
|
||||
}
|
||||
|
||||
bool app::vsync() const
|
||||
{
|
||||
return SDL_GL_GetSwapInterval() != 0;
|
||||
}
|
||||
|
||||
void app::vsync(bool on)
|
||||
{
|
||||
if (on)
|
||||
{
|
||||
// try adaptive vsync
|
||||
if (SDL_GL_SetSwapInterval(-1) != 0)
|
||||
{
|
||||
// failed, try usual vsync then
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_GL_SetSwapInterval(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
64
libs/app/source/application_base.cpp
Normal file
64
libs/app/source/application_base.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
void application_base::on_event(resize_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(focus_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(mouse_move_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(mouse_wheel_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(mouse_button_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(touch_down_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(touch_up_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(touch_move_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(key_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(text_input_event const &)
|
||||
{}
|
||||
|
||||
void application_base::stop()
|
||||
{
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
bool application_base::running() const
|
||||
{
|
||||
return running_;
|
||||
}
|
||||
|
||||
}
|
||||
24
libs/app/source/resource.cpp
Normal file
24
libs/app/source/resource.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include <psemek/app/resource.hpp>
|
||||
#include <psemek/util/executable_path.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static std::filesystem::path global_resource_root = util::executable_path().parent_path();
|
||||
|
||||
}
|
||||
|
||||
std::filesystem::path resource_root()
|
||||
{
|
||||
return global_resource_root;
|
||||
}
|
||||
|
||||
void set_resource_root(std::filesystem::path const & root)
|
||||
{
|
||||
global_resource_root = root;
|
||||
}
|
||||
|
||||
}
|
||||
83
libs/app/source/scene_application.cpp
Normal file
83
libs/app/source/scene_application.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include <psemek/app/scene_application.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
void scene_application::on_event(resize_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(focus_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(mouse_move_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(mouse_wheel_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(mouse_button_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(touch_down_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(touch_up_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(touch_move_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(key_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(text_input_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::stop()
|
||||
{
|
||||
if (auto scene = current_scene())
|
||||
scene->on_exit();
|
||||
application_base::stop();
|
||||
}
|
||||
|
||||
void scene_application::update()
|
||||
{
|
||||
if (auto scene = current_scene())
|
||||
scene->update();
|
||||
}
|
||||
|
||||
void scene_application::present()
|
||||
{
|
||||
if (auto scene = current_scene())
|
||||
scene->present();
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
void scene_application::on_event_impl(Event const & event)
|
||||
{
|
||||
application_base::on_event(event);
|
||||
if (auto scene = current_scene())
|
||||
scene->on_event(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
#include <psemek/app/ui_scene.hpp>
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
ui_scene::ui_scene(ui::controller & controller)
|
||||
: controller_(controller)
|
||||
{}
|
||||
|
||||
void ui_scene::on_scene_enter(app * parent)
|
||||
{
|
||||
scene_base::on_scene_enter(parent);
|
||||
controller_.set_root(ui_);
|
||||
update_clock_.restart();
|
||||
}
|
||||
|
||||
void ui_scene::on_scene_exit()
|
||||
{
|
||||
scene_base::on_scene_exit();
|
||||
controller_.set_root(nullptr);
|
||||
}
|
||||
|
||||
void ui_scene::on_resize(int width, int height)
|
||||
{
|
||||
scene_base::on_resize(width, height);
|
||||
controller_.reshape({{{0.f, width}, {0.f, height}}});
|
||||
}
|
||||
|
||||
void ui_scene::on_mouse_move(int x, int y, int dx, int dy)
|
||||
{
|
||||
scene_base::on_mouse_move(x, y, dx, dy);
|
||||
controller_.event(ui::mouse_move{{x, y}});
|
||||
}
|
||||
|
||||
void ui_scene::on_mouse_wheel(int delta)
|
||||
{
|
||||
scene_base::on_mouse_wheel(delta);
|
||||
controller_.event(ui::mouse_wheel{delta});
|
||||
}
|
||||
|
||||
void ui_scene::on_left_button_down()
|
||||
{
|
||||
scene_base::on_left_button_down();
|
||||
controller_.event(ui::mouse_click{ui::mouse_button::left, true});
|
||||
}
|
||||
|
||||
void ui_scene::on_left_button_up()
|
||||
{
|
||||
scene_base::on_left_button_up();
|
||||
controller_.event(ui::mouse_click{ui::mouse_button::left, false});
|
||||
}
|
||||
|
||||
void ui_scene::on_middle_button_down()
|
||||
{
|
||||
scene_base::on_left_button_down();
|
||||
controller_.event(ui::mouse_click{ui::mouse_button::middle, true});
|
||||
}
|
||||
|
||||
void ui_scene::on_middle_button_up()
|
||||
{
|
||||
scene_base::on_left_button_down();
|
||||
controller_.event(ui::mouse_click{ui::mouse_button::middle, false});
|
||||
}
|
||||
|
||||
void ui_scene::on_right_button_down()
|
||||
{
|
||||
scene_base::on_right_button_down();
|
||||
controller_.event(ui::mouse_click{ui::mouse_button::right, true});
|
||||
}
|
||||
|
||||
void ui_scene::on_right_button_up()
|
||||
{
|
||||
scene_base::on_right_button_down();
|
||||
controller_.event(ui::mouse_click{ui::mouse_button::right, false});
|
||||
}
|
||||
|
||||
void ui_scene::on_key_down(SDL_Keycode key)
|
||||
{
|
||||
scene_base::on_key_down(key);
|
||||
controller_.event(ui::key_press{key, true});
|
||||
}
|
||||
|
||||
void ui_scene::on_key_up(SDL_Keycode key)
|
||||
{
|
||||
scene_base::on_key_up(key);
|
||||
controller_.event(ui::key_press{key, false});
|
||||
}
|
||||
|
||||
void ui_scene::on_text_input(std::string_view text)
|
||||
{
|
||||
scene_base::on_text_input(text);
|
||||
controller_.event(ui::text_input{text});
|
||||
}
|
||||
|
||||
void ui_scene::update()
|
||||
{
|
||||
controller_.update(update_clock_.restart().count());
|
||||
std::size_t events = controller_.loop()->pump(max_events_per_frame_);
|
||||
if (max_events_per_frame_ && events == *max_events_per_frame_)
|
||||
log::warning() << "UI event loop had more than " << *max_events_per_frame_ << " events, delaying others";
|
||||
}
|
||||
|
||||
void ui_scene::present()
|
||||
{
|
||||
gfx::render_target rt;
|
||||
rt.viewport = {{{0, width()}, {0, height()}}};
|
||||
rt.framebuffer = &gfx::framebuffer::null();
|
||||
rt.draw_buffer = gl::BACK_LEFT;
|
||||
controller_.render(rt);
|
||||
}
|
||||
|
||||
void ui_scene::set_ui(std::shared_ptr<ui::element> ui)
|
||||
{
|
||||
ui_ = std::move(ui);
|
||||
if (active())
|
||||
controller_.set_root(ui_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,4 +3,8 @@ file(GLOB_RECURSE PSEMEK_ASYNC_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "s
|
|||
|
||||
psemek_add_library(psemek-async ${PSEMEK_ASYNC_HEADERS} ${PSEMEK_ASYNC_SOURCES})
|
||||
target_include_directories(psemek-async PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-async PUBLIC psemek-log psemek-util)
|
||||
target_link_libraries(psemek-async PUBLIC
|
||||
psemek-log
|
||||
psemek-util
|
||||
psemek-prof
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ namespace psemek::async
|
|||
|
||||
void stop() override;
|
||||
|
||||
void clear() override;
|
||||
|
||||
void wait() override;
|
||||
|
||||
void wait_for(clock::duration period) override;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
#include <psemek/util/function.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::async
|
||||
{
|
||||
|
|
@ -30,6 +29,9 @@ namespace psemek::async
|
|||
// NB: the executor must call stop() from destructor.
|
||||
virtual void stop() = 0;
|
||||
|
||||
// Clear the pending tasks queue.
|
||||
virtual void clear() = 0;
|
||||
|
||||
// Wait for all the tasks to be executed.
|
||||
// May take forever.
|
||||
virtual void wait() = 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/function.hpp>
|
||||
#include <psemek/util/exception.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
|
@ -39,6 +40,18 @@ namespace psemek::async
|
|||
using type = util::function<void()>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct future_result_type
|
||||
{
|
||||
using type = T &;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct future_result_type<void>
|
||||
{
|
||||
using type = void;
|
||||
};
|
||||
|
||||
struct cancel_token
|
||||
{};
|
||||
|
||||
|
|
@ -54,21 +67,26 @@ namespace psemek::async
|
|||
std::weak_ptr<cancel_token> weak_cancel;
|
||||
|
||||
std::mutex then_mutex;
|
||||
typename then_function<T>::type then_func;
|
||||
std::vector<typename then_function<T>::type> then_funcs;
|
||||
std::vector<util::function<void(std::exception_ptr)>> then_exception_funcs;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
struct empty_future_error
|
||||
: std::exception
|
||||
: util::exception
|
||||
{
|
||||
char const * what() const noexcept { return "future is empty"; }
|
||||
empty_future_error(util::stacktrace stacktrace = {})
|
||||
: util::exception("Future is empty", std::move(stacktrace))
|
||||
{}
|
||||
};
|
||||
|
||||
struct canceled_task_error
|
||||
: std::exception
|
||||
: util::exception
|
||||
{
|
||||
char const * what() const noexcept { return "task canceled"; }
|
||||
canceled_task_error(util::stacktrace stacktrace = {})
|
||||
: util::exception("Task canceled", std::move(stacktrace))
|
||||
{}
|
||||
};
|
||||
|
||||
struct auto_cancel_tag{};
|
||||
|
|
@ -80,14 +98,16 @@ namespace psemek::async
|
|||
using result_type = T;
|
||||
|
||||
future() = default;
|
||||
future(future&&) = default;
|
||||
future(future const &) = default;
|
||||
future(future &&) = default;
|
||||
|
||||
future(std::shared_ptr<detail::task_state<T>> state, std::shared_ptr<detail::cancel_token> cancel)
|
||||
: state_(std::move(state))
|
||||
, cancel_(std::move(cancel))
|
||||
{}
|
||||
|
||||
future & operator = (future&&) = default;
|
||||
future & operator = (future const &) = default;
|
||||
future & operator = (future &&) = default;
|
||||
|
||||
~future()
|
||||
{
|
||||
|
|
@ -131,7 +151,7 @@ namespace psemek::async
|
|||
return has_value_unsafe();
|
||||
}
|
||||
|
||||
T get()
|
||||
typename detail::future_result_type<T>::type get()
|
||||
{
|
||||
if (!state_)
|
||||
throw empty_future_error{};
|
||||
|
|
@ -143,7 +163,7 @@ namespace psemek::async
|
|||
if constexpr (std::is_same_v<T, void>)
|
||||
return;
|
||||
else
|
||||
return std::move(*(state_->value));
|
||||
return *(state_->value);
|
||||
}
|
||||
else
|
||||
std::rethrow_exception(state_->exception);
|
||||
|
|
@ -177,15 +197,19 @@ namespace psemek::async
|
|||
};
|
||||
|
||||
struct empty_promise_error
|
||||
: std::exception
|
||||
: util::exception
|
||||
{
|
||||
char const * what() const noexcept { return "promise is empty"; }
|
||||
empty_promise_error(util::stacktrace stacktrace = {})
|
||||
: util::exception("Promise is empty", std::move(stacktrace))
|
||||
{}
|
||||
};
|
||||
|
||||
struct satisfied_promise_error
|
||||
: std::exception
|
||||
: util::exception
|
||||
{
|
||||
char const * what() const noexcept { return "promise already contains a value or exception"; }
|
||||
satisfied_promise_error(util::stacktrace stacktrace = {})
|
||||
: util::exception("Promise already contains a value or exception", std::move(stacktrace))
|
||||
{}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
|
@ -230,8 +254,10 @@ namespace psemek::async
|
|||
state_->value = value;
|
||||
}
|
||||
state_->value_cv.notify_all();
|
||||
if (state_->then_func)
|
||||
state_->then_func(*state_->value);
|
||||
|
||||
std::lock_guard lock{state_->then_mutex};
|
||||
for (auto & func : state_->then_funcs)
|
||||
func(*state_->value);
|
||||
}
|
||||
|
||||
void set_value(T && value)
|
||||
|
|
@ -243,8 +269,10 @@ namespace psemek::async
|
|||
state_->value = std::move(value);
|
||||
}
|
||||
state_->value_cv.notify_all();
|
||||
if (state_->then_func)
|
||||
state_->then_func(*state_->value);
|
||||
|
||||
std::lock_guard lock{state_->then_mutex};
|
||||
for (auto & func : state_->then_funcs)
|
||||
func(*state_->value);
|
||||
}
|
||||
|
||||
void set_exception(std::exception_ptr e)
|
||||
|
|
@ -254,6 +282,10 @@ namespace psemek::async
|
|||
std::lock_guard lock{state_->value_mutex};
|
||||
if (state_->value || state_->exception) throw satisfied_promise_error{};
|
||||
state_->exception = std::move(e);
|
||||
|
||||
std::lock_guard lock_then{state_->then_mutex};
|
||||
for (auto & func : state_->then_exception_funcs)
|
||||
func(state_->exception);
|
||||
}
|
||||
state_->value_cv.notify_all();
|
||||
}
|
||||
|
|
@ -315,8 +347,10 @@ namespace psemek::async
|
|||
state_->value = true;
|
||||
}
|
||||
state_->value_cv.notify_all();
|
||||
if (state_->then_func)
|
||||
state_->then_func();
|
||||
|
||||
std::lock_guard lock{state_->then_mutex};
|
||||
for (auto & func : state_->then_funcs)
|
||||
func();
|
||||
}
|
||||
|
||||
void set_exception(std::exception_ptr e)
|
||||
|
|
@ -326,6 +360,10 @@ namespace psemek::async
|
|||
std::lock_guard lock{state_->value_mutex};
|
||||
if (state_->value || state_->exception) throw satisfied_promise_error{};
|
||||
state_->exception = std::move(e);
|
||||
|
||||
std::lock_guard lock_then{state_->then_mutex};
|
||||
for (auto & func : state_->then_exception_funcs)
|
||||
func(state_->exception);
|
||||
}
|
||||
state_->value_cv.notify_all();
|
||||
}
|
||||
|
|
@ -353,6 +391,14 @@ namespace psemek::async
|
|||
return p.get_future();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
future<T> make_ready_future(T const & x)
|
||||
{
|
||||
promise<T> p;
|
||||
p.set_value(x);
|
||||
return p.get_future();
|
||||
}
|
||||
|
||||
template <typename Signature>
|
||||
struct packaged_task;
|
||||
|
||||
|
|
@ -479,7 +525,7 @@ namespace psemek::async
|
|||
auto fut = t.get_future();
|
||||
|
||||
std::lock_guard lock{state_->then_mutex};
|
||||
state_->then_func = std::move(t);
|
||||
state_->then_funcs.push_back(std::move(t));
|
||||
return fut;
|
||||
}
|
||||
else
|
||||
|
|
@ -489,7 +535,7 @@ namespace psemek::async
|
|||
auto fut = t.get_future();
|
||||
|
||||
std::lock_guard lock{state_->then_mutex};
|
||||
state_->then_func = std::move(t);
|
||||
state_->then_funcs.push_back(std::move(t));
|
||||
return fut;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
libs/async/include/psemek/async/synchronous_executor.hpp
Normal file
28
libs/async/include/psemek/async/synchronous_executor.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/async/executor.hpp>
|
||||
|
||||
namespace psemek::async
|
||||
{
|
||||
|
||||
struct synchronous_executor
|
||||
: executor
|
||||
{
|
||||
void post(task t) override;
|
||||
|
||||
void post_at(clock::time_point time, task t) override;
|
||||
|
||||
void stop() override;
|
||||
|
||||
void clear() override;
|
||||
|
||||
void wait() override;
|
||||
|
||||
void wait_for(clock::duration period) override;
|
||||
|
||||
void wait_until(clock::time_point time) override;
|
||||
|
||||
std::size_t task_count() const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
#include <psemek/util/thread.hpp>
|
||||
#include <psemek/util/synchronyzed_queue.hpp>
|
||||
|
||||
#include <future>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
|
@ -24,12 +23,16 @@ namespace psemek::async
|
|||
|
||||
~threadpool() override { stop(); }
|
||||
|
||||
std::size_t thread_count() const { return threads_.size(); }
|
||||
|
||||
void post(task t) override;
|
||||
|
||||
void post_at(clock::time_point time, task t) override;
|
||||
|
||||
void stop() override;
|
||||
|
||||
void clear() override;
|
||||
|
||||
void wait() override;
|
||||
|
||||
void wait_for(clock::duration period) override;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#include <psemek/async/event_loop.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace psemek::async
|
||||
{
|
||||
|
||||
|
|
@ -20,6 +18,11 @@ namespace psemek::async
|
|||
queue_.clear();
|
||||
}
|
||||
|
||||
void event_loop::clear()
|
||||
{
|
||||
queue_.clear();
|
||||
}
|
||||
|
||||
void event_loop::wait()
|
||||
{
|
||||
while (!queue_.empty())
|
||||
|
|
|
|||
37
libs/async/source/synchronous_executor.cpp
Normal file
37
libs/async/source/synchronous_executor.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include <psemek/async/synchronous_executor.hpp>
|
||||
#include <psemek/util/exception.hpp>
|
||||
|
||||
namespace psemek::async
|
||||
{
|
||||
|
||||
void synchronous_executor::post(task t)
|
||||
{
|
||||
t();
|
||||
}
|
||||
|
||||
void synchronous_executor::post_at(clock::time_point, task)
|
||||
{
|
||||
throw util::exception("Synchronous_executor doesn't support executor::post_at");
|
||||
}
|
||||
|
||||
void synchronous_executor::stop()
|
||||
{}
|
||||
|
||||
void synchronous_executor::clear()
|
||||
{}
|
||||
|
||||
void synchronous_executor::wait()
|
||||
{}
|
||||
|
||||
void synchronous_executor::wait_for(clock::duration)
|
||||
{}
|
||||
|
||||
void synchronous_executor::wait_until(clock::time_point)
|
||||
{}
|
||||
|
||||
std::size_t synchronous_executor::task_count() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,19 +2,14 @@
|
|||
|
||||
#include <psemek/util/unused.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/util/exception.hpp>
|
||||
#include <psemek/prof/profiler.hpp>
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
namespace psemek::async
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct stop_execution{};
|
||||
|
||||
}
|
||||
|
||||
threadpool::threadpool(std::string const & name, std::size_t thread_count)
|
||||
: working_count_{0}
|
||||
{
|
||||
|
|
@ -24,10 +19,13 @@ namespace psemek::async
|
|||
threads_.emplace_back([this, tname = std::move(tname)]() mutable
|
||||
{
|
||||
log::thread_registrator reg(std::move(tname));
|
||||
for (bool running = true; running;)
|
||||
while (true)
|
||||
{
|
||||
auto task = task_queue_.pop();
|
||||
|
||||
if (!task)
|
||||
break;
|
||||
|
||||
{
|
||||
std::lock_guard lock{working_count_mutex_};
|
||||
++working_count_;
|
||||
|
|
@ -35,11 +33,12 @@ namespace psemek::async
|
|||
|
||||
try
|
||||
{
|
||||
prof::profiler prof("task");
|
||||
task();
|
||||
}
|
||||
catch (stop_execution const &)
|
||||
catch (util::exception const & e)
|
||||
{
|
||||
running = false;
|
||||
log::error() << "Unhandled exception in threadpool executor: " << e;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
|
|
@ -81,11 +80,16 @@ namespace psemek::async
|
|||
for (auto const & thread: threads_)
|
||||
{
|
||||
unused(thread);
|
||||
task_queue_.push([]{ throw stop_execution{}; });
|
||||
task_queue_.push(nullptr);
|
||||
}
|
||||
threads_.clear();
|
||||
}
|
||||
|
||||
void threadpool::clear()
|
||||
{
|
||||
task_queue_.clear();
|
||||
}
|
||||
|
||||
void threadpool::wait()
|
||||
{
|
||||
std::unique_lock lock{working_count_mutex_};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
find_package(SDL2_mixer REQUIRED)
|
||||
|
||||
file(GLOB_RECURSE PSEMEK_AUDIO_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp" "include/*.h")
|
||||
file(GLOB_RECURSE PSEMEK_AUDIO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp" "source/*.c")
|
||||
file(GLOB_RECURSE PSEMEK_AUDIO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
|
||||
|
||||
psemek_add_library(psemek-audio ${PSEMEK_AUDIO_HEADERS} ${PSEMEK_AUDIO_SOURCES})
|
||||
target_include_directories(psemek-audio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-audio PUBLIC psemek-sdl2 psemek-geom psemek-util psemek-log psemek-prof SDL2_mixer)
|
||||
target_link_libraries(psemek-audio PUBLIC psemek-random psemek-math psemek-util psemek-log psemek-prof)
|
||||
|
|
|
|||
1592
libs/audio/include/psemek/audio/audio_file/AudioFile.h
Normal file
1592
libs/audio/include/psemek/audio/audio_file/AudioFile.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <version>
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#ifdef __cpp_lib_atomic_shared_ptr
|
||||
#include <atomic>
|
||||
#else
|
||||
#include <psemek/util/mutexed.hpp>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::audio
|
||||
|
|
@ -18,12 +24,12 @@ namespace psemek::audio
|
|||
|
||||
stream_ptr stream() const
|
||||
{
|
||||
return std::atomic_load(&stream_);
|
||||
return stream_.load();
|
||||
}
|
||||
|
||||
stream_ptr stream(stream_ptr new_stream)
|
||||
{
|
||||
return std::atomic_exchange(&stream_, std::move(new_stream));
|
||||
return stream_.exchange(std::move(new_stream));
|
||||
}
|
||||
|
||||
stream_ptr stop()
|
||||
|
|
@ -33,11 +39,15 @@ namespace psemek::audio
|
|||
|
||||
bool is_stopped() const
|
||||
{
|
||||
return stream() != nullptr;
|
||||
return stream() == nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
stream_ptr stream_;
|
||||
#ifdef __cpp_lib_atomic_shared_ptr
|
||||
std::atomic<stream_ptr> stream_;
|
||||
#else
|
||||
util::mutexed<stream_ptr> stream_;
|
||||
#endif
|
||||
};
|
||||
|
||||
using channel_ptr = std::shared_ptr<channel>;
|
||||
|
|
|
|||
12
libs/audio/include/psemek/audio/combine/concat.hpp
Normal file
12
libs/audio/include/psemek/audio/combine/concat.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
stream_ptr concat(std::vector<stream_ptr> streams);
|
||||
|
||||
}
|
||||
16
libs/audio/include/psemek/audio/combine/duplicate.hpp
Normal file
16
libs/audio/include/psemek/audio/combine/duplicate.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/track.hpp>
|
||||
#include <psemek/audio/recorder.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
track_ptr make_duplicator(std::shared_ptr<recorder> recorder);
|
||||
track_ptr make_duplicator(stream_ptr stream);
|
||||
std::pair<stream_ptr, stream_ptr> duplicate(stream_ptr stream);
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/track.hpp>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
stream_ptr loop(track_ptr track, std::optional<std::size_t> count = {});
|
||||
stream_ptr loop(stream_ptr stream, std::optional<std::size_t> count = {});
|
||||
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
#include <psemek/audio/channel.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
|
@ -12,10 +13,14 @@ namespace psemek::audio
|
|||
: stream
|
||||
{
|
||||
virtual channel_ptr add(stream_ptr stream) = 0;
|
||||
|
||||
virtual std::size_t stream_count() const = 0;
|
||||
};
|
||||
|
||||
using mixer_ptr = std::shared_ptr<mixer>;
|
||||
|
||||
mixer_ptr make_mixer();
|
||||
|
||||
mixer_ptr mix(std::vector<stream_ptr> const & streams);
|
||||
|
||||
}
|
||||
|
|
@ -9,14 +9,24 @@ namespace psemek::audio
|
|||
constexpr int frequency = 44100;
|
||||
constexpr float inv_frequency = 1.f / frequency;
|
||||
|
||||
inline std::int64_t seconds_to_frames(float seconds)
|
||||
{
|
||||
return static_cast<std::int64_t>(std::round(seconds * frequency));
|
||||
}
|
||||
|
||||
inline std::int64_t seconds_to_samples(float seconds)
|
||||
{
|
||||
return static_cast<std::int64_t>(2 * std::round(seconds * frequency));
|
||||
return 2 * seconds_to_frames(seconds);
|
||||
}
|
||||
|
||||
inline float frames_to_seconds(std::int64_t frames)
|
||||
{
|
||||
return static_cast<float>(frames) * inv_frequency;
|
||||
}
|
||||
|
||||
inline float samples_to_seconds(std::int64_t samples)
|
||||
{
|
||||
return static_cast<float>(samples) * 0.5f * inv_frequency;
|
||||
return frames_to_seconds(samples / 2);
|
||||
}
|
||||
|
||||
inline float to_db(float amplitude)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/constants.hpp>
|
||||
#include <psemek/geom/constants.hpp>
|
||||
#include <psemek/math/constants.hpp>
|
||||
|
||||
#include <complex>
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ namespace psemek::audio
|
|||
|
||||
void frequency(float f)
|
||||
{
|
||||
m_ = std::exp(std::complex<float>{0.f, 2.f * geom::pi * f * inv_frequency});
|
||||
m_ = std::exp(std::complex<float>{0.f, 2.f * math::pi * f * inv_frequency});
|
||||
}
|
||||
|
||||
std::complex<float> phase() const
|
||||
|
|
@ -26,6 +26,8 @@ namespace psemek::audio
|
|||
return result_;
|
||||
}
|
||||
|
||||
std::vector<float> grab_result();
|
||||
|
||||
private:
|
||||
std::vector<float> result_;
|
||||
int position_{0};
|
||||
10
libs/audio/include/psemek/audio/detail/white_noise.hpp
Normal file
10
libs/audio/include/psemek/audio/detail/white_noise.hpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::audio::detail
|
||||
{
|
||||
|
||||
std::vector<float> white_noise(std::size_t sample_count);
|
||||
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/track.hpp>
|
||||
#include <psemek/audio/recorder.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
track_ptr make_duplicator(std::shared_ptr<recorder> recorder);
|
||||
|
||||
inline track_ptr make_duplicator(stream_ptr stream)
|
||||
{
|
||||
return make_duplicator(make_recorder(std::move(stream)));
|
||||
}
|
||||
|
||||
inline std::pair<stream_ptr, stream_ptr> duplicate(stream_ptr stream)
|
||||
{
|
||||
auto track = make_duplicator(std::move(stream));
|
||||
return {track->stream(), track->stream()};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -10,58 +10,87 @@ namespace psemek::audio
|
|||
|
||||
struct duration
|
||||
{
|
||||
private:
|
||||
struct samples_tag{};
|
||||
struct frames_tag{};
|
||||
|
||||
public:
|
||||
|
||||
duration()
|
||||
: samples_{0}
|
||||
: frames_{0}
|
||||
{}
|
||||
|
||||
duration(std::int64_t samples)
|
||||
: samples_{samples}
|
||||
{}
|
||||
static duration from_samples(std::int64_t samples)
|
||||
{
|
||||
return duration(samples_tag{}, samples);
|
||||
}
|
||||
|
||||
duration(std::size_t samples)
|
||||
: samples_{samples}
|
||||
{}
|
||||
static duration from_frames(std::int64_t samples)
|
||||
{
|
||||
return duration(frames_tag{}, samples);
|
||||
}
|
||||
|
||||
duration(float seconds)
|
||||
: samples_{seconds_to_samples(seconds)}
|
||||
: frames_{seconds_to_frames(seconds)}
|
||||
{}
|
||||
|
||||
duration(duration const & other) = default;
|
||||
duration(duration const & other)
|
||||
: frames_{other.frames_}
|
||||
{}
|
||||
|
||||
float seconds() const
|
||||
{
|
||||
return samples_to_seconds(samples_);
|
||||
return frames_to_seconds(frames_);
|
||||
}
|
||||
|
||||
std::int64_t frames() const
|
||||
{
|
||||
return frames_;
|
||||
}
|
||||
|
||||
std::int64_t samples() const
|
||||
{
|
||||
return samples_;
|
||||
return frames_ * 2;
|
||||
}
|
||||
|
||||
duration & operator = (duration const & other)
|
||||
{
|
||||
frames_ = other.frames_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
duration & operator += (duration const & other)
|
||||
{
|
||||
samples_ += other.samples_;
|
||||
frames_ += other.frames_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
duration & operator -= (duration const & other)
|
||||
{
|
||||
samples_ -= other.samples_;
|
||||
frames_ -= other.frames_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend duration operator + (duration const & d1, duration const & d2)
|
||||
{
|
||||
return duration{d1.samples() + d2.samples()};
|
||||
return duration{d1.frames() + d2.frames()};
|
||||
}
|
||||
|
||||
friend duration operator - (duration const & d1, duration const & d2)
|
||||
{
|
||||
return duration{d1.samples() - d2.samples()};
|
||||
return duration{d1.frames() - d2.frames()};
|
||||
}
|
||||
|
||||
private:
|
||||
std::int64_t samples_;
|
||||
std::int64_t frames_;
|
||||
|
||||
duration(samples_tag, std::int64_t samples)
|
||||
: frames_(samples / 2)
|
||||
{}
|
||||
|
||||
duration(frames_tag, std::int64_t frames)
|
||||
: frames_(frames)
|
||||
{}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
13
libs/audio/include/psemek/audio/effect/all_pass.hpp
Normal file
13
libs/audio/include/psemek/audio/effect/all_pass.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/duration.hpp>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
// All-pass filter
|
||||
// Turns x[n] into y[n] = - gain * x[n] + x[n - delay] + gain * y[n - delay]
|
||||
stream_ptr all_pass(stream_ptr stream, duration delay, float gain);
|
||||
|
||||
}
|
||||
13
libs/audio/include/psemek/audio/effect/echo.hpp
Normal file
13
libs/audio/include/psemek/audio/effect/echo.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/duration.hpp>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
// Comb filter
|
||||
// Turns x[n] into y[n] = x[n - delay] + gain * y[n - delay]
|
||||
stream_ptr echo(stream_ptr stream, duration delay, float gain);
|
||||
|
||||
}
|
||||
17
libs/audio/include/psemek/audio/effect/filter.hpp
Normal file
17
libs/audio/include/psemek/audio/effect/filter.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
// Turns x[n] into y[n] = a0*x[n] + a1*x[n-1]
|
||||
stream_ptr feedforward_filter(stream_ptr stream, float a0, float a1);
|
||||
|
||||
// Turns x[n] into y[n] = a0*x[n] + b1*y[n-1]
|
||||
stream_ptr feedback_filter(stream_ptr stream, float a0, float b1);
|
||||
|
||||
stream_ptr low_pass_filter(stream_ptr stream);
|
||||
stream_ptr high_pass_filter(stream_ptr stream);
|
||||
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ namespace psemek::audio
|
|||
float smoothness() const;
|
||||
float smoothness(float value);
|
||||
|
||||
void apply(float * data, std::size_t sample_count);
|
||||
void apply(util::span<float> samples);
|
||||
|
||||
private:
|
||||
std::atomic<float> gain_[2];
|
||||
|
|
|
|||
|
|
@ -1,30 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/track.hpp>
|
||||
#include <psemek/audio/channel.hpp>
|
||||
|
||||
#include <psemek/util/pimpl.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
|
||||
#include <psemek/io/stream.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
struct engine
|
||||
{
|
||||
engine();
|
||||
~engine();
|
||||
virtual channel_ptr output() = 0;
|
||||
|
||||
channel_ptr output();
|
||||
|
||||
private:
|
||||
psemek_declare_pimpl
|
||||
virtual ~engine() {}
|
||||
};
|
||||
|
||||
// Implemented by platform backend
|
||||
std::unique_ptr<engine> make_engine();
|
||||
|
||||
}
|
||||
|
|
|
|||
13
libs/audio/include/psemek/audio/midi.hpp
Normal file
13
libs/audio/include/psemek/audio/midi.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
inline float midi_frequency(float note)
|
||||
{
|
||||
return 440.f * std::pow(2.f, (note - 69.f) / 12.f);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/track.hpp>
|
||||
#include <psemek/audio/channel.hpp>
|
||||
#include <psemek/audio/duration.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
|
@ -10,17 +13,21 @@ namespace psemek::audio
|
|||
|
||||
struct recorder
|
||||
{
|
||||
virtual std::optional<std::size_t> length() const = 0;
|
||||
virtual channel_ptr channel() = 0;
|
||||
|
||||
virtual std::size_t request(std::size_t samples) = 0;
|
||||
|
||||
virtual util::span<float const> buffer() const = 0;
|
||||
|
||||
virtual std::vector<float> grab_buffer() = 0;
|
||||
|
||||
virtual ~recorder() {}
|
||||
};
|
||||
|
||||
std::shared_ptr<recorder> make_recorder();
|
||||
std::shared_ptr<recorder> make_recorder(stream_ptr stream);
|
||||
std::shared_ptr<recorder> make_recorder(std::vector<float> samples);
|
||||
std::shared_ptr<recorder> make_recorder(util::span<float const> samples);
|
||||
|
||||
std::shared_ptr<track> record(stream_ptr stream);
|
||||
std::shared_ptr<track> record(stream_ptr stream, duration duration);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/util/span.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
|
@ -14,7 +16,7 @@ namespace psemek::audio
|
|||
|
||||
// Return value less than sample count means end of stream
|
||||
// Must be called from mixing thread
|
||||
virtual std::size_t read(float * data, std::size_t sample_count) = 0;
|
||||
virtual std::size_t read(util::span<float> samples) = 0;
|
||||
|
||||
// The number of samples already played from this stream
|
||||
virtual std::size_t played() const = 0;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
#include <psemek/util/blob.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
|
@ -20,12 +22,16 @@ namespace psemek::audio
|
|||
using track_ptr = std::shared_ptr<track>;
|
||||
|
||||
track_ptr load_raw(util::span<float const> data);
|
||||
track_ptr load_raw(util::blob data);
|
||||
track_ptr load_raw(std::vector<float> data);
|
||||
|
||||
track_ptr load_wav(util::span<char const> data);
|
||||
track_ptr load_wav(std::vector<char> const & data);
|
||||
|
||||
track_ptr load_mp3(util::span<char const> data);
|
||||
track_ptr load_mp3(util::blob data);
|
||||
track_ptr load_mp3(std::vector<char> data);
|
||||
|
||||
track_ptr load_lazy(std::filesystem::path const & path);
|
||||
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue