diff --git a/Cargo.lock b/Cargo.lock index adc92ca..caeae81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,10 +309,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "bevy" -version = "0.11.0" +name = "base64" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04a90fe8e9c03fa2d30acf39a5178a48526df00c1ccea2fc43fa6d9ca4d8a168" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "bevy" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e71a9143ac21bed247c30129399af8be170309e7ff5983a1bd37e87d3da520" dependencies = [ "bevy_internal", ] @@ -353,14 +359,14 @@ checksum = "0edba455601861b8e8b76128ae5d46dd968114edde60f0ac3d2c21535a947548" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] name = "bevy_a11y" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f758f437d9d862bf10a8e3a0f76b426095c19a87d118c945dcb935358d856076" +checksum = "3d87d5753cefaeb5f5c5d5e937844f5386eabaf9ab95f3013e86d8fb438d424e" dependencies = [ "accesskit", "bevy_app", @@ -370,9 +376,9 @@ dependencies = [ [[package]] name = "bevy_animation" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d130cb8b7e2b81304591c5c8e511accd2df58b8d8185ab4836ed2f377e6a61f" +checksum = "cc510d47ec4813359b7e715edc6976380d4244833feae124977468994554a0ab" dependencies = [ "bevy_app", "bevy_asset", @@ -389,9 +395,9 @@ dependencies = [ [[package]] name = "bevy_app" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1817e8d5b1146ea9e7730a7264d3470394840e0754d15abded26473f867967a0" +checksum = "3cb660188d5d4ceaead6d5861ce22ecedc08b68c385cc8edf0a3c0c0285560bf" dependencies = [ "bevy_derive", "bevy_ecs", @@ -405,11 +411,12 @@ dependencies = [ [[package]] name = "bevy_asset" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e12f951d4af2ad4ad230cd7bcb05248149c415eec17c34bf26731c4cd8b897f" +checksum = "6733d20a22bb10da928785fdbf6fad6cfb1c7da4a8c170ab3adbba5862c375d5" dependencies = [ "anyhow", + "async-channel", "bevy_app", "bevy_diagnostic", "bevy_ecs", @@ -432,10 +439,35 @@ dependencies = [ ] [[package]] -name = "bevy_audio" -version = "0.11.0" +name = "bevy_asset_loader" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6bade3f5389f9463e150af874aebe672b5101df4268d28b0109a66f9cdce56e" +checksum = "67504fe2bfe56b0c58a2e042a7f5b1dd242a9b9f28e78e3fe63e2c3d62692bea" +dependencies = [ + "anyhow", + "bevy", + "bevy_asset_loader_derive", + "bevy_common_assets", + "path-slash", + "serde", +] + +[[package]] +name = "bevy_asset_loader_derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0d73cdac61fd6f9cb4df9c936ffe6d1343a63470c91c6a6823e1664d298fcb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bevy_audio" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f505704c3a8396c2d9dde898e19aee114143584b11bdb7189fcafc231b6e7a" dependencies = [ "anyhow", "bevy_app", @@ -452,10 +484,22 @@ dependencies = [ ] [[package]] -name = "bevy_core" -version = "0.11.0" +name = "bevy_common_assets" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "263b6a943ecba176c8390a1100615021f61a3b2d7a87e8eecf4009b6ed4457e0" +checksum = "a0e5659f20aeaa1703e76d87c62d66f92aaa56e431fbed71bb38345b576aa6f0" +dependencies = [ + "anyhow", + "bevy", + "ron", + "serde", +] + +[[package]] +name = "bevy_core" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d47b435bdebeefedda95de98a419c4d3b4c160ed8ce3470ea358a16aad6038" dependencies = [ "bevy_app", "bevy_ecs", @@ -469,9 +513,9 @@ dependencies = [ [[package]] name = "bevy_core_pipeline" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c70113b5c4106855b888f96d8574697eb9082713f976c9b6487c1f5ab28589" +checksum = "a01c2652f5a6d24e0ab465e6feca8a034dfb33dfefbcdc19e436fec879a362a8" dependencies = [ "bevy_app", "bevy_asset", @@ -490,20 +534,20 @@ dependencies = [ [[package]] name = "bevy_derive" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1477347b17df781756ba0dfd677e2975e57e930752cd3cd42e6cdd8fdaa3223" +checksum = "c5cc78985f4d0ad1fd7b8ead06dcfaa192685775a7b1be158191c788c7d52298" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] name = "bevy_diagnostic" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a594f970c261007cdd3edeccd61651c2cb4513de3d0b8b35d93f5d9c32c059" +checksum = "ee4e366724d233fdc7e282e1415f4cd570e97fbb8443e5a8ff3f8cc5ae253ffd" dependencies = [ "bevy_app", "bevy_core", @@ -516,9 +560,9 @@ dependencies = [ [[package]] name = "bevy_ecs" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "032c81ba7d919c1004b0abc33cc6c588c8f896a4d7c55a7c7aa1e46382242f43" +checksum = "fb6fd0ec64cd32b8fcf16157173431ba0e675b29c4643a8d6c9a9eeef6f93c32" dependencies = [ "async-channel", "bevy_ecs_macros", @@ -537,14 +581,14 @@ dependencies = [ [[package]] name = "bevy_ecs_macros" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15ff7fcafdb8fe464ddd300b4860a76d5c6f9d684472e4bf21852d6f0ff3991" +checksum = "e13b0fd864433db6ff825efd0eb86b2690e208151905b184cc9bfd2c3ac66c3b" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -600,9 +644,9 @@ dependencies = [ [[package]] name = "bevy_encase_derive" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdf808dbdc68a0c519e09026c627bda85250205a40ac02794866bff254d6b56" +checksum = "da8ffb3d214ee4d8b7e851bc8409768a0f18c872c3a25065c248611ff832180e" dependencies = [ "bevy_macro_utils", "encase_derive_impl", @@ -610,9 +654,9 @@ dependencies = [ [[package]] name = "bevy_gilrs" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b877a371caa64edd6ec5d66b47c67b9e9e9acff2f3bcc51e31e175463e89f6ba" +checksum = "b84a2fbca3811261bcf02908132096595b81e5ec033169f922d6077f57baabb7" dependencies = [ "bevy_app", "bevy_ecs", @@ -626,9 +670,9 @@ dependencies = [ [[package]] name = "bevy_gizmos" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7938b43b4bdf9d039b7d3b310f871ed5ffa5a185e861a9c85731c40182019f8d" +checksum = "64c08196fcb36b7175577443cbe2c1193d596a15eb0fa210bae378e6e1478876" dependencies = [ "bevy_app", "bevy_asset", @@ -646,12 +690,12 @@ dependencies = [ [[package]] name = "bevy_gltf" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09b699698a2f5843ef63064010a5e7783403f99a697a04f41a2f8141cb4245d" +checksum = "478c832d8b132198ca9485c636c97eaea7a1fe393dabadbcabc693ef4437e0db" dependencies = [ "anyhow", - "base64", + "base64 0.13.1", "bevy_animation", "bevy_app", "bevy_asset", @@ -676,21 +720,53 @@ dependencies = [ ] [[package]] -name = "bevy_gltf_components" -version = "0.2.0" +name = "bevy_gltf_blueprints" +version = "0.1.0" dependencies = [ "bevy", + "bevy_gltf_components 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bevy_gltf_components" +version = "0.1.0" +dependencies = [ + "bevy", + "ron", + "serde", +] + +[[package]] +name = "bevy_gltf_components" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168c88211436e833f6f5104d2f2db8a9f3d705b6c6ae22b921f1be6b80b7d4cb" +dependencies = [ + "bevy", + "ron", + "serde", +] + +[[package]] +name = "bevy_gltf_flow" +version = "0.3.0" +dependencies = [ + "bevy", + "bevy_asset_loader", "bevy_editor_pls", + "bevy_gltf_blueprints", + "bevy_gltf_components 0.1.0", "bevy_rapier3d", + "rand", "ron", "serde", ] [[package]] name = "bevy_hierarchy" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba41e1bb0c367b31e59b53ab858de56764c78bee87c121843c1ff033efa0086c" +checksum = "402789ee53acf345243cf2c86a895d6cf8631e533780ed261c6ecf891eb050b8" dependencies = [ "bevy_app", "bevy_core", @@ -703,9 +779,9 @@ dependencies = [ [[package]] name = "bevy_input" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7221091c7b219a63a1f3f019512e8b72bed673230b97c3fcbca37ba566b1cffb" +checksum = "b9dfd9c768cf153f3fc807661996b2db44b824299860ba198fb3b92dd731bdd8" dependencies = [ "bevy_app", "bevy_ecs", @@ -718,9 +794,9 @@ dependencies = [ [[package]] name = "bevy_internal" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f232e7bd2566abd05656789e3c6278a5ca2a24f1232dff525e5b0233a99a610" +checksum = "1006f2c501bceb1aef5cc18ed07eb822f295763227b83ba6887e6743698af9f8" dependencies = [ "bevy_a11y", "bevy_animation", @@ -757,9 +833,9 @@ dependencies = [ [[package]] name = "bevy_log" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487dfd1fc75fada8f3f2f4773addf3fbba53a2a91cb913616e6dc6c26dd62995" +checksum = "f0b1e5ca5b217dd384a3bf9186df0d0da757035f9f06d8eec0ebe62cffc05c34" dependencies = [ "android_log-sys", "bevy_app", @@ -773,21 +849,21 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3868e555723249fde3786891f35893b3001b2be4efb51f431467cb7fc378cd" +checksum = "d1cd460205fe05634d58b32d9bb752b1b4eaf32b2d29cbd4161ba35eb44a2f8c" dependencies = [ "quote", "rustc-hash", - "syn 2.0.27", + "syn 2.0.37", "toml_edit", ] [[package]] name = "bevy_math" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25088c6598fe0b8ded992c781dc49e613993c7a4e6a731c0f2ab0408add6afdb" +checksum = "267f2ec44aa948051768b1320c2dbff0871799e0a3b746f5fe5b6ee7258fbaf5" dependencies = [ "glam", "serde", @@ -795,9 +871,9 @@ dependencies = [ [[package]] name = "bevy_mikktspace" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99dde80034660f7dbb473141c31f0a746acc7229f5a06ce769aba5f16fd592ab" +checksum = "0d98eaa7f40b97bb9b2d681ecf9fd439830a7eb88ad2846680d4f4acd285cf84" dependencies = [ "glam", ] @@ -819,9 +895,9 @@ dependencies = [ [[package]] name = "bevy_pbr" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3efec2ae4b4f9fd38b82b93350499dac2dc6f07e63ef50a03c00c52075e2dea8" +checksum = "9386944248ac8fcaaabec2302b0e7cd8196ef5d7b7a2e63b10ace3eeb813d3de" dependencies = [ "bevy_app", "bevy_asset", @@ -842,9 +918,9 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c74fcf37593a0053f539c3b088f34f268cbefed031d8eb8ff0fb10d175160242" +checksum = "15702dff420fac72c2ab92428a8600e079ae89c5845401c4e39b843665a3d2d0" [[package]] name = "bevy_rapier3d" @@ -862,9 +938,9 @@ dependencies = [ [[package]] name = "bevy_reflect" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362492a6b66f676176705cc06017b012320fa260a9cf4baf3513387e9c05693e" +checksum = "3ac66cccbf1457c5cfc004a0e83569bd4ddc5d6310701f4af6aa81001fe2964a" dependencies = [ "bevy_math", "bevy_ptr", @@ -883,23 +959,23 @@ dependencies = [ [[package]] name = "bevy_reflect_derive" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e974d78eaf1b45e1b4146711b5c16e37c24234e12f3a52f5f2e28332c969d3c" +checksum = "e5a2a1fa784e9a22560b9de350246a159cd59239eb61c7b66824031b3b28abb0" dependencies = [ "bevy_macro_utils", "bit-set", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", "uuid", ] [[package]] name = "bevy_render" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e4b6a82c3a2be1c0d0cbecf62debb8251b72c0ae76285f66265aabc5bf2d37" +checksum = "b2e125de06ffaed8100623843b447fb524dc16f2a2120adce81143d7307278be" dependencies = [ "anyhow", "async-channel", @@ -947,21 +1023,21 @@ dependencies = [ [[package]] name = "bevy_render_macros" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c4d937f966644f5e1e3c9157736acdd36286bcce06142ff9ad25cd71348c09" +checksum = "e283eb4156285d0d4b85ce9b959d080b652165848f0b3f1a8770af6cfad41c34" dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] name = "bevy_scene" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1e00eb30e2053d9fff0802b2f557350b4e66bac58d531de30882048b4e3232" +checksum = "a77172c572239e741283e585433e986dd7f1bfdd7f7489ff10933f59e93cbb04" dependencies = [ "anyhow", "bevy_app", @@ -981,9 +1057,9 @@ dependencies = [ [[package]] name = "bevy_sprite" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f64119444ef9788dcdd05012a60f0fa3b7ddb396d434ebcfc3edefd76c91b5" +checksum = "d7811ade4df81ffa6bae0e293c42d784ad88ce84d2b10fa05801e3c368714581" dependencies = [ "bevy_app", "bevy_asset", @@ -1006,9 +1082,9 @@ dependencies = [ [[package]] name = "bevy_tasks" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faab904296a3d6976bb8a12bc0f42f6c98fb6cd87a96244e0151d359f684ec2d" +checksum = "9200e7b42d49c787d9a08675c425d8bd6393ba3beed2eac64be6027a44a01870" dependencies = [ "async-channel", "async-executor", @@ -1020,9 +1096,9 @@ dependencies = [ [[package]] name = "bevy_text" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b52a19b3d0caf20acd1bdb47b6a00717accc834b46c4f204a63de15cea45ec4b" +checksum = "822e2cdb1c46338b85b7648d866f0b631cab23bd8f24395bb8ee7842dde024f0" dependencies = [ "ab_glyph", "anyhow", @@ -1043,9 +1119,9 @@ dependencies = [ [[package]] name = "bevy_time" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09225ad2ffef14da000080143730b36ba225844ae479e4791cdb9d08066d06a" +checksum = "2ba50bf25c4dc40296b744f77de10d39c8981b710d8dce609da9de5e54ef164b" dependencies = [ "bevy_app", "bevy_ecs", @@ -1058,9 +1134,9 @@ dependencies = [ [[package]] name = "bevy_transform" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da8a0cd3780e120e20be333cc48d41cb74620d798dc61bc18eb2a82d3545e184" +checksum = "86043ec5cc3cf406d33c0e4d6920171601e186b1288e9b4f5ae54682a0564032" dependencies = [ "bevy_app", "bevy_ecs", @@ -1072,9 +1148,9 @@ dependencies = [ [[package]] name = "bevy_ui" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85992eb5809936b3326940dc8d6e7b219af3dde1ecbca5948addd6a78694cc" +checksum = "105c82a79df1dfcdb75941fa1f58dbdbd8f702a740bc39e7bf719c3d51d55286" dependencies = [ "bevy_a11y", "bevy_app", @@ -1102,9 +1178,9 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10bfde141f0cdd15e07bca72f4439a9db80877c283738f581d061972ef483b1b" +checksum = "829eb8d0d06a0baeabc2e8bad74136ed3329b055aa1e11c5d9df09ebb9be3d85" dependencies = [ "ahash 0.8.3", "bevy_utils_proc_macros", @@ -1119,20 +1195,20 @@ dependencies = [ [[package]] name = "bevy_utils_proc_macros" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e37f2e885b0e8af59dc19871c313d3cf2a2495db35bb4d4ae0a61b3f87d5401" +checksum = "0d104f29e231123c703e8b394e2341d2425c33c5a2e9ab8cc8d0a554bdb62a41" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] name = "bevy_window" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0528832361e3d942df287c90537ef6fafb726c4934468a7c3a5d53d659bfbf54" +checksum = "4a66f9963152093220fc5ffd2244cadcc7f79cf2c23dd02076f4d0cbb7f5162f" dependencies = [ "bevy_app", "bevy_ecs", @@ -1146,9 +1222,9 @@ dependencies = [ [[package]] name = "bevy_winit" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c6709dc70cfee1eb94d5f125d29612c4a9345dfc1a70dd3189af927b2fd503" +checksum = "003e20cff652a364f1f98b0d5ba24f53140dc77643241afe4a9b848bdde66184" dependencies = [ "accesskit_winit", "approx", @@ -1218,6 +1294,9 @@ name = "bitflags" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +dependencies = [ + "serde", +] [[package]] name = "block" @@ -1278,7 +1357,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -1720,7 +1799,7 @@ checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -2001,7 +2080,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -2835,7 +2914,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -3048,6 +3127,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -3104,6 +3189,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "pretty-type-name" version = "1.0.1" @@ -3146,9 +3237,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -3174,6 +3265,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" @@ -3305,13 +3426,14 @@ dependencies = [ [[package]] name = "ron" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64", - "bitflags 1.3.2", + "base64 0.21.4", + "bitflags 2.3.3", "serde", + "serde_derive", ] [[package]] @@ -3369,22 +3491,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.175" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -3522,9 +3644,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -3603,7 +3725,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -3679,7 +3801,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", ] [[package]] @@ -3869,7 +3991,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", "wasm-bindgen-shared", ] @@ -3903,7 +4025,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.27", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 2f3c0e4..5730e77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,38 @@ [package] -name = "bevy_gltf_components" -version = "0.2.0" +name = "bevy_gltf_flow" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" +[workspace] +members = [ + "crates/bevy_gltf_components", + "crates/bevy_gltf_blueprints", +] + + [dev-dependencies] -bevy="0.11" +bevy="0.11.2" bevy_rapier3d = { version = "0.22.0", features = [ "serde-serialize", "debug-render-3d", "enhanced-determinism"] } bevy_editor_pls = { git="https://github.com/jakobhellermann/bevy_editor_pls.git" } +bevy_asset_loader = { version = "0.17.0", features = ["standard_dynamic_assets" ]} #version = "0.16", +rand = "0.8.5" [dependencies] -bevy = { version = "0.11", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } +bevy = { version = "0.11.2", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } +bevy_gltf_components = { path = "crates/bevy_gltf_components" } +bevy_gltf_blueprints = { path = "crates/bevy_gltf_blueprints" } + serde = "*" ron="*" [[example]] -name = "general" -path = "examples/general/main.rs" +name = "basic" +path = "examples/basic/main.rs" + +[[example]] +name = "advanced" +path = "examples/advanced/main.rs" #### --------------------Dev/ debug------------------------------- # Enable high optimizations for dependencies (incl. Bevy), but not for our code: diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..ad21aac --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,4 @@ +This crate is available under either: + +* The [MIT License](./LICENSE_MIT) +* The [Apache License, Version 2.0](./LICENSE_APACHE) \ No newline at end of file diff --git a/README.md b/README.md index 5bbc483..574a9c8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ [![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) +[![License](https://img.shields.io/crates/l/bevy_gltf_components)](https://github.com/kaosat-dev/Blender_bevy_components_worklflow/blob/main/License.md) -# bevy_gltf_components +# Blender_bevy_components_worklflow ![demo](./docs/blender_gltf_components.png) -A plugin & tools for adding components from gltf files in the [bevy](https://bevyengine.org/) game engine. +Crates & tools for adding components from gltf files in the [Bevy](https://bevyengine.org/) game engine. It enables minimalistic [Blender](https://www.blender.org/) (gltf) centric workflow for Bevy, ie defining entites & their components inside Blender using Blender's objects **custom properties**. @@ -16,219 +17,46 @@ It also allows you to setup 'blueprints' in Blender by using collections (the re ## Features * Useful if you want to use Blender (or any editor allowing to export gltf with configurable gltf_extras) as your Editor -* define Bevy components as custom properties in Blender (RON, though an older JSON version is also available) -* no plugin or extra tools needed in Blender (but I provide a little Blender plugin to auto-export to gltf on save if you want !) +* define Bevy components as custom properties in Blender (some visually , some using RON, though an older JSON version is also available) +* no plugin or extra tools needed in Blender (but I provide a [little Blender plugin](./tools/gltf_auto_export/README.md) to auto-export to gltf on save (and more !) if you want !) * define components in Blender Collections & override any of them in your collection instances if you want +* ability to automatically turn your Blender collections into [gltf Blueprints](./crates/bevy_gltf_blueprints/README.md) for reuse * minimal setup & code, you can have something basic running fast - -## Usage - -***important*** : the plugin for processing gltf files runs in ***preUpdate*** , so you cannot use the components directly if you spawn your scene from gltf in ***setup*** (the additional components will not show up) -Please see the included example or use bevy_asset_loader for a reliable workflow - -1. Add the crate to your dependencies -```toml -[dependencies] -bevy_gltf_components = { git="https://github.com/kaosat-dev/Blender_bevy_components_worklflow.git" } - -``` -(not on crates.io yet !) - -2. Import - -```rust -use bevy_gltf_components::ComponentsFromGltfPlugin; -``` - -3. Add the plugin -```rust -.add_plugin(ComponentsFromGltfPlugin) -``` - -### Example (without bevy_asset_loader) - -See [here](./examples/general/main.rs) for more details - - ```rust -use bevy::prelude::*; -use bevy_gltf_components::ComponentsFromGltfPlugin; - - -#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] -#[reflect(Component)] -struct TuppleTestF32(f32); - -#[derive(Component, Reflect, Default, Debug, )] -#[reflect(Component)] -/// helper marker component, for demo only -pub struct LoadedMarker; - - -#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] -enum AppState { - #[default] - Loading, - Running, -} - -fn main(){ - App::new() - .add_plugins(( - DefaultPlugins, - ComponentsFromGltfPlugin, - )) - - .add_state::() - .add_systems(Startup, setup) - .add_systems(Update, ( - spawn_level.run_if(in_state(AppState::Loading)), - )) - .run(); -} - -#[derive(Resource)] -struct AssetLoadHelper(Handle); -// we preload the data here, but this is for DEMO PURPOSES ONLY !! Please use https://github.com/NiklasEi/bevy_asset_loader or a similar logic to seperate loading / pre processing -// of assets from the spawning -// AssetLoadHelper is also just for the same purpose, you do not need it in a real scenario -// the states here are also for demo purposes only, -fn setup( - mut commands: Commands, - asset_server: Res, -) { - - let tmp: Handle = asset_server.load("models/level1.glb#Scene0"); - commands.insert_resource(AssetLoadHelper(tmp)); -} - -fn spawn_level( - mut commands: Commands, - scene_markers: Query<&LoadedMarker>, - preloaded_scene: Res, - - mut asset_event_reader: EventReader>, - mut next_state: ResMut>, -){ - - if let Some(asset_event) = asset_event_reader.iter().next() { - match asset_event { - AssetEvent::Created { handle: _ } => { - info!("GLTF loaded"); - if scene_markers.is_empty() { - info!("spawning scene"); - commands.spawn( - ( - SceneBundle { - scene: preloaded_scene.0.clone(), - ..default() - }, - LoadedMarker, - Name::new("Level1") - ) - ); - next_state.set(AppState::Running); - } - } - _ => () - } - } -} - - -``` - - -### Example (with bevy_asset_loader, recommended for ease of use) - -- follow [bevy_asset_loader's](https://github.com/NiklasEi/bevy_asset_loader) docs to setup your gltf asset loading -- also add this plugin - - ```rust -use bevy::prelude::*; -use bevy_gltf_components::ComponentsFromGltfPlugin; - -// replace this with your actual states ! -#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)] -enum AppState { - #[default] - Loading, - Running, -} - - -fn main(){ - App::new() - .add_plugins(( - DefaultPlugins, - ComponentsFromGltfPlugin, - )) - // do the setup for bevy_asset_loader - .add_system(setup_game.in_schedule(OnEnter(AppState::Running))) - - .run(); -} - - -pub fn setup_game( - mut commands: Commands, - game_assets: Res, // assets with your "world" or "level" gltf for example -) { - - - // more fast approach, load the game seperatly - commands.spawn(( - SceneBundle { - scene: game_assets.world.clone(), - ..default() - }, - bevy::prelude::Name::from("world"), - )); - -} - - -``` - -# Workflow with blender / demo information - -This example, is actually closer to a boilerplate + tooling showcases how to use a minimalistic [Blender](https://www.blender.org/) (gltf) centric workflow for [Bevy](https://bevyengine.org/), ie defining entites & their components -inside Blender using Blender's objects **custom properties**. -Aka "Blender as editor for Bevy" - -It also allows you to setup 'blueprints' in Blender by using collections (the recomended way to go most of the time), or directly on single use objects . - -## Features - -* Useful if you want to use Blender (or any editor allowing to export gltf with configurable gltf_extras) as your Editor -* define Bevy components as custom properties in Blender (RON, though an older JSON version is also available) -* no plugin or extra tools needed in Blender (but I provide a little Blender plugin to auto-export to gltf on save if you want !) -* define components in Blender Collections & override any of them in your collection instances if you want -* code to auto add additional required components in Bevy (if B is needed with A but B is not present, it adds it: optional & configurable) -* minimal setup & code, you can have something basic running fast +* minimal dependencies: Bevy, Serde & Ron only ! * opensource -There is a [video tutorial/explanation](https://youtu.be/-lcScjQCA3c) if you want, or you can skip to the text version ahead + +## Crates + +- [bevy_gltf_components]('./crates/bevy_gltf_components/) This crate allows you to define components direclty inside gltf files and instanciate/inject the components on the Bevy side. +There is a [video tutorial/explanation](https://youtu.be/-lcScjQCA3c) if you want, or you can read the crate docs/ basic example + +- [bevy_gltf_blueprints]('./crates/bevy_gltf_blueprints/) This crate adds the ability to define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning. +## Tools +### Blender: gltf_auto_export + +- for convenience I also added a [Blender addon](./tools/gltf_auto_export/README.md) that automatically exports your level/world from Blender to gltf whenever you save your Blend file +- it also supports automatical exports of collections as [Gltf blueprints](./crates/bevy_gltf_blueprints/README.md) & more ! + +Please read the [README]((./tools/gltf_auto_export/README.md)) of the add-on for installation & use instructions + + +## Examples + +- [basic](./examples/basic/) use of ```bevy_gltf_components``` only, to spawn entities with components defined inside gltf files +- [advanced](./examples/advanced/) more advanced example : use of ```bevy_gltf_blueprints``` to spawn a level and then populate it with entities coming from different gltf files, live (at runtime) spawning of entities etc ## Workflow The workflow goes as follows (once you got your Bevy code setup) -### All core/helper modules - -see the [example](./examples/general/) for more information on how to set things up - - -### Then... - - create & register all your components you want to be able to set from the Blender side (this is basic Bevy, no specific work needed) ![component registration](./docs/component_registration.png) - - -- Create a mesh/ collection (for reuse) in Blender +- Create an object / collection (for reuse) in Blender - Go to object properties => add a property, and add your component data - unit structs, enums, and more complex strucs / components are all supported, (if the fields are basic data types at least, have not tried more complex ones yet, but should also work) @@ -242,7 +70,7 @@ see the [example](./examples/general/) for more information on how to set things ![unit struct components in Bevy](./docs/demo_simple_components.png) - (the Rust struct for these components for reference is [here](./examples/general/game.rs#34) ) + (the Rust struct for these components for reference is [here](./examples/basic/game.rs#34) ) ![complex components in Blender](./docs/components_blender_parameters.png) @@ -251,9 +79,9 @@ see the [example](./examples/general/) for more information on how to set things ![complex components in Blender](./docs/camera_tracking_component.png) - (the Rust struct for this component for reference is [here](./examples/general/core/camera/camera_tracking.rs#21) ) + (the Rust struct for this component for reference is [here](./examples/basic/core/camera/camera_tracking.rs#21) ) - There is an other examples of using various Component types: Enums, Tupple structs, strucs with fields etc [here](./examples/general/test_components.rs), + There is an other examples of using various Component types: Enums, Tupple structs, strucs with fields etc [here](./examples/basic/test_components.rs), even colors, Vecs (arrays), Vec2, Vec3 etc are all supported ![complex components in Blender](./docs/components_blender_parameters2.png) @@ -263,17 +91,20 @@ see the [example](./examples/general/) for more information on how to set things * the leaf collections are the assets you use in your level * add an empty called xxxx_components * add the components as explained in the previous part + ![blender collection asset](./docs/blender_collections.png) * In the Level/world itself, just create an instance of the collection (standard Blender, ie Shift+A -> collection instance -> pick the collection) - export your level as a glb/gltf file : - !!**IMPORTANT** you need to check the following: - - custom properties - - cameras & lights if you want a complete level (as in this example) + - using Blender's default gltf exporter + !!**IMPORTANT** you need to check the following: + - custom properties + - cameras & lights if you want a complete level (as in this example) + ![gltf_export](./docs/gltf_export.png) + - or much better, using [gltf_auto_export](./tools/gltf_auto_export/) - ![gltf_export](./docs/gltf_export.png) - load it in Bevy (see the demo main file for this) @@ -286,51 +117,20 @@ see the [example](./examples/general/) for more information on how to set things > note: you get a warning if there are any unregistered components in your gltf file (they get ignored) you will get a warning **per entity** + ![missing components warnings](./docs/component_warnings.png) -### Additional notes - -* You usually define either the Components directly or use ```Proxy components``` that get replaced in Bevy systems with the actual Components that you want (usually when for some reason, ie external crates with unregistered components etc) you cannot use the components directly. - -Included are the following modules / tools - * [```process_gltf```](./src/process_gltfs.rs) the most important module: this is the one extracting ```component``` information from the gltf files - * [```insert_dependant_component```](./examples/general/core/relationships/relationships_insert_dependant_components.rs) a small utility to automatically inject - components that are dependant on an other component - for example an Entity with a Player component should also always have a ShouldBeWithPlayer component - you get a warning if you use this though, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components) - * [```camera```](./examples/general/core/camera/) an example post process/replace proxies plugin, for Camera that also adds CameraTracking functions (to enable a camera to follow an object, ie the player) - * [```lighting```](./examples/general/core/lighting/) an other example post process/replace proxies plugin for lighting, that toggles shadows, lighting config, etc so that things look closer to the original Blender data - * [```physics```](./examples/general/core/physics/) an other example post process/replace proxies plugin for physics, that add [Rapier](https://rapier.rs/docs/user_guides/bevy_plugin/getting_started_bevy) Colliders, Rigidbodies etc . Most of these do not need proxies these days, as the most Rapier components are in the Registry & can be used directly - -Feel free to use as you want, rip it appart, use any/all parts that you need ! - -This tooling and workflow has enabled me to go from a blank Bevy + Blender setup to a working barebones level in very little time (30 minutes or so ?) ! -You can then add your own components & systems for your own gameplay very easilly - -## Information -- the Bevy/ Rust code is [here](./examples/general/main.rs) -- the Blender file is [here](./assets/models/level.blend) -- I added [bevy_editor_pls](https://github.com/jakobhellermann/bevy_editor_pls) as a dependency for convenience so you can inspect your level/components - ## Limitations / issues -- the components have to be defined in ```text``` in Blender, might try using the AppTypeRegistry and some Python code on the Blender side for a nicer UI (although this loses the "fast & easy, no tooling" approach) -- the asset management in this example is stripped down for demo purposes, I normally use https://github.com/NiklasEi/bevy_asset_loader to define, organise and preload assets -(in a different state that comes before the actual game/level), so you will see some of the changes to the level/scene "flash by" +- some components have to be defined in ```text``` in Blender, might try using the AppTypeRegistry and some Python code on the Blender side for a nicer UI (although this loses the "fast & easy, no tooling" approach) - Some of `bevy_rapier`/physics code / ways to define colliders could perhaps be done better within Blender (currently it also goes via RON) +- there seem to be some random system ordering issues that I am still investigating (only when replacing proxy components, no breaking bugs, just restarting your Bevy app is enough) ## Future work - I have a number of other tools/ code helpers that I have not yet included here, because they need cleanup/ might make this example too complex - * gltf spawning tools where you just need to preload gltf files then you can spawn 1...n entities defined in gltf files by name (for example enemies, powerups, etc) * simplified animation logic: ie instead of having to manually specify the animations you need from a gltf file, it is integrated with the spawning system above, which creates a ```Animations``` component in all entities that have an ```AnimationPlayer``` and you can simply query for both to easilly control your animations per entity. -## Blender gltf_auto_export -- for convenience I also added a [Blender addon](./tools/blender_auto_export/README.md) that automatically exports your level/world from Blender to gltf whenever you save your Blend file -(actually when you save inside your level/world scene or in the "library" scene, where I personally usually store all collections to instanciate). -It is **very** barebones and messy, but it does a minimal ok job. Please read the README of the add-on for installation instructions - - ## Credits @@ -338,8 +138,7 @@ It is **very** barebones and messy, but it does a minimal ok job. Please read th ## License -This example, all its code, contents & assets is Dual-licensed under either of - -- Apache License, Version 2.0, ([LICENSE-APACHE](/LICENSE_APACHE) or https://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](/LICENSE_MIT) or https://opensource.org/licenses/MIT) +This repo, all its code, contents & assets is Dual-licensed under either of +- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](./LICENSE_MIT.md) or https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/assets/advanced/advanced.blend b/assets/advanced/advanced.blend new file mode 100644 index 0000000..f906237 Binary files /dev/null and b/assets/advanced/advanced.blend differ diff --git a/assets/advanced/assets_core.assets.ron b/assets/advanced/assets_core.assets.ron new file mode 100644 index 0000000..8d0a099 --- /dev/null +++ b/assets/advanced/assets_core.assets.ron @@ -0,0 +1 @@ +({}) \ No newline at end of file diff --git a/assets/advanced/assets_game.assets.ron b/assets/advanced/assets_game.assets.ron new file mode 100644 index 0000000..6efe66d --- /dev/null +++ b/assets/advanced/assets_game.assets.ron @@ -0,0 +1,6 @@ +({ + "world":File (path: "advanced/models/world.glb#Scene0"), + "models": Folder ( + path: "advanced/models/library", + ), +}) \ No newline at end of file diff --git a/assets/advanced/models/library/Container.glb b/assets/advanced/models/library/Container.glb new file mode 100644 index 0000000..6d900ce Binary files /dev/null and b/assets/advanced/models/library/Container.glb differ diff --git a/assets/advanced/models/library/Health_Pickup.glb b/assets/advanced/models/library/Health_Pickup.glb new file mode 100644 index 0000000..682e172 Binary files /dev/null and b/assets/advanced/models/library/Health_Pickup.glb differ diff --git a/assets/advanced/models/library/MagicTeapot.glb b/assets/advanced/models/library/MagicTeapot.glb new file mode 100644 index 0000000..04a3e57 Binary files /dev/null and b/assets/advanced/models/library/MagicTeapot.glb differ diff --git a/assets/advanced/models/library/Pillar.glb b/assets/advanced/models/library/Pillar.glb new file mode 100644 index 0000000..2115f3a Binary files /dev/null and b/assets/advanced/models/library/Pillar.glb differ diff --git a/assets/advanced/models/library/Player.glb b/assets/advanced/models/library/Player.glb new file mode 100644 index 0000000..4d00af1 Binary files /dev/null and b/assets/advanced/models/library/Player.glb differ diff --git a/assets/advanced/models/world.glb b/assets/advanced/models/world.glb new file mode 100644 index 0000000..b90f122 Binary files /dev/null and b/assets/advanced/models/world.glb differ diff --git a/assets/advanced/scenes/save.scn.ron b/assets/advanced/scenes/save.scn.ron new file mode 100644 index 0000000..ed30389 --- /dev/null +++ b/assets/advanced/scenes/save.scn.ron @@ -0,0 +1,485 @@ +( + resources: {}, + entities: { + 20: ( + components: { + "bevy_render::camera::projection::Projection": Perspective(( + fov: 0.3995965, + aspect_ratio: 1.7777778, + near: 0.1, + far: 100.0, + )), + "bevy_render::primitives::Frustum": (), + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 34.821884, + y: 49.024857, + z: -36.79615, + ), + rotation: (-0.1694689, 0.82838506, 0.40884802, 0.3433684), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core_pipeline::tonemapping::Tonemapping": BlenderFilmic, + "bevy_core_pipeline::tonemapping::DebandDither": Enabled, + "bevy_render::view::ColorGrading": ( + exposure: 0.0, + gamma: 1.0, + pre_saturation: 1.0, + post_saturation: 1.0, + ), + "bevy_core::name::Name": ( + hash: 17702508670109176045, + name: "Camera", + ), + "advanced::core::camera::camera_tracking::CameraTrackingOffset": (( + x: 26.0, + y: 48.0, + z: -26.0, + )), + "bevy_pbr::light::ClusterConfig": FixedZ( + total: 4096, + z_slices: 24, + z_config: ( + first_slice_depth: 5.0, + far_z_mode: MaxLightRange, + ), + dynamic_resizing: true, + ), + "bevy_core_pipeline::bloom::settings::BloomSettings": ( + intensity: 0.01, + low_frequency_boost: 0.7, + low_frequency_boost_curvature: 0.95, + high_pass_frequency: 1.0, + prefilter_settings: ( + threshold: 0.0, + threshold_softness: 0.0, + ), + composite_mode: Additive, + ), + }, + ), + 34: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 4.697565, + y: 1.5983224, + z: 8.962274, + ), + rotation: (0.000000000000000031724054, -0.00000000000000000000647681, -0.000013119204, 1.0), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 9837288155836662016, + name: "Health_Pickup.001", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + }, + ), + 54: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 8.799996, + y: 1.02484, + z: -10.799994, + ), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 17978181434632022651, + name: "Player", + ), + "advanced::core::camera::camera_tracking::CameraTrackable": (), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Player"), + "advanced::game::Player": (), + "advanced::game::SoundMaterial": Wood, + }, + ), + 60: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 3.6351967, + y: 1.7298106, + z: -7.313273, + ), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 7225506896223411979, + name: "MagicTeapot.001", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("MagicTeapot"), + }, + ), + 64: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -4.6068983, + y: 1.5983224, + z: -10.579347, + ), + rotation: (0.000000000000000031724054, 0.00000000000000000000647681, 0.000013119204, 1.0), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 3089896164553476909, + name: "Health_Pickup.002", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + }, + ), + 72: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -11.560788, + y: 0.0, + z: 7.6554174, + ), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 16961132108296874979, + name: "Container.001", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "advanced::game::picking::Pickable": (), + }, + ), + 80: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -21.397858, + y: 0.3833189, + z: -0.32418346, + ), + rotation: (0.0, 0.0, 0.0, 1.0), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 5104740624378885265, + name: "Container.002", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Container"), + "advanced::game::picking::Pickable": (), + }, + ), + 82: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 2.9156065, + y: 1.4984571, + z: 2.1909573, + ), + rotation: (0.058853183, 0.0726243, 0.2048649, 0.97431636), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 107557640935939866, + name: "test5159735758431545549", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -1.2580805, + y: -0.39687577, + z: 0.4816798, + ), + angvel: ( + x: 0.2979751, + y: 0.07926611, + z: 0.8434645, + ), + ), + }, + ), + 86: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 0.26087752, + y: 1.5525806, + z: 1.5980839, + ), + rotation: (0.059497803, -0.0000018232388, 0.13145457, 0.9895351), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 3398656236303073559, + name: "test7470642598731063943", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.9268077, + y: -0.19806683, + z: 0.41948256, + ), + angvel: ( + x: 0.26946256, + y: -0.000006710977, + z: 0.5953494, + ), + ), + }, + ), + 90: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 2.6515265, + y: 1.5944021, + z: -4.391837, + ), + rotation: (-0.030030435, -0.0000006527225, 0.029748484, 0.9991062), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 12541900054595385134, + name: "test3938024405863834719", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.28430828, + y: -0.022357654, + z: -0.2870027, + ), + angvel: ( + x: -0.17986917, + y: -0.0000035613396, + z: 0.17818078, + ), + ), + }, + ), + 94: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: -4.2356462, + y: 1.596993, + z: 0.7254991, + ), + rotation: (-0.0221751, -0.0000000001891749, 0.011065631, 0.99969286), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 6757906322211730861, + name: "test11007490954016878479", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -0.21747473, + y: -0.014912919, + z: -0.43581253, + ), + angvel: ( + x: -0.2727097, + y: -0.0000000034594905, + z: 0.13608481, + ), + ), + }, + ), + 98: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 3.1525247, + y: 1.5518407, + z: -2.9611976, + ), + rotation: (-0.09219627, 0.1602262, -0.11205085, 0.9763565), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 12588565107899185946, + name: "test5980867849331267699", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 0.8323179, + y: -0.20597076, + z: -0.68975484, + ), + angvel: ( + x: -0.37971017, + y: 0.49603412, + z: -0.6079359, + ), + ), + }, + ), + 4294967310: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 4.826278, + y: 1.2710563, + z: -3.1997645, + ), + rotation: (-0.303028, 0.00000087800436, -0.23889118, 0.9225535), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 15533546218717453536, + name: "test12380979123759326444", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 1.2146912, + y: -1.1640646, + z: -1.5408095, + ), + angvel: ( + x: -1.1932359, + y: 0.000002945365, + z: -0.94068503, + ), + ), + }, + ), + 4294967314: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 3.9906094, + y: 1.4824095, + z: 2.4394412, + ), + rotation: (0.06015042, 0.085218765, 0.2215642, 0.9695509), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 2466794778849297109, + name: "test12475628281920299197", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: -1.0818624, + y: -0.37798148, + z: 0.45334253, + ), + angvel: ( + x: 0.25961447, + y: 0.14854014, + z: 0.7426717, + ), + ), + }, + ), + 4294967321: ( + components: { + "bevy_transform::components::transform::Transform": ( + translation: ( + x: 2.2306876, + y: 0.989814, + z: -1.3596333, + ), + rotation: (0.30614096, 0.002587511, -0.42789298, 0.8503991), + scale: ( + x: 1.0, + y: 1.0, + z: 1.0, + ), + ), + "bevy_core::name::Name": ( + hash: 1545925632270385398, + name: "test15780367212768138828", + ), + "bevy_gltf_blueprints::spawn_from_blueprints::BlueprintName": ("Health_Pickup"), + "advanced::game::picking::Pickable": (), + "bevy_rapier3d::dynamics::rigid_body::Velocity": ( + linvel: ( + x: 1.3027526, + y: -1.8947054, + z: 1.6179247, + ), + angvel: ( + x: 1.4565696, + y: -0.16299045, + z: -1.3631926, + ), + ), + }, + ), + }, +) \ No newline at end of file diff --git a/assets/models/test.blend b/assets/basic/models/basic.blend similarity index 100% rename from assets/models/test.blend rename to assets/basic/models/basic.blend diff --git a/assets/models/level1.glb b/assets/basic/models/level1.glb similarity index 67% rename from assets/models/level1.glb rename to assets/basic/models/level1.glb index 881fad4..abcf170 100644 Binary files a/assets/models/level1.glb and b/assets/basic/models/level1.glb differ diff --git a/assets/test.ron b/assets/test.ron deleted file mode 100644 index 587d785..0000000 --- a/assets/test.ron +++ /dev/null @@ -1,3 +0,0 @@ -(( - blender_worklfow::Player: () -)) \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/Cargo.toml b/crates/bevy_gltf_blueprints/Cargo.toml new file mode 100644 index 0000000..881f1ad --- /dev/null +++ b/crates/bevy_gltf_blueprints/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "bevy_gltf_blueprints" +version = "0.1.0" +authors = ["Mark 'kaosat-dev' Moissette"] +description = "Adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy." +homepage = "https://github.com/kaosat-dev/Blender_bevy_components_worklflow" +repository = "https://github.com/kaosat-dev/Blender_bevy_components_worklflow" +keywords = ["gamedev", "bevy", "gltf", "blueprint", "prefab"] +categories = ["game-development"] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dev-dependencies] +bevy="0.11.2" + +[dependencies] +bevy_gltf_components = "0.1.0" +bevy = { version = "0.11.2", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf", "bevy_animation"] } diff --git a/crates/bevy_gltf_blueprints/LICENCE.md b/crates/bevy_gltf_blueprints/LICENCE.md new file mode 100644 index 0000000..ad21aac --- /dev/null +++ b/crates/bevy_gltf_blueprints/LICENCE.md @@ -0,0 +1,4 @@ +This crate is available under either: + +* The [MIT License](./LICENSE_MIT) +* The [Apache License, Version 2.0](./LICENSE_APACHE) \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/LICENSE_APACHE.md b/crates/bevy_gltf_blueprints/LICENSE_APACHE.md new file mode 100644 index 0000000..f748977 --- /dev/null +++ b/crates/bevy_gltf_blueprints/LICENSE_APACHE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [Mark "kaosat-dev" Moissette] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/LICENSE_MIT.md b/crates/bevy_gltf_blueprints/LICENSE_MIT.md new file mode 100644 index 0000000..f8b9094 --- /dev/null +++ b/crates/bevy_gltf_blueprints/LICENSE_MIT.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Mark "kaosat-dev" Moissette + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/README.md b/crates/bevy_gltf_blueprints/README.md new file mode 100644 index 0000000..3f6eda2 --- /dev/null +++ b/crates/bevy_gltf_blueprints/README.md @@ -0,0 +1,146 @@ +[![Crates.io](https://img.shields.io/crates/v/bevy_gltf_blueprints)](https://crates.io/crates/bevy_gltf_blueprints) +[![Docs](https://img.shields.io/docsrs/bevy_gltf_blueprints)](https://docs.rs/bevy_gltf_blueprints/latest/bevy_gltf_blueprints/) +[![License](https://img.shields.io/crates/l/bevy_gltf_blueprints)](https://github.com/kaosat-dev/Blender_bevy_components_worklflow/blob/main/crates/bevy_gltf_blueprints/License.md) +[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) + +# bevy_gltf_blueprints + +Built upon [bevy_gltf_components](https://crates.io/crates/bevy_gltf_components) this crate adds the ability to define Blueprints/Prefabs for [Bevy](https://bevyengine.org/) inside gltf files and spawn them in Bevy. + +A blueprint is a set of **overrideable** components + a hierarchy: ie + + * just a Gltf file with Gltf_extras specifying components + * a component called BlueprintName + +Particularly useful when using [Blender](https://www.blender.org/) as an editor for the [Bevy](https://bevyengine.org/) game engine, combined with the [Blender plugin](https://github.com/kaosat-dev/Blender_bevy_components_worklflow/tree/main/tools/gltf_auto_export) that does a lot of the work for you + + +## Usage + +Here's a minimal usage example: + +```toml +# Cargo.toml +[dependencies] +bevy_gltf_blueprints = { version = "0.1.0"} + +``` + +```rust no_run +use bevy::prelude::*; +use bevy_gltf_blueprints::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(BlueprintsPlugin) + + .run(); +} + +// not shown here: any other setup that is not specific to blueprints + +fn spawn_blueprint( + mut commands: Commands, + keycode: Res>, +){ + if keycode.just_pressed(KeyCode::S) { + let new_entity = commands.spawn(( + BlueprintName("Health_Pickup".to_string()), // mandatory !! + SpawnHere, // mandatory !! + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // VERY important !! + // any other component you want to insert + )); + } +} +``` + +## Setup + +- configure your "library"/"blueprints" path: + advanced/models/library/ + +## Spawning entities from blueprints + +You can spawn entities from blueprints like this: +```rust no_run +commands.spawn(( + BlueprintName("Health_Pickup".to_string()), // mandatory !! + SpawnHere, // mandatory !! + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), // VERY important !! + // any other component you want to insert +)) + +``` + +Once spawning of the actual entity is done, the spawned Blueprint will be *gone/merged* with the contents of Blueprint ! + +> Important : +you can **add** or **override** components present inside your Blueprint when spawning the BluePrint itself: ie + +### Adding components not specified inside the blueprint + +you can just add any additional components you need when spawning : + +```rust no_run +commands.spawn(( + BlueprintName("Health_Pickup".to_string()), + SpawnHere, + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + // from Rapier/bevy_xpbd: this means the entity will also have a velocity component when inserted into the world + Velocity { + linvel: Vec3::new(vel_x, vel_y, vel_z), + angvel: Vec3::new(0.0, 0.0, 0.0), + }, +)) + +``` +### Overriding components specified inside the blueprint + +any component you specify when spawning the Blueprint that is also specified **within** the Blueprint will **override** that component in the final spawned entity + +for example +```rust no_run +commands.spawn(( + BlueprintName("Health_Pickup".to_string()), + SpawnHere, + TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + HealthPowerUp(20)// if this is component is also present inside the "Health_Pickup" blueprint, that one will be replaced with this component during spawning +)) + +``` + +### BluePrintBundle + +There is also a bundle for convenience , which just has + * a ```BlueprintName``` component + * a ```SpawnHere``` component + * a ```TransformBundle``` sub-bundle (so we know where to spawn) + +[```BluePrintBundle```](./src/lib.rs#22) + + +## SystemSet + +the ordering of systems is very important ! + +For example to replace your proxy components (stand-in components when you cannot/ do not want to use real components in the gltf file) with actual ones, which should happen **AFTER** the Blueprint based spawning, + +so ```bevy_gltf_blueprints``` provides a **SystemSet** for that purpose:[```GltfBlueprintsSet```](./src/lib.rs#16) + +Typically , the order of systems should be + +***bevy_gltf_components (GltfComponentsSet::Injection)*** => ***bevy_gltf_blueprints (GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn)*** => ***replace_proxies*** + +see https://github.com/kaosat-dev/Blender_bevy_components_worklflow/tree/main/examples/advanced for how to set it up correctly + +## Examples + +https://github.com/kaosat-dev/Blender_bevy_components_worklflow/tree/main/examples/advanced + +## License + +This crate, all its code, contents & assets is Dual-licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](./LICENSE_MIT.md) or https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/src/clone_entity.rs b/crates/bevy_gltf_blueprints/src/clone_entity.rs new file mode 100644 index 0000000..3702863 --- /dev/null +++ b/crates/bevy_gltf_blueprints/src/clone_entity.rs @@ -0,0 +1,66 @@ +use bevy::prelude::*; +use bevy::ecs::system::Command; + +// modified version from https://github.com/bevyengine/bevy/issues/1515, +// more specifically https://gist.github.com/nwtnni/85d6b87ae75337a522166c500c9a8418 +// to work with Bevy 0.11 +pub struct CloneEntity { + pub source: Entity, + pub destination: Entity, +} + +impl CloneEntity { + // Copy all components from an entity to another. + // Using an entity with no components as the destination creates a copy of the source entity. + // Panics if: + // - the components are not registered in the type registry, + // - the world does not have a type registry + // - the source or destination entity do not exist + fn clone_entity(self, world: &mut World) { + let components = { + let registry = world.get_resource::().unwrap().read(); + + world + .get_entity(self.source) + .unwrap() + .archetype() + .components() + .map(|component_id| { + world + .components() + .get_info(component_id) + .unwrap() + .type_id() + .unwrap() + }) + .map(|type_id| { + // println!("type_id {:?}", type_id); + registry + .get(type_id) + .unwrap() + .data::() + .unwrap() + .clone() + }) + .collect::>() + }; + + for component in components { + let source = component + .reflect(world.get_entity(self.source).unwrap()) + .unwrap() + .clone_value(); + + let mut destination = world.get_entity_mut(self.destination).unwrap(); + + component.apply_or_insert(&mut destination, &*source); + } + } +} + +// This allows the command to be used in systems +impl Command for CloneEntity { + fn apply(self, world: &mut World) { + self.clone_entity(world) + } +} \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/src/lib.rs b/crates/bevy_gltf_blueprints/src/lib.rs new file mode 100644 index 0000000..dac0f4e --- /dev/null +++ b/crates/bevy_gltf_blueprints/src/lib.rs @@ -0,0 +1,90 @@ + +pub mod spawn_from_blueprints; +use bevy_gltf_components::GltfComponentsSet; +pub use spawn_from_blueprints::*; + +pub mod spawn_post_process; +pub use spawn_post_process::*; + +pub mod clone_entity; +pub use clone_entity::*; + +use bevy::prelude::*; + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +/// set for the two stages of blueprint based spawning : +pub enum GltfBlueprintsSet{ + Spawn, + AfterSpawn, +} + +#[derive(Bundle)] +pub struct BluePrintBundle { + pub blueprint: BlueprintName, + pub spawn_here: SpawnHere, + pub transform: TransformBundle +} +impl Default for BluePrintBundle { + fn default() -> Self { + BluePrintBundle { + blueprint: BlueprintName("default".into()), + spawn_here: SpawnHere, + transform: TransformBundle::default() + } + } +} + + +#[derive(Clone, Resource)] +pub(crate) struct BluePrintsConfig { + pub(crate) library_folder: String, +} + +#[derive(Debug, Clone)] +pub struct BlueprintsPlugin { + /// The base folder where library/blueprints assets are loaded from, relative to the executable. + pub library_folder: String, +} + +impl Default for BlueprintsPlugin { + fn default() -> Self { + Self { + library_folder: "assets/models/library".to_string(), + } + } +} + +impl Plugin for BlueprintsPlugin { + fn build(&self, app: &mut App) { + app + .register_type::() + .register_type::() + .insert_resource(BluePrintsConfig{library_folder: self.library_folder.clone()}) + + .configure_sets( + Update, + (GltfBlueprintsSet::Spawn, GltfBlueprintsSet::AfterSpawn).chain().after(GltfComponentsSet::Injection) + ) + + .add_systems(Update, + (spawn_from_blueprints) + // .run_if(in_state(AppState::AppRunning).or_else(in_state(AppState::LoadingGame))) // FIXME: how to replace this with a crate compatible version ? + .in_set(GltfBlueprintsSet::Spawn), + ) + + .add_systems( + Update, + ( + // spawn_entities, + update_spawned_root_first_child, + apply_deferred, + cleanup_scene_instances, + apply_deferred, + ) + .chain() + // .run_if(in_state(AppState::LoadingGame).or_else(in_state(AppState::AppRunning))) // FIXME: how to replace this with a crate compatible version ? + .in_set(GltfBlueprintsSet::AfterSpawn), + ) + ; + } +} \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs b/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs new file mode 100644 index 0000000..9f9118c --- /dev/null +++ b/crates/bevy_gltf_blueprints/src/spawn_from_blueprints.rs @@ -0,0 +1,81 @@ +use bevy::{prelude::*, gltf::Gltf}; + +use crate::BluePrintsConfig; + +/// this is a flag component for our levels/game world +#[derive(Component)] +pub struct GameWorldTag; + +/// Main component for the blueprints +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub struct BlueprintName(pub String); + +/// flag component needed to signify the intent to spawn a Blueprint +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub struct SpawnHere; + +#[derive(Component)] +/// FlagComponent for spawned entity +pub struct Spawned; + + +#[derive(Component)] +/// helper component, just to transfer some data +pub(crate) struct Original(pub Entity); + +#[derive(Component)] +/// FlagComponent for dynamically spawned scenes +pub struct SpawnedRoot; + +/// main spawning functions, +/// * also takes into account the already exisiting "override" components, ie "override components" > components from blueprint +pub(crate) fn spawn_from_blueprints( + spawn_placeholders: Query<(Entity, &Name, &BlueprintName, &Transform), (Added, Added, Without, Without)>, + + mut commands: Commands, + mut game_world: Query<(Entity, &Children), With>, + + assets_gltf: Res>, + asset_server: Res, + blueprints_config: Res +){ + + for (entity, name, blupeprint_name, global_transform) in spawn_placeholders.iter() { + info!("need to spawn {:?}", blupeprint_name.0); + let what = &blupeprint_name.0; + let model_path = format!("{}/{}.glb", &blueprints_config.library_folder, &what); // FIXME: needs to be platform agnostic + let scene:Handle = asset_server.load(&model_path); + // let scene = game_assets.models.get(&model_path).expect(&format!("no matching model {:?} found", model_path)); + info!("attempting to spawn {:?}",model_path); + + let world = game_world.single_mut(); + let world = world.1[0]; // FIXME: dangerous hack because our gltf data have a single child like this, but might not always be the case + + let gltf = assets_gltf.get(&scene).expect("this gltf should have been loaded"); + // WARNING we work under the assumtion that there is ONLY ONE named scene, and that the first one is the right one + let main_scene_name =gltf.named_scenes.keys().nth(0).expect("there should be at least one named scene in the gltf file to spawn"); + let scene = &gltf.named_scenes[main_scene_name]; + + + //spawn_requested_events.send(SpawnRequestedEvent { what: "enemy".into(), position, amount: 1, spawner_id: None }); + let child_scene = commands.spawn( + ( + SceneBundle { + scene: scene.clone(), + transform: global_transform.clone(), + ..Default::default() + }, + bevy::prelude::Name::from(["scene_wrapper", &name.clone()].join("_") ), + // Parent(world) // FIXME/ would be good if this worked directly + SpawnedRoot, + /*AnimationHelper{ // TODO: insert this at the ENTITY level, not the scene level + named_animations: gltf.named_animations.clone(), + // animations: gltf.named_animations.values().clone() + },*/ + Original(entity) + )).id(); + commands.entity(world).add_child(child_scene); + } +} \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/src/spawn_post_process.rs b/crates/bevy_gltf_blueprints/src/spawn_post_process.rs new file mode 100644 index 0000000..2c49d34 --- /dev/null +++ b/crates/bevy_gltf_blueprints/src/spawn_post_process.rs @@ -0,0 +1,136 @@ +use bevy::prelude::*; +use bevy::utils::HashMap; + +use super::{CloneEntity, SpawnHere}; +use super::{Original, SpawnedRoot}; + + +// FIXME: move to more relevant module +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub struct AnimationHelper { + pub named_animations: HashMap>, +} + +#[derive(Component)] +/// FlagComponent for dynamically spawned scenes +pub(crate) struct SpawnedRootProcessed; + + + +/// this system updates the first (and normally only) child of a scene flaged SpawnedRoot +/// - adds a name based on parent component (spawned scene) which is named on the scene name/prefab to be instanciated +/// - adds the initial physics impulse (FIXME: we would need to add a temporary physics component to those who do not have it) +// FIXME: updating hierarchy does not work in all cases ! this is sadly dependant on the structure of the exported blend data, disablin for now +// - blender root-> object with properties => WORKS +// - scene instance -> does not work +// it might be due to how we add components to the PARENT item in gltf to components +pub(crate) fn update_spawned_root_first_child( + // all_children: Query<(Entity, &Children)>, + unprocessed_entities :Query<(Entity, &Children, &Name, &Parent, &Original ), (With, Without)>, + mut commands: Commands, + + // FIXME: should be done at a more generic gltf level + animation_helpers: Query<&AnimationHelper>, + added_animation_helpers : Query<(Entity, &AnimationPlayer), Added> +){ + /* + currently we have + - scene instance + - root node ? + - the actual stuff + we want to remove the root node + so we wend up with + - scene instance + - the actual stuff + + so + - get root node + - add its children to the scene instance + - remove root node + + Another issue is, the scene instance become empty if we have a pickabke as the "actual stuff", so that would lead to a lot of + empty scenes if we spawn pickables + - perhaps another system that cleans up empty scene instances ? + + FIME: this is all highly dependent on the hierachy ;.. + */ + + for (scene_instance, children, name, parent, original) in unprocessed_entities.iter() { + println!("children of scene {:?}", children); + if children.len() == 0 { + continue; + } + // the root node is the first & normally only child inside a scene, it is the one that has all relevant components + let root_entity = children.first().unwrap(); //FIXME: and what about childless ones ?? + // let root_entity_data = all_children.get(*root_entity).unwrap(); + + // fixme : randomization should be controlled via parameters, perhaps even the seed could be specified ? + // use this https://rust-random.github.io/book/guide-seeding.html#a-simple-number, blenders seeds are also uInts + // also this is not something we want every time, this should be a settable parameter when requesting a spawn + + + // add missing name of entity, based on the wrapper's name + let name= name.clone().replace("scene_wrapper_", ""); + + // this is our new actual entity + commands.entity(*root_entity).insert(( + bevy::prelude::Name::from(name.clone()), + // ItemType {name}, + // Spawned, // FIXME: not sure + )); + + // flag the spawned_root as being processed + commands.entity(scene_instance).insert(SpawnedRootProcessed); + + + // let original_transforms = + // parent is either the world or an entity with a marker (BlueprintName) + commands.entity(parent.get()).add_child(*root_entity); + // commands.entity(*root_entity).despawn_recursive(); + // commands.entity(parent.get()).push_children(&actual_stuff); + //commands.entity(*root_entity).log_components(); + + let matching_animation_helper = animation_helpers.get(scene_instance); + + // println!("WE HAVE SOME ADDED ANIMATION PLAYERS {:?}", matching_animation_helper); + if let Ok(anim_helper) = matching_animation_helper{ + for (added, _) in added_animation_helpers.iter(){ + commands.entity(added).insert( + AnimationHelper{ // TODO: insert this at the ENTITY level, not the scene level + named_animations: anim_helper.named_animations.clone(), + // animations: gltf.named_animations.values().clone() + }, + ); + } + } + + commands.add(CloneEntity { + source: original.0, + destination: *root_entity, + }); + + // remove the original entity, now that we have cloned it into the spawned scenes first child + commands.entity(original.0).despawn_recursive(); + commands.entity(*root_entity).remove::(); + } +} + +/// cleans up dynamically spawned scenes so that they get despawned if they have no more children +// FIXME: this can run too early , and since withouth_children matches not yet completed scene instances, boom, it removes components/children before they can be used +pub(crate) fn cleanup_scene_instances( + scene_instances: Query<(Entity, &Children), With>, + without_children: Query, Without)>,// if there are not children left, bevy removes Children ? + mut commands: Commands +){ + for (entity, children) in scene_instances.iter(){ + if children.len() == 0{ // it seems this does not happen ? + info!("cleaning up emptied spawned scene instance"); + commands.entity(entity).despawn_recursive(); + } + } + for entity in without_children.iter() { + info!("cleaning up emptied spawned scene instance"); + commands.entity(entity).despawn_recursive(); + } +} diff --git a/crates/bevy_gltf_components/Cargo.toml b/crates/bevy_gltf_components/Cargo.toml new file mode 100644 index 0000000..a596ab3 --- /dev/null +++ b/crates/bevy_gltf_components/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bevy_gltf_components" +version = "0.1.0" +authors = ["Mark 'kaosat-dev' Moissette"] +description = "Allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side." +homepage = "https://github.com/kaosat-dev/Blender_bevy_components_worklflow" +repository = "https://github.com/kaosat-dev/Blender_bevy_components_worklflow" +keywords = ["gamedev", "bevy", "assets", "gltf", "components"] +categories = ["game-development"] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dev-dependencies] +bevy="0.11.2" + +[dependencies] +bevy = { version = "0.11.2", default-features = false, features = ["bevy_asset", "bevy_scene", "bevy_gltf"] } +serde = "1.0.188" +ron="0.8.1" diff --git a/crates/bevy_gltf_components/LICENCE.md b/crates/bevy_gltf_components/LICENCE.md new file mode 100644 index 0000000..ad21aac --- /dev/null +++ b/crates/bevy_gltf_components/LICENCE.md @@ -0,0 +1,4 @@ +This crate is available under either: + +* The [MIT License](./LICENSE_MIT) +* The [Apache License, Version 2.0](./LICENSE_APACHE) \ No newline at end of file diff --git a/crates/bevy_gltf_components/LICENSE_APACHE.md b/crates/bevy_gltf_components/LICENSE_APACHE.md new file mode 100644 index 0000000..f748977 --- /dev/null +++ b/crates/bevy_gltf_components/LICENSE_APACHE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [Mark "kaosat-dev" Moissette] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/crates/bevy_gltf_components/LICENSE_MIT.md b/crates/bevy_gltf_components/LICENSE_MIT.md new file mode 100644 index 0000000..f8b9094 --- /dev/null +++ b/crates/bevy_gltf_components/LICENSE_MIT.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Mark "kaosat-dev" Moissette + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/crates/bevy_gltf_components/README.md b/crates/bevy_gltf_components/README.md new file mode 100644 index 0000000..fd76281 --- /dev/null +++ b/crates/bevy_gltf_components/README.md @@ -0,0 +1,83 @@ +[![Crates.io](https://img.shields.io/crates/v/bevy_gltf_components)](https://crates.io/crates/bevy_gltf_components) +[![Docs](https://img.shields.io/docsrs/bevy_gltf_components)](https://docs.rs/bevy_gltf_components/latest/bevy_gltf_components/) +[![License](https://img.shields.io/crates/l/bevy_gltf_components)](https://github.com/kaosat-dev/Blender_bevy_components_worklflow/blob/main/crates/bevy_gltf_components/License.md) +[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking) + + +# bevy_gltf_components + +This crate allows you to define [Bevy](https://bevyengine.org/) components direclty inside gltf files and instanciate the components on the Bevy side. + +## Usage + +***important*** : the plugin for processing gltf files runs in ***update*** , so you cannot use the components directly if you spawn your scene from gltf in ***setup*** (the additional components will not show up) + +Please see the + * [example](https://github.com/kaosat-dev/Blender_bevy_components_worklflow/examples/basic) + * or use [```bevy_asset_loader```](https://github.com/NiklasEi/bevy_asset_loader) for a reliable workflow. + * alternatively, use the [```bevy_gltf_blueprints```](https://github.com/kaosat-dev/Blender_bevy_components_worklflow/blob/main/crates/bevy_gltf_blueprints) crate, build on this crate's features, + that allows you to directly spawn entities from gltf based blueprints. + +Here's a minimal usage example: + +```toml +# Cargo.toml +[dependencies] +bevy="0.11.2" +bevy_gltf_components = { version = "0.1.0"} + +``` + +```rust no_run +//too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blender_bevy_components_worklflow/examples/basic for a real example + fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(ComponentsFromGltfPlugin) + .add_system(spawn_level) + .run(); + } + + fn spawn_level( + asset_server: Res, + mut commands: bevy::prelude::Commands, + keycode: Res>, + + ){ + if keycode.just_pressed(KeyCode::Return) { + commands.spawn(SceneBundle { + scene: asset_server.load("basic/models/level1.glb#Scene0"), + transform: Transform::from_xyz(2.0, 0.0, -5.0), + ..Default::default() + }); + } +} + +``` + + +## SystemSet + +the ordering of systems is very important ! + +For example to replace your proxy components (stand-in components when you cannot/ do not want to use real components in the gltf file) with actual ones, + +which should happen **AFTER** the components from the gltf files have been injected, + +so ```bevy_gltf_components``` provides a **SystemSet** for that purpose:[```GltfComponentsSet```](./src/lib.rs#46) + +Typically , the order of systems should be + +***bevy_gltf_components (GltfComponentsSet::Injection)*** => ***replace_proxies*** + +## Examples + +https://github.com/kaosat-dev/Blender_bevy_components_worklflow/tree/main/examples/basic + + +## License + +This crate, all its code, contents & assets is Dual-licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](./LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](./LICENSE_MIT.md) or https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/src/gltf_to_components.rs b/crates/bevy_gltf_components/src/gltf_to_components.rs similarity index 95% rename from src/gltf_to_components.rs rename to crates/bevy_gltf_components/src/gltf_to_components.rs index 515cb2e..afcca23 100644 --- a/src/gltf_to_components.rs +++ b/crates/bevy_gltf_components/src/gltf_to_components.rs @@ -13,6 +13,7 @@ use bevy::gltf::{Gltf, GltfExtras}; use super::capitalize_first_letter; +/// main function: injects components into each entity in gltf files that have gltf_extras, using reflection pub fn gltf_extras_to_components( gltf: &mut Gltf, scenes: &mut ResMut>, @@ -21,7 +22,7 @@ pub fn gltf_extras_to_components( ){ let mut added_components = 0; for (_name, scene) in &gltf.named_scenes { - info!("gltf: {:?} scene name {:?}", gltf_name, _name); + debug!("gltf: {:?} scene name {:?}", gltf_name, _name); let scene = scenes.get_mut(scene).unwrap(); @@ -49,7 +50,6 @@ pub fn gltf_extras_to_components( if entity_components.contains_key(&target_entity) { let mut updated_components: Vec> = Vec::new(); let current_components = &entity_components[&target_entity]; - // first inject the current components for component in current_components { updated_components.push(component.clone_value()); @@ -74,7 +74,6 @@ pub fn gltf_extras_to_components( debug!("-----value {:?}", &extras.value); } - // println!("FOUND ASSET {:?}", foob); // GltfNode // find a way to link this name to the current entity ? => WOULD BE VERY USEFULL for animations & co !! debug!("done pre-processing components, now adding them to entities"); @@ -98,10 +97,9 @@ pub fn gltf_extras_to_components( // scene.world.components(). // TODO: how can we insert any additional components "by hand" here ? } - let entity_mut = scene.world.entity_mut(entity); - let archetype = entity_mut.archetype().clone(); - let _all_components = archetype.components(); - // println!("All components {:?}", all_components); + // let entity_mut = scene.world.entity_mut(entity); + // let archetype = entity_mut.archetype().clone(); + // let _all_components = archetype.components(); if added_components > 0 { debug!("------done adding {} components", added_components); @@ -120,7 +118,6 @@ pub fn gltf_extras_to_components( for (key, value) in lookup.into_iter() { let type_string = key.replace("component: ", "").trim().to_string(); let capitalized_type_name = capitalize_first_letter(type_string.as_str()); - // println!("capitalized_type_name {}", capitalized_type_name); let mut parsed_value:String; match value.clone() { @@ -140,8 +137,6 @@ pub fn gltf_extras_to_components( if info.field_len() == 1 { let field = info.field_at(0).expect("we should always have at least one field here"); let field_name = field.type_name(); - // println!("field name {}", field_name); - // let bla = TypeId::of::(); // TODO: find a way to cast with typeId instead of this matching /*match field.type_id(){ TypeId::of::() => { diff --git a/crates/bevy_gltf_components/src/lib.rs b/crates/bevy_gltf_components/src/lib.rs new file mode 100644 index 0000000..00bfdc1 --- /dev/null +++ b/crates/bevy_gltf_components/src/lib.rs @@ -0,0 +1,74 @@ +pub mod utils; +pub use utils::*; + +pub mod gltf_to_components; +pub use gltf_to_components::*; + +pub mod process_gltfs; +pub use process_gltfs::*; + +use bevy::prelude::{ + App,Plugin, Update, SystemSet, IntoSystemConfigs +}; + + +/// A Bevy plugin for extracting components from gltf files and automatically adding them to the relevant entities +/// It will automatically run every time you load a gltf file +/// Add this plugin to your Bevy app to get access to this feature +/// ``` +/// # use bevy::prelude::*; +/// # use bevy::gltf::*; +/// # use bevy_gltf_components::ComponentsFromGltfPlugin; +/// +/// //too barebones of an example to be meaningfull, please see https://github.com/kaosat-dev/Blender_bevy_components_worklflow/examples/basic for a real example +/// fn main() { +/// App::new() +/// .add_plugins(DefaultPlugins) +/// .add_plugin(ComponentsFromGltfPlugin) +/// .add_system(spawn_level) +/// .run(); +/// } +/// +/// fn spawn_level( +/// asset_server: Res, +/// mut commands: bevy::prelude::Commands, +/// keycode: Res>, + +/// ){ +/// if keycode.just_pressed(KeyCode::Return) { +/// commands.spawn(SceneBundle { +/// scene: asset_server.load("basic/models/level1.glb#Scene0"), +/// transform: Transform::from_xyz(2.0, 0.0, -5.0), +/// ..Default::default() +/// }); +/// } +///} +/// ``` + + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +/// systemset to order your systems after the component injection when needed +pub enum GltfComponentsSet{ + Injection, +} + + +#[derive(Default)] +pub struct ComponentsFromGltfPlugin; +impl Plugin for ComponentsFromGltfPlugin { + fn build(&self, app: &mut App) { + app + .insert_resource(GltfLoadingTracker::new()) + + .add_systems(Update, ( + track_new_gltf, + process_loaded_scenes, + )) + + .add_systems(Update, + (process_loaded_scenes) + .in_set(GltfComponentsSet::Injection) + ) + ; + } +} diff --git a/src/process_gltfs.rs b/crates/bevy_gltf_components/src/process_gltfs.rs similarity index 96% rename from src/process_gltfs.rs rename to crates/bevy_gltf_components/src/process_gltfs.rs index c57912a..dce20d4 100644 --- a/src/process_gltfs.rs +++ b/crates/bevy_gltf_components/src/process_gltfs.rs @@ -5,7 +5,8 @@ use bevy::gltf::Gltf; use super::gltf_extras_to_components; #[derive(Resource)] - pub struct GltfLoadingTracker{ +/// component to keep track of gltfs' loading state +pub struct GltfLoadingTracker{ pub loading_gltfs: HashSet>, pub loaded_gltfs: HashSet> } diff --git a/src/utils.rs b/crates/bevy_gltf_components/src/utils.rs similarity index 100% rename from src/utils.rs rename to crates/bevy_gltf_components/src/utils.rs diff --git a/docs/blender_addon_install2.png b/docs/blender_addon_install2.png index de1db1b..6b81a22 100644 Binary files a/docs/blender_addon_install2.png and b/docs/blender_addon_install2.png differ diff --git a/docs/blender_addon_use2.png b/docs/blender_addon_use2.png index b0c6b31..3665b4a 100644 Binary files a/docs/blender_addon_use2.png and b/docs/blender_addon_use2.png differ diff --git a/docs/blender_addon_use3.png b/docs/blender_addon_use3.png new file mode 100644 index 0000000..6555e13 Binary files /dev/null and b/docs/blender_addon_use3.png differ diff --git a/docs/exported_library_files.png b/docs/exported_library_files.png new file mode 100644 index 0000000..d9ca8fb Binary files /dev/null and b/docs/exported_library_files.png differ diff --git a/docs/process.svg b/docs/process.svg new file mode 100644 index 0000000..0bcdeec --- /dev/null +++ b/docs/process.svg @@ -0,0 +1,881 @@ + + + + + + + + + + + + + + + + + Original + + + + + + Object A(unique) + + Main Scene (world/level) + + + Object B1 (instance) + + + + Object C0 (instance) + + + + + + Object C1 (instance) + + + + Library Scene + + Object B: collection/ blueprint + + + Object C: collection/ blueprint + + + + Transform Step + + + + + + Object A(unique) + + Temporary Scene (world/level) + + + Object B1 (Empty)+ blueprintName("ObjectB")+ spawnHere + + + + Object C0 (Empty)+ blueprintName("ObjectC")+ spawnHere + + + + + + Object C1 (Empty)+ blueprintName("ObjectC")+ spawnHere + + + + Determine used Collections to export + + Object B + + Object C + + + Result + + + + + Entity A(unique) + + + world/level.gltf + + + Entity B1+ blueprintName("ObjectB")+ spawnHere + + + + Entity C0+ blueprintName("ObjectC")+ spawnHere + + + + + + Entity C1+ blueprintName("ObjectC")+ spawnHere + + + + Library of gltf files (one per Collection/Blueprint) + + Object B.gltf + blueprintName("ObjectB") + + Object C:.gltf + blueprintName("ObjectC") + + + Object D: unused collection/ blueprint + + + + + + + diff --git a/docs/workflow_empties.jpg b/docs/workflow_empties.jpg new file mode 100644 index 0000000..eace6cb Binary files /dev/null and b/docs/workflow_empties.jpg differ diff --git a/docs/workflow_original.jpg b/docs/workflow_original.jpg new file mode 100644 index 0000000..c4ca7d4 Binary files /dev/null and b/docs/workflow_original.jpg differ diff --git a/examples/advanced/README.md b/examples/advanced/README.md new file mode 100644 index 0000000..823ab05 --- /dev/null +++ b/examples/advanced/README.md @@ -0,0 +1,20 @@ + +# Workflow with blender / demo information + +This example, is actually closer to a boilerplate + tooling showcases how to use a minimalistic [Blender](https://www.blender.org/) (gltf) centric workflow for [Bevy](https://bevyengine.org/), ie defining entites & their components +inside Blender using Blender's objects **custom properties**. +Aka "Blender as editor for Bevy" + +It also allows you to setup 'blueprints' in Blender by using collections (the recomended way to go most of the time), or directly on single use objects . + + +## Running this example + +``` +cargo run --example advanced --features bevy/dynamic_linking +``` + +### Additional notes + +* You usually define either the Components directly or use ```Proxy components``` that get replaced in Bevy systems with the actual Components that you want (usually when for some reason, ie external crates with unregistered components etc) you cannot use the components directly. +* this example contains code for future features, not finished yet ! please disregard anything related to saving & loading diff --git a/examples/advanced/TODO.md b/examples/advanced/TODO.md new file mode 100644 index 0000000..b61ed5a --- /dev/null +++ b/examples/advanced/TODO.md @@ -0,0 +1,53 @@ +- [x] seperate "spawn velocity" into a seperate (optional) component +- [x] try dynamically spawned entities with save & load +- [ ] fix issues with multiple entites having the same name ?? +- [ ] fix issues with system ordering + - [x] add sets for proxy-replacements +- [x] annoying camera movement : camera should be saveable as well + => we need to be able to only save the camera position (and name) +- [ ] only spawn "new level" stuff when transitioning from menu to game ? + - [ ] put more thoughts into this + +- [ ] rework how save-loading is triggered + - [ ] no hardcoded keypresses + - [ ] ability to choose what save to load ? + - [ ] take a look at bevy_moonshine_save + - [ ] move to system pipelines + +- [x] split Blueprints into a seperate crate: perhaps bevy_gltf_blueprints + - [x] how to deal with states that are not defined as part of the plugin/crate ? + - [x] same issue for the assets + +- [ ] support multiple main scenes in the blender plugin ? +- [ ] study possibilities of expanding the bevy & blender tooling side to define UIS + - likely using the blender data only as a placeholder/ directly replace in Python + +- system ordering ? + load level => inject components => spawn blueprint entities/rehydrate => (loading) => replace proxies + OR + load level => inject components => (loading) => spawn blueprint entities/rehydrate => replace proxies + +- perhaps it does make more sense to save ALL entities and not just the dynamic ones? mostly as we have the blueprints anyway, which should cut down on needed data ? + + +- different approaches for dealing with saving/loading + in particular the problem of entites that are defined as part of the level but can be despawned (items that get picked up etc) + + Bevy side + * var 1 : spawn all entities completely, despawn those saveables that are not actually present in the save data but that have been spawned + * problems: needs correct ordering of systems otherwise the diffing above will not work + * pros: minimal save files, only bevy boilerplate + * cons: requires spawning potentially huge gltf files just to "peek" inside of them to know if they are saveable + + + * var 2 : save both saveables & unsaveables but only save the absolute minimal data for unsaveables + * problems: how to combine different filters into a single save file ? + * pros: easier diffing, more homogeneous handling + * cons: a lot bigger save file with mostly useless data + + Blender side + + * var 3 => CHOSEN OPTION : mark INSTANCES in advance as static/dynamic (ie saveable or not), thus this data would be present in the world/level and not inside the gltf data + * problems: would require adding custom properties to each instance in Blender (COULD be automated by 'peeking' inside the collection) + * pros: simpler, and this might be more "editor-like" where you would mark each item as static or not + * cons: potentially a lot of manual work / error prone \ No newline at end of file diff --git a/examples/advanced/assets/assets_core.rs b/examples/advanced/assets/assets_core.rs new file mode 100644 index 0000000..52a4c16 --- /dev/null +++ b/examples/advanced/assets/assets_core.rs @@ -0,0 +1,7 @@ +use bevy::prelude::*; +use bevy_asset_loader::prelude::*; + +#[derive(AssetCollection, Resource)] +pub struct CoreAssets { + +} diff --git a/examples/advanced/assets/assets_game.rs b/examples/advanced/assets/assets_game.rs new file mode 100644 index 0000000..b7bb336 --- /dev/null +++ b/examples/advanced/assets/assets_game.rs @@ -0,0 +1,13 @@ +use bevy::gltf::Gltf; +use bevy::prelude::*; +use bevy::utils::HashMap; +use bevy_asset_loader::prelude::*; + +#[derive(AssetCollection, Resource)] +pub struct GameAssets { + #[asset(key = "world")] + pub world: Handle, + + #[asset(key = "models", collection(typed, mapped))] + pub models: HashMap>, +} diff --git a/examples/advanced/assets/mod.rs b/examples/advanced/assets/mod.rs new file mode 100644 index 0000000..a431113 --- /dev/null +++ b/examples/advanced/assets/mod.rs @@ -0,0 +1,34 @@ +pub mod assets_core; +pub use assets_core::*; + +pub mod assets_game; +pub use assets_game::*; + +use bevy::prelude::*; +use bevy_asset_loader::prelude::*; + +use crate::state::{AppState}; + +pub struct AssetsPlugin; +impl Plugin for AssetsPlugin { + fn build(&self, app: &mut App) { + app + // load core assets (ie assets needed in the main menu, and everywhere else before loading more assets in game) + .add_loading_state(LoadingState::new(AppState::CoreLoading).continue_to_state(AppState::MenuRunning)) + .add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>( + AppState::CoreLoading, + "advanced/assets_core.assets.ron", + ) + .add_collection_to_loading_state::<_, CoreAssets>(AppState::CoreLoading) + + // load game assets + .add_loading_state(LoadingState::new(AppState::AppLoading).continue_to_state(AppState::AppRunning)) + .add_dynamic_collection_to_loading_state::<_, StandardDynamicAssetCollection>( + AppState::AppLoading, + "advanced/assets_game.assets.ron", + ) + .add_collection_to_loading_state::<_, GameAssets>(AppState::AppLoading) + + ; + } +} \ No newline at end of file diff --git a/examples/general/core/camera/camera_replace_proxies.rs b/examples/advanced/core/camera/camera_replace_proxies.rs similarity index 100% rename from examples/general/core/camera/camera_replace_proxies.rs rename to examples/advanced/core/camera/camera_replace_proxies.rs diff --git a/examples/general/core/camera/camera_tracking.rs b/examples/advanced/core/camera/camera_tracking.rs similarity index 100% rename from examples/general/core/camera/camera_tracking.rs rename to examples/advanced/core/camera/camera_tracking.rs diff --git a/examples/advanced/core/camera/mod.rs b/examples/advanced/core/camera/mod.rs new file mode 100644 index 0000000..28f3aaa --- /dev/null +++ b/examples/advanced/core/camera/mod.rs @@ -0,0 +1,26 @@ +pub mod camera_tracking; +pub use camera_tracking::*; + +pub mod camera_replace_proxies; +pub use camera_replace_proxies::*; + +use bevy::prelude::*; +use bevy_gltf_blueprints::GltfBlueprintsSet; + +pub struct CameraPlugin; +impl Plugin for CameraPlugin { + fn build(&self, app: &mut App) { + app + .register_type::() + .register_type::() + .register_type::() + + .add_systems(Update, + ( + camera_replace_proxies.after(GltfBlueprintsSet::AfterSpawn), + camera_track, + ) + ) + ; + } +} \ No newline at end of file diff --git a/examples/general/core/lighting/lighting_replace_proxies.rs b/examples/advanced/core/lighting/lighting_replace_proxies.rs similarity index 100% rename from examples/general/core/lighting/lighting_replace_proxies.rs rename to examples/advanced/core/lighting/lighting_replace_proxies.rs diff --git a/examples/general/core/lighting/mod.rs b/examples/advanced/core/lighting/mod.rs similarity index 100% rename from examples/general/core/lighting/mod.rs rename to examples/advanced/core/lighting/mod.rs diff --git a/examples/advanced/core/mod.rs b/examples/advanced/core/mod.rs new file mode 100644 index 0000000..4e40b44 --- /dev/null +++ b/examples/advanced/core/mod.rs @@ -0,0 +1,94 @@ +pub mod camera; +use bevy_rapier3d::prelude::Velocity; +pub use camera::*; + +pub mod lighting; +pub use lighting::*; + +pub mod relationships; +pub use relationships::*; + +pub mod physics; +pub use physics::*; + +// pub mod blueprints; +// pub use blueprints::*; + +pub mod save_load; +pub use save_load::*; + +use bevy::prelude::*; +use bevy_gltf_blueprints::*; + +use rand::Rng; + + +fn spawn_test( + keycode: Res>, + mut commands: Commands, + + mut game_world: Query<(Entity, &Children), With>, + + +) { + if keycode.just_pressed(KeyCode::T) { + let world = game_world.single_mut(); + let world = world.1[0]; + + let mut rng = rand::thread_rng(); + let range = 5.5; + let x: f32 = rng.gen_range(-range..range); + let y: f32 = rng.gen_range(-range..range); + + + let mut rng = rand::thread_rng(); + let range = 0.8; + let vel_x: f32 = rng.gen_range(-range..range); + let vel_y: f32 = rng.gen_range(2.0..2.5); + let vel_z: f32 = rng.gen_range(-range..range); + + + let name_index:u64 = rng.gen(); + + let new_entity = commands.spawn(( + BluePrintBundle{ + blueprint: BlueprintName("Health_Pickup".to_string()), + transform: TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + ..Default::default() + }, + bevy::prelude::Name::from(format!("test{}", name_index)), + // BlueprintName("Health_Pickup".to_string()), + // SpawnHere, + // TransformBundle::from_transform(Transform::from_xyz(x, 2.0, y)), + + Velocity { + linvel: Vec3::new(vel_x, vel_y, vel_z), + angvel: Vec3::new(0.0, 0.0, 0.0), + }, + )).id(); + commands.entity(world).add_child(new_entity); + } +} + +pub struct CorePlugin; +impl Plugin for CorePlugin { + fn build(&self, app: &mut App) { + app + .add_plugins(( + LightingPlugin, + CameraPlugin, + PhysicsPlugin, + SaveLoadPlugin, + BlueprintsPlugin{ + library_folder: "advanced/models/library".into() + } + )) + + // just for testing + .add_systems( + Update, + spawn_test + ) + ; + } +} diff --git a/examples/advanced/core/physics/controls.rs b/examples/advanced/core/physics/controls.rs new file mode 100644 index 0000000..9c88bba --- /dev/null +++ b/examples/advanced/core/physics/controls.rs @@ -0,0 +1,12 @@ +use bevy::prelude::{ResMut, info}; +use bevy_rapier3d::prelude::RapierConfiguration; + +pub fn pause_physics(mut physics_config: ResMut){ + info!("pausing physics"); + physics_config.physics_pipeline_active = false; +} + +pub fn resume_physics(mut physics_config: ResMut){ + info!("unpausing physics"); + physics_config.physics_pipeline_active = true; +} \ No newline at end of file diff --git a/examples/advanced/core/physics/mod.rs b/examples/advanced/core/physics/mod.rs new file mode 100644 index 0000000..58f8bc7 --- /dev/null +++ b/examples/advanced/core/physics/mod.rs @@ -0,0 +1,39 @@ +pub mod physics_replace_proxies; +pub use physics_replace_proxies::*; + +pub mod utils; + +pub mod controls; +pub use controls::*; + +use bevy::prelude::*; +use crate::state::GameState; +// use super::blueprints::GltfBlueprintsSet; +use bevy_gltf_blueprints::GltfBlueprintsSet; +// use crate::Collider; +pub struct PhysicsPlugin; +impl Plugin for PhysicsPlugin { + fn build(&self, app: &mut App) { + app + .register_type::() + .register_type::() + .register_type::() + + // find a way to make serde's stuff serializable + // .register_type::() + //bevy_rapier3d::dynamics::CoefficientCombineRule + + .add_systems(Update, physics_replace_proxies.after(GltfBlueprintsSet::AfterSpawn)) + + .add_systems( + OnEnter(GameState::InGame), + resume_physics + ) + .add_systems( + OnExit(GameState::InGame), + pause_physics + ) + ; + } +} + diff --git a/examples/advanced/core/physics/physics_replace_proxies.rs b/examples/advanced/core/physics/physics_replace_proxies.rs new file mode 100644 index 0000000..85bce84 --- /dev/null +++ b/examples/advanced/core/physics/physics_replace_proxies.rs @@ -0,0 +1,121 @@ +use bevy::prelude::*; +// use bevy::render::primitives::Aabb; +use bevy_rapier3d::geometry::Collider as RapierCollider; +use bevy_rapier3d::dynamics::RigidBody as RapierRigidBody; +use bevy_rapier3d::prelude::{ComputedColliderShape, ActiveEvents, ActiveCollisionTypes}; + +use super::utils::*; + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub enum RigidBodyProxy{ + #[default] + Dynamic, + Fixed, + Position, + Velocity +} + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub enum Collider { + Ball(f32), + Cuboid(Vec3), + Capsule(Vec3, Vec3, f32), + #[default] + Mesh, +} + + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub enum AutoAABBCollider { + #[default] + Cuboid, + Ball, + Capsule +} + +// replaces all physics stand-ins with the actual rapier types +pub fn physics_replace_proxies ( + meshes: Res>, + mesh_handles: Query<&Handle>, + // rigidbodies + proxy_rigidbodies: Query<(Entity, &RigidBodyProxy,), (Without, Added)>, + mut proxy_colliders: Query<(Entity, &Collider, &Name, &mut Visibility), (Without, Added)>, + // needed for tri meshes + children: Query<&Children>, + + mut commands: Commands, +) { + for proxy_colider in proxy_colliders.iter_mut() { + let (entity, collider_proxy, name, mut visibility) = proxy_colider; + // we hide the collider meshes: perhaps they should be removed altogether once processed ? + if name.ends_with( "_collider" ) || name.ends_with( "_sensor" ) { + *visibility = Visibility::Hidden; + } + + let mut rapier_collider:RapierCollider; + match collider_proxy{ + Collider::Ball(radius) => { + info!("generating collider from proxy: ball"); + rapier_collider = RapierCollider::ball(*radius); + commands.entity(entity) + .insert(rapier_collider) + .insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!! + ; + } + Collider::Cuboid(size) => { + info!("generating collider from proxy: cuboid"); + rapier_collider = RapierCollider::cuboid(size.x, size.y, size.z); + commands.entity(entity) + .insert(rapier_collider) + .insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!! + ; + } + Collider::Capsule(a, b, radius) => { + info!("generating collider from proxy: capsule"); + rapier_collider = RapierCollider::capsule(*a, *b, *radius); + commands.entity(entity) + .insert(rapier_collider) + .insert(ActiveEvents::COLLISION_EVENTS) // FIXME: this is just for demo purposes !!! + ; + } + Collider::Mesh => { + info!("generating collider from proxy: mesh"); + for (_, collider_mesh) in Mesh::search_in_children(entity, &children, &meshes, &mesh_handles) + { + rapier_collider = RapierCollider::from_bevy_mesh(collider_mesh, &ComputedColliderShape::TriMesh).unwrap(); + commands.entity(entity) + .insert(rapier_collider) + // FIXME: this is just for demo purposes !!! + .insert(ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC | ActiveCollisionTypes::STATIC_STATIC | ActiveCollisionTypes::DYNAMIC_STATIC) + .insert(ActiveEvents::COLLISION_EVENTS) + ; + // .insert(ActiveEvents::COLLISION_EVENTS) + // break; + // RapierCollider::convex_hull(points) + } + + } + } + } + // rigidbodies + for (entity, proxy_rigidbody) in proxy_rigidbodies.iter() { + info!("Generation rigid body from Proxy rigid body !! {:?}", proxy_rigidbody ); + let rigid_body:RapierRigidBody = match proxy_rigidbody { + RigidBodyProxy::Dynamic => RapierRigidBody::Dynamic, + RigidBodyProxy::Fixed=> RapierRigidBody::Fixed, + RigidBodyProxy::Position => RapierRigidBody::KinematicPositionBased, + RigidBodyProxy::Velocity => RapierRigidBody::KinematicVelocityBased, + }; + commands.entity(entity) + .insert(rigid_body) + // IMPORTANT ! this allows collisions between dynamic & static(fixed) entities + // see https://rapier.rs/docs/user_guides/bevy_plugin/colliders#active-collision-types + .insert(ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_STATIC | ActiveCollisionTypes::STATIC_STATIC | ActiveCollisionTypes::DYNAMIC_STATIC) + .insert(ActiveEvents::COLLISION_EVENTS) + ; + } + +} diff --git a/examples/general/core/physics/utils.rs b/examples/advanced/core/physics/utils.rs similarity index 100% rename from examples/general/core/physics/utils.rs rename to examples/advanced/core/physics/utils.rs diff --git a/examples/general/core/physics/utils_old.rs b/examples/advanced/core/physics/utils_old.rs similarity index 100% rename from examples/general/core/physics/utils_old.rs rename to examples/advanced/core/physics/utils_old.rs diff --git a/examples/general/core/relationships/mod.rs b/examples/advanced/core/relationships/mod.rs similarity index 100% rename from examples/general/core/relationships/mod.rs rename to examples/advanced/core/relationships/mod.rs diff --git a/examples/general/core/relationships/relationships_insert_dependant_components.rs b/examples/advanced/core/relationships/relationships_insert_dependant_components.rs similarity index 100% rename from examples/general/core/relationships/relationships_insert_dependant_components.rs rename to examples/advanced/core/relationships/relationships_insert_dependant_components.rs diff --git a/examples/advanced/core/save_load/loading.rs b/examples/advanced/core/save_load/loading.rs new file mode 100644 index 0000000..cc1e8e2 --- /dev/null +++ b/examples/advanced/core/save_load/loading.rs @@ -0,0 +1,243 @@ +use bevy::prelude::*; +use bevy_gltf_blueprints::{clone_entity::CloneEntity, SpawnHere, GameWorldTag}; + +use crate::{ + assets::GameAssets, + state::{InAppRunning, AppState, GameState} +}; + +use super::Saveable; + +const SCENE_FILE_PATH: &str = "scenes/save.scn.ron"; + +#[derive(Component, Debug, )] +pub struct TempLoadedSceneMarker; + +#[derive(Component, Debug, )] +pub struct SaveablesToRemove(Vec<(Entity, Name)>); + +#[derive(Component, Event)] +pub struct LoadRequest { + pub path: String, +} + + +pub fn should_load( + save_requested_events: EventReader, +) -> bool { + return save_requested_events.len() > 0 +} + +pub fn load_prepare( + mut next_app_state: ResMut>, + mut next_game_state: ResMut>, +){ + + next_app_state.set(AppState::LoadingGame); + next_game_state.set(GameState::None); + info!("--loading: prepare") +} + +/// unload the level recursively +pub fn _unload_world_old(world: &mut World) { + let entities: Vec = world + // .query_filtered::, With)>>() + .query_filtered::>()// our level/world contains this component + .iter(world) + .collect(); + for entity in entities { + // Check the entity again in case it was despawned recursively + if world.get_entity(entity).is_some() { + world.entity_mut(entity).despawn_recursive(); + } + } +} + +pub fn unload_world( + mut commands: Commands, + gameworlds: Query> +){ + for e in gameworlds.iter(){ + info!("--loading: despawn old world/level"); + commands.entity(e).despawn_recursive(); + } +} + +// almost identical to setup_game, !!?? +pub fn load_world( + mut commands: Commands, + game_assets: Res, + // scenes: ResMut, +){ + info!("--loading: loading world/level"); + + commands.spawn(( + SceneBundle { + scene: game_assets.world.clone(), + ..default() + }, + bevy::prelude::Name::from("world"), + GameWorldTag, + InAppRunning + )); +} + + + + +pub fn load_saved_scene( + mut commands: Commands, + asset_server: Res +) { + commands.spawn( + ( + DynamicSceneBundle { + // Scenes are loaded just like any other asset. + scene: asset_server.load(SCENE_FILE_PATH), + ..default() + }, + TempLoadedSceneMarker + )); + // commands.entity(world).add_child(child_scene); + info!("--loading: loaded saved scene"); +} + +pub fn process_loaded_scene( + loaded_scene: Query<(Entity, &Children), With>, + named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient + mut commands: Commands, + + mut game_world: Query<(Entity, &Children), With>, + saveables: Query<(Entity, &Name), With>, + asset_server: Res +){ + for (loaded_scene, children) in loaded_scene.iter(){ + info!("--loading: post processing loaded scene"); + + let mut entities_to_load:Vec<(Entity, Name)> = vec![]; + + for loaded_entity in children.iter() { + if let Ok((source, name, _)) = named_entities.get(*loaded_entity) { + entities_to_load.push((source, name.clone())); + + let mut found = false; + for (e, n, p) in named_entities.iter(){ + // if we have an entity with the same name as in same file, overwrite + if e != source && name.as_str() == n.as_str(){ + // println!("found entity with same name {} {} {:?} {:?}", name, n, source, e); + // source is entity within the newly loaded scene (source), e is within the existing world (destination) + info!("copying data from {:?} to {:?}", source, e); + commands.add(CloneEntity { + source: source, + destination: e, + }); + // FIXME: issue with hierarchy & parenting, would be nice to be able to filter out components from CloneEntity + commands.entity(p.get()).add_child(e); + commands.entity(source).despawn_recursive(); + found = true; + break; + } + } + // entity not found in the list of existing entities (ie entities that came as part of the level) + // so we spawn a new one + if !found { + info!("generating new entity"); + let world = game_world.single_mut(); + let world = world.1[0]; + + let new_entity = commands.spawn(( + bevy::prelude::Name::from(name.clone()), + SpawnHere, + )).id(); + + commands.add(CloneEntity { + source: source, + destination: new_entity, + }); + + commands.entity(world).add_child(new_entity); + info!("copying data from {:?} to {:?}", source, new_entity); + + + + } + + } + } + commands.spawn(SaveablesToRemove(entities_to_load.clone())); + + + + // if an entity is present in the world but NOT in the saved entities , it should be removed from the world + // ideally this should be run between spawning of the world/level AND spawn_placeholders + + // remove the dynamic scene + info!("--loading: DESPAWNING LOADED SCENE"); + commands.entity(loaded_scene).despawn_recursive(); + + asset_server.mark_unused_assets(); + asset_server.free_unused_assets(); + + } + //for saveable in saveables.iter(){ + // println!("SAVEABLE BEFORE {:?}", saveable) + //} +} + +pub fn final_cleanup( + saveables_to_remove: Query<(Entity, &SaveablesToRemove)>, + mut commands: Commands, + saveables: Query<(Entity, &Name), With>, + mut next_app_state: ResMut>, + mut next_game_state: ResMut>, + +){ + if let Ok((e, entities_to_load)) = saveables_to_remove.get_single() + { + info!("saveables to remove {:?}", entities_to_load); + for (e, n) in saveables.iter(){ + let mut found = false; + println!("SAVEABLE {}", n); + + //let entities_to_load = saveables_to_remove.single(); + for (en, na) in entities_to_load.0.iter(){ + found = na.as_str() == n.as_str(); + if found { + break; + } + } + if !found { + println!("REMOVING THIS ONE {}", n); + commands.entity(e).despawn_recursive(); + } + } + // if there is a saveable that is NOT in the list of entities to load, despawn it + + // despawn list + commands.entity(e).despawn_recursive(); + + info!("--loading: done, move to InGame state"); + // next_app_state.set(AppState::AppRunning); + next_game_state.set(GameState::InGame); + } +} + +fn process_loaded_scene_load_alt( + entities: Query<(Entity, &Children), With>, + named_entities: Query<(Entity, &Name, &Parent)>, // FIXME: very inneficient + mut commands: Commands, + +){ + for (entity, children) in entities.iter(){ + let mut entities_to_load:Vec<(Entity, Name)> = vec![]; + for saved_source in children.iter() { + if let Ok((source, name, _)) = named_entities.get(*saved_source) { + println!("AAAAAAA {}", name); + entities_to_load.push((source, name.clone())); + } + } + println!("entities to load {:?}", entities_to_load); + + commands.entity(entity).despawn_recursive(); + } +} \ No newline at end of file diff --git a/examples/advanced/core/save_load/mod.rs b/examples/advanced/core/save_load/mod.rs new file mode 100644 index 0000000..30784aa --- /dev/null +++ b/examples/advanced/core/save_load/mod.rs @@ -0,0 +1,73 @@ +pub mod saveable; +use bevy::asset::free_unused_assets_system; +use bevy_gltf_components::GltfComponentsSet; +pub use saveable::*; + +pub mod saving; +pub use saving::*; + +pub mod loading; +pub use loading::*; + +use bevy::prelude::*; +use bevy::prelude::{App, Plugin, IntoSystemConfigs}; +use bevy::utils::Uuid; + +use bevy_gltf_blueprints::GltfBlueprintsSet; + + + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum LoadingSet{ + Load, + PostLoad, +} + +pub struct SaveLoadPlugin; +impl Plugin for SaveLoadPlugin { + fn build(&self, app: &mut App) { + app + .register_type::() + .register_type::() + .add_event::() + .add_event::() + + .configure_sets( + Update, + (LoadingSet::Load, LoadingSet::PostLoad) + .chain() + .before(GltfBlueprintsSet::Spawn) + .before(GltfComponentsSet::Injection) + ) + + .add_systems(PreUpdate, save_game.run_if(should_save)) + + .add_systems(Update, + ( + load_prepare, + unload_world, + load_world, + load_saved_scene, + // process_loaded_scene + ) + .chain() + .run_if(should_load) // .run_if(in_state(AppState::AppRunning)) + .in_set(LoadingSet::Load) + ) + .add_systems(Update, + ( + process_loaded_scene, + apply_deferred, + final_cleanup, + apply_deferred, + free_unused_assets_system + ) + .chain() + .in_set(LoadingSet::PostLoad) + ) + + // .add_systems(Update, bla) + + ; + } +} diff --git a/examples/advanced/core/save_load/old.rs b/examples/advanced/core/save_load/old.rs new file mode 100644 index 0000000..7d8a389 --- /dev/null +++ b/examples/advanced/core/save_load/old.rs @@ -0,0 +1,137 @@ +const NEW_SCENE_FILE_PATH:&str="save.scn.ron"; + + + + +use bevy::ecs::component::Components; +use bevy::ecs::entity::EntityMap; +use serde::{Deserialize, Serialize}; + + +use std::io::Read; +use bevy::scene::serde::SceneDeserializer; +use ron::Deserializer; +use serde::de::DeserializeSeed; + + + + +#[derive(Debug, Deserialize)] +struct Components2; + +#[derive(Debug, Deserialize)] +struct Fake { + resources: HashMap, + entities: HashMap +} + +fn ron_test(){ + let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; + match File::open(full_path) { + Ok(mut file) => { + let mut serialized_scene = Vec::new(); + if let Err(why) = file.read_to_end(&mut serialized_scene) { + error!("file read failed: {why:?}"); + } + match Deserializer::from_bytes(&serialized_scene) { + Ok(mut deserializer) => { + // deserializer. + let bla:Fake = ron::from_str("( + resources: {}, + entities: {} + )").unwrap(); + info!("testing {:?}", bla); + info!("YOYO DONE YO !") + } + Err(why) => { + error!("deserializer creation failed: {why:?}"); + } + } + } + Err(why) => { + error!("load failed: {why:?}"); + } + } +} + +fn inject_component_data(world: &mut World, scene: DynamicScene){ + let mut entity_map = EntityMap::default(); + if let Err(why) = scene.write_to_world(world, &mut entity_map) { + panic!("world write failed: {why:?}"); + } + println!("entity map {:?}", entity_map); + // TODO: EntityMap doesn't implement `iter()` + for old_entity in entity_map.keys() { + let entity = entity_map.get(old_entity).unwrap(); + info!("entity update required: {old_entity:?} -> {entity:?}"); + let e_mut = world + .entity_mut(entity); + } + + info!("done loading scene"); +} + +fn post_load(world: &mut World){ + let full_path = "/home/ckaos/projects/grappling-boom-bot/assets/save.ron"; + match File::open(full_path) { + Ok(mut file) => { + let mut serialized_scene = Vec::new(); + if let Err(why) = file.read_to_end(&mut serialized_scene) { + error!("file read failed: {why:?}"); + } + match Deserializer::from_bytes(&serialized_scene) { + Ok(mut deserializer) => { + let result = SceneDeserializer { + type_registry: &world.resource::().read(), + } + .deserialize(&mut deserializer); + info!("deserialize done"); + match result { + Ok(scene) => { + info!("scene loaded"); + // scene.write_to_world(world, entity_map) + // println!("{:?}", scene.entities); + inject_component_data(world, scene); + /*for dyn_ent in scene.entities.iter(){ + // let mut query = scene.world.query::<(Entity, &Name, &GltfExtras, &Parent)>(); + }*/ + } + Err(why) => { + error!("deserialization failed: {why:?}"); + } + } + } + Err(why) => { + error!("deserializer creation failed: {why:?}"); + } + } + } + Err(why) => { + error!("load failed: {why:?}"); + } + } + +} + + + +#[derive(Component, Reflect, Debug, Default )] +#[reflect(Component)] +pub struct Hackish; + + + +/// unload saveables +fn unload_saveables(world: &mut World) { + let entities: Vec = world + .query_filtered::>()// our level/world contains this component + .iter(world) + .collect(); + for entity in entities { + // Check the entity again in case it was despawned recursively + if world.get_entity(entity).is_some() { + info!("despawning"); + world.entity_mut(entity).despawn_recursive(); + } + } +} \ No newline at end of file diff --git a/examples/advanced/core/save_load/saveable.rs b/examples/advanced/core/save_load/saveable.rs new file mode 100644 index 0000000..c944fc0 --- /dev/null +++ b/examples/advanced/core/save_load/saveable.rs @@ -0,0 +1,17 @@ +use bevy::prelude::*; +use bevy::utils::{Uuid}; + +#[derive(Component, Reflect, Debug, )] +#[reflect(Component)] +pub struct Saveable{ + id: Uuid +} + +impl Default for Saveable{ + fn default() -> Self { + Saveable{ + id: Uuid::new_v4() + } + } +} + diff --git a/examples/advanced/core/save_load/saving.rs b/examples/advanced/core/save_load/saving.rs new file mode 100644 index 0000000..1e78322 --- /dev/null +++ b/examples/advanced/core/save_load/saving.rs @@ -0,0 +1,95 @@ +use bevy::pbr::{Clusters, VisiblePointLights}; +use bevy::render::camera::CameraRenderGraph; +use bevy::render::view::VisibleEntities; +use bevy::{prelude::*, gltf::GltfExtras}; +use bevy::tasks::IoTaskPool; +use bevy_rapier3d::prelude::RigidBody; +use std::io::Write; +use std::fs::File; + +use crate::core::physics::{Collider, RigidBodyProxy}; +use crate::game::{Pickable, Player}; + +use super::Saveable; + + +const NEW_SCENE_FILE_PATH:&str="save.scn.ron"; + +#[derive(Component, Event)] +pub struct SaveRequest { + pub path: String, +} + + +pub fn should_save( + // keycode: Res>, + save_requested_events: EventReader, + +) -> bool { + return save_requested_events.len() > 0; + + // return keycode.just_pressed(KeyCode::S) +} + +pub fn save_game( + world: &mut World, + // save_requested_events: EventReader, +){ + info!("saving"); + // world. + /*for bli in save_requested_events.iter(){ + println!("SAAAAVE TO THISSSSS {:?}", bli.path) + }*/ + + let saveable_entities: Vec = world + .query_filtered::>() + .iter(world) + .collect(); + + /*let static_entities: Vec = world + .query_filtered::>() + .iter(world) + .collect();*/ + println!("saveable entities {}", saveable_entities.len()); + + let mut scene_builder = DynamicSceneBuilder::from_world(world); + scene_builder + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + + .deny::() + .deny::() + .deny::() + .deny::() + + // camera stuff + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + .deny::() + //.deny::() + + + .extract_entities(saveable_entities.into_iter()); + + + + let dyn_scene = scene_builder.build(); + let serialized_scene = dyn_scene.serialize_ron(world.resource::()).unwrap(); + + #[cfg(not(target_arch = "wasm32"))] + IoTaskPool::get() + .spawn(async move { + // Write the scene RON data to file + File::create(format!("assets/scenes/{NEW_SCENE_FILE_PATH}")) + .and_then(|mut file| file.write(serialized_scene.as_bytes())) + .expect("Error while writing scene to file"); + }) + .detach(); +} \ No newline at end of file diff --git a/examples/advanced/game/in_game.rs b/examples/advanced/game/in_game.rs new file mode 100644 index 0000000..4167c1a --- /dev/null +++ b/examples/advanced/game/in_game.rs @@ -0,0 +1,30 @@ +use bevy::prelude::*; + +use crate::{assets::GameAssets, state::{InAppRunning, GameState}}; +use bevy_gltf_blueprints::GameWorldTag; + +pub fn setup_game( + mut commands: Commands, + game_assets: Res, + mut next_game_state: ResMut>, +) { + + println!("setting up all stuff"); + commands.insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 0.2, + }); + // here we actually spawn our game world/level + + commands.spawn(( + SceneBundle { + scene: game_assets.world.clone(), + ..default() + }, + bevy::prelude::Name::from("world"), + GameWorldTag, + InAppRunning + )); + + next_game_state.set(GameState::InGame) +} \ No newline at end of file diff --git a/examples/advanced/game/in_main_menu.rs b/examples/advanced/game/in_main_menu.rs new file mode 100644 index 0000000..138d6d6 --- /dev/null +++ b/examples/advanced/game/in_main_menu.rs @@ -0,0 +1,116 @@ +use bevy::prelude::*; + +use crate::{state::{AppState, GameState, InMainMenu}, core::save_load::{LoadRequest, SaveRequest}}; + +pub fn setup_main_menu(mut commands: Commands){ + commands.spawn((Camera2dBundle::default(), InMainMenu)); + + commands.spawn(( + TextBundle::from_section( + "SOME GAME TITLE !!", + TextStyle { + //font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(100.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu + )); + + + commands.spawn(( + TextBundle::from_section( + "New Game (press Enter to start, press T once the game is started for demo spawning)", + TextStyle { + //font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(200.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu + )); + + /* + commands.spawn(( + TextBundle::from_section( + "Load Game", + TextStyle { + //font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(250.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu + )); + + commands.spawn(( + TextBundle::from_section( + "Exit Game", + TextStyle { + //font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 18.0, + color: Color::WHITE, + ..Default::default() + }, + ) + .with_style(Style { + position_type: PositionType::Absolute, + top: Val::Px(300.0), + left: Val::Px(200.0), + ..default() + }), + InMainMenu + ));*/ +} + +pub fn teardown_main_menu(bla: Query>, mut commands: Commands){ + for bli in bla.iter(){ + commands.entity(bli).despawn_recursive(); + } +} + +pub fn main_menu( + keycode: Res>, + + mut next_app_state: ResMut>, + // mut next_game_state: ResMut>, + + mut save_requested_events: EventWriter, + mut load_requested_events: EventWriter, + +){ + if keycode.just_pressed(KeyCode::Return) { + next_app_state.set(AppState::AppLoading); + // next_game_state.set(GameState::None); + } + + if keycode.just_pressed(KeyCode::L) { + next_app_state.set(AppState::AppLoading); + // load_requested_events.send(LoadRequest { path: "toto".into() }) + } + + if keycode.just_pressed(KeyCode::S) { + // save_requested_events.send(SaveRequest { path: "toto".into() }) + } +} \ No newline at end of file diff --git a/examples/advanced/game/mod.rs b/examples/advanced/game/mod.rs new file mode 100644 index 0000000..81691c5 --- /dev/null +++ b/examples/advanced/game/mod.rs @@ -0,0 +1,136 @@ +pub mod in_game; +pub use in_game::*; + +pub mod in_main_menu; +pub use in_main_menu::*; + +pub mod picking; +pub use picking::*; + +use bevy::prelude::*; +use bevy_rapier3d::prelude::*; +use crate::{insert_dependant_component, state::{AppState, GameState}}; + + + +// this file is just for demo purposes, contains various types of components, systems etc + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub enum SoundMaterial{ + Metal, + Wood, + Rock, + Cloth, + Squishy, + #[default] + None +} + + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +/// Demo marker component +pub struct Player; + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +/// Demo component showing auto injection of components +pub struct ShouldBeWithPlayer; + + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +/// Demo marker component +pub struct Interactible; + + + + + +fn player_move_demo( + keycode: Res>, + mut players: Query<&mut Transform, With>, +){ + + let speed = 0.2; + if let Ok(mut player) = players.get_single_mut() { + if keycode.pressed(KeyCode::Left) { + player.translation.x += speed; + } + if keycode.pressed(KeyCode::Right) { + player.translation.x -= speed; + } + + if keycode.pressed(KeyCode::Up) { + player.translation.z += speed; + } + if keycode.pressed(KeyCode::Down) { + player.translation.z -= speed; + } + } +} + +// collision tests/debug +pub fn test_collision_events( + mut collision_events: EventReader, + mut contact_force_events: EventReader, +) +{ + for collision_event in collision_events.iter() { + println!("collision"); + match collision_event { + CollisionEvent::Started(_entity1, _entity2 ,_) => { + println!("collision started") + } + CollisionEvent::Stopped(_entity1, _entity2 ,_) => { + println!("collision ended") + + } + } + } + + for contact_force_event in contact_force_events.iter() { + println!("Received contact force event: {:?}", contact_force_event); + } +} + + +pub struct GamePlugin; +impl Plugin for GamePlugin { + fn build(&self, app: &mut App) { + app + .add_plugins(PickingPlugin) + + .register_type::() + .register_type::() + .register_type::() + // little helper utility, to automatically inject components that are dependant on an other component + // ie, here an Entity with a Player component should also always have a ShouldBeWithPlayer component + // you get a warning if you use this, as I consider this to be stop-gap solution (usually you should have either a bundle, or directly define all needed components) + .add_systems(Update, ( + // insert_dependant_component::, + player_move_demo, //.run_if(in_state(AppState::Running)), + // test_collision_events + ) + .run_if(in_state(GameState::InGame))) + + + .add_systems( + OnEnter(AppState::MenuRunning), + setup_main_menu + ) + .add_systems( + OnExit(AppState::MenuRunning), + teardown_main_menu + ) + .add_systems(Update, (main_menu)) + + + .add_systems( + OnEnter(AppState::AppRunning), + setup_game + ) + ; + } +} diff --git a/examples/advanced/game/picking.rs b/examples/advanced/game/picking.rs new file mode 100644 index 0000000..5f05cfa --- /dev/null +++ b/examples/advanced/game/picking.rs @@ -0,0 +1,40 @@ + +use bevy::prelude::*; +use super::Player; + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub struct Pickable; + +// very simple, crude picking (as in picking up objects) implementation + +pub fn picking( + players: Query<&GlobalTransform, With>, + pickables: Query<(Entity, &GlobalTransform), With>, + mut commands: Commands +){ + for player_transforms in players.iter(){ + for (pickable, pickable_transforms) in pickables.iter(){ + let distance = player_transforms.translation().distance(pickable_transforms.translation()); + if distance < 2.5 { + commands.entity(pickable).despawn_recursive(); + } + } + } +} + +pub struct PickingPlugin; +impl Plugin for PickingPlugin { + fn build(&self, app: &mut App) { + app + .register_type::() + + .add_systems(Update, ( + picking, //.run_if(in_state(AppState::Running)), + )) + + + + ; + } +} diff --git a/examples/advanced/main.rs b/examples/advanced/main.rs new file mode 100644 index 0000000..ecbee4e --- /dev/null +++ b/examples/advanced/main.rs @@ -0,0 +1,51 @@ +use std::time::Duration; +use bevy::{prelude::*, asset::ChangeWatcher, gltf::Gltf}; +use bevy_editor_pls::prelude::*; +use bevy_rapier3d::prelude::*; +use bevy_gltf_components::ComponentsFromGltfPlugin; + +mod core; +use crate::core::*; + +pub mod assets; +use assets::*; + +pub mod state; +use state::*; + +mod game; +use game::*; + +mod test_components; +use test_components::*; + + + +fn main(){ + App::new() + .add_plugins(( + DefaultPlugins.set( + AssetPlugin { + // This tells the AssetServer to watch for changes to assets. + // It enables our scenes to automatically reload in game when we modify their files. + // practical in our case to be able to edit the shaders without needing to recompile + // watch_for_changes: ChangeWatcher::with_delay(Duration::from_millis(50)), : FIXME: breaks scene save/loading + ..default() + } + ), + // editor + EditorPlugin::default(), + // physics + RapierPhysicsPlugin::::default(), + RapierDebugRenderPlugin::default(), + // our custom plugins + ComponentsFromGltfPlugin, + + StatePlugin, + AssetsPlugin, + CorePlugin, // reusable plugins + GamePlugin, // specific to our game + ComponentsTestPlugin // Showcases different type of components /structs + )) + .run(); +} diff --git a/examples/advanced/state.rs b/examples/advanced/state.rs new file mode 100644 index 0000000..7d87c58 --- /dev/null +++ b/examples/advanced/state.rs @@ -0,0 +1,58 @@ +use bevy::prelude::*; +use bevy::app::AppExit; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)] +pub enum AppState{ + #[default] + CoreLoading, + MenuRunning, + AppLoading, + AppRunning, + AppEnding, + + // FIXME: not sure + LoadingGame +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)] +pub enum GameState { + #[default] + None, + + InMenu, + InGame, + + InGameOver, + + InSaving, + InLoading +} + +// tag components for all entities within a certain state (for despawning them if needed) , FIXME: seems kinda hack-ish +#[derive(Component)] +pub struct InCoreLoading; +#[derive(Component, Default)] +pub struct InMenuRunning; +#[derive(Component)] +pub struct InAppLoading; +#[derive(Component)] +pub struct InAppRunning; + +// components for tagging in game vs in game menu stuff +#[derive(Component, Default)] +pub struct InMainMenu; +#[derive(Component, Default)] +pub struct InMenu; +#[derive(Component, Default)] +pub struct InGame; + + +pub struct StatePlugin; +impl Plugin for StatePlugin { + fn build(&self, app: &mut App) { + app + .add_state::() + .add_state::() + ; + } +} \ No newline at end of file diff --git a/examples/general/test_components.rs b/examples/advanced/test_components.rs similarity index 100% rename from examples/general/test_components.rs rename to examples/advanced/test_components.rs diff --git a/examples/general/README.md b/examples/basic/README.md similarity index 100% rename from examples/general/README.md rename to examples/basic/README.md diff --git a/examples/basic/core/camera/camera_replace_proxies.rs b/examples/basic/core/camera/camera_replace_proxies.rs new file mode 100644 index 0000000..9bd5859 --- /dev/null +++ b/examples/basic/core/camera/camera_replace_proxies.rs @@ -0,0 +1,34 @@ + +use bevy::prelude::*; +use bevy::core_pipeline::bloom::{BloomSettings, BloomCompositeMode}; +use bevy::core_pipeline::tonemapping::{Tonemapping, DebandDither}; + +use super::CameraTrackingOffset; + +pub fn camera_replace_proxies ( + mut commands: Commands, + mut added_cameras: Query<(Entity, &mut Camera), (Added, With)>, +) { + + for (entity, mut camera) in added_cameras.iter_mut(){ + info!("detected added camera, updating proxy"); + camera.hdr = true; + commands.entity(entity) + .insert( + DebandDither::Enabled + ) + .insert( + Tonemapping::BlenderFilmic + ) + .insert( + BloomSettings{ + intensity: 0.01, + composite_mode:BloomCompositeMode::Additive, + ..default() + } + ) + + ; + } +} + diff --git a/examples/basic/core/camera/camera_tracking.rs b/examples/basic/core/camera/camera_tracking.rs new file mode 100644 index 0000000..ec3a45f --- /dev/null +++ b/examples/basic/core/camera/camera_tracking.rs @@ -0,0 +1,57 @@ +use bevy::prelude::*; + + +#[derive(Component, Reflect, Debug)] +#[reflect(Component)] +/// Component for cameras, with an offset from the Trackable target +/// +pub struct CameraTracking{ + pub offset: Vec3 +} +impl Default for CameraTracking { + fn default() -> Self { + CameraTracking { offset: Vec3::new(0.0, 6.0, 8.0) } + } +} + + +#[derive(Component, Reflect, Debug, Deref, DerefMut)] +#[reflect(Component)] +/// Component for cameras, with an offset from the Trackable target +pub struct CameraTrackingOffset(Vec3); +impl Default for CameraTrackingOffset { + fn default() -> Self { + CameraTrackingOffset(Vec3::new(0.0, 6.0, 8.0)) + } +} + +impl CameraTrackingOffset { + fn new (input: Vec3) -> Self { + CameraTrackingOffset(input) + } +} + + + + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +/// Add this component to an entity if you want it to be tracked by a Camera +pub struct CameraTrackable; + +pub fn camera_track( + mut tracking_cameras: Query<(&mut Transform, &CameraTrackingOffset), (With, With, Without)>, + camera_tracked: Query<&Transform, With>, +) { + + for (mut camera_transform, tracking_offset) in tracking_cameras.iter_mut() { + for tracked_transform in camera_tracked.iter(){ + + let target_position = tracked_transform.translation + tracking_offset.0; + let eased_position = camera_transform.translation.lerp(target_position, 0.1); + camera_transform.translation = eased_position;// + tracking.offset;// tracked_transform.translation + tracking.offset; + *camera_transform = camera_transform.looking_at(tracked_transform.translation, Vec3::Y); + } + } + +} diff --git a/examples/general/core/camera/mod.rs b/examples/basic/core/camera/mod.rs similarity index 100% rename from examples/general/core/camera/mod.rs rename to examples/basic/core/camera/mod.rs diff --git a/examples/basic/core/lighting/lighting_replace_proxies.rs b/examples/basic/core/lighting/lighting_replace_proxies.rs new file mode 100644 index 0000000..9e1d31d --- /dev/null +++ b/examples/basic/core/lighting/lighting_replace_proxies.rs @@ -0,0 +1,29 @@ +use bevy::prelude::*; + +use bevy::pbr::{CascadeShadowConfigBuilder, CascadeShadowConfig}; + +// fixme might be too specific to might needs, should it be moved out ? also these are all for lights, not models +pub fn lighting_replace_proxies( + mut added_dirights: Query<(Entity, &mut DirectionalLight), Added>, + mut added_spotlights: Query<&mut SpotLight, Added>, + mut commands: Commands, + +){ + + for (entity, mut light) in added_dirights.iter_mut(){ + light.illuminance *= 5.0; + light.shadows_enabled = true; + let shadow_config:CascadeShadowConfig = CascadeShadowConfigBuilder { + first_cascade_far_bound: 15.0, + maximum_distance: 135.0, + ..default() + } + .into(); + commands.entity(entity) + .insert(shadow_config); + } + for mut light in added_spotlights.iter_mut(){ + light.shadows_enabled = true; + } +} + diff --git a/examples/basic/core/lighting/mod.rs b/examples/basic/core/lighting/mod.rs new file mode 100644 index 0000000..d1744b7 --- /dev/null +++ b/examples/basic/core/lighting/mod.rs @@ -0,0 +1,18 @@ +mod lighting_replace_proxies; +use lighting_replace_proxies::*; + +use bevy::prelude::*; +use bevy::pbr::{NotShadowCaster, DirectionalLightShadowMap}; + +pub struct LightingPlugin; +impl Plugin for LightingPlugin { + fn build(&self, app: &mut App) { + app + .insert_resource(DirectionalLightShadowMap { size: 4096 }) + // FIXME: adding these since they are missing + .register_type::() + + .add_systems(PreUpdate, lighting_replace_proxies) // FIXME: you should actually run this in a specific state most likely + ; + } +} \ No newline at end of file diff --git a/examples/general/core/mod.rs b/examples/basic/core/mod.rs similarity index 100% rename from examples/general/core/mod.rs rename to examples/basic/core/mod.rs diff --git a/examples/general/core/physics/controls.rs b/examples/basic/core/physics/controls.rs similarity index 100% rename from examples/general/core/physics/controls.rs rename to examples/basic/core/physics/controls.rs diff --git a/examples/general/core/physics/mod.rs b/examples/basic/core/physics/mod.rs similarity index 100% rename from examples/general/core/physics/mod.rs rename to examples/basic/core/physics/mod.rs diff --git a/examples/general/core/physics/physics_replace_proxies.rs b/examples/basic/core/physics/physics_replace_proxies.rs similarity index 100% rename from examples/general/core/physics/physics_replace_proxies.rs rename to examples/basic/core/physics/physics_replace_proxies.rs diff --git a/examples/basic/core/physics/utils.rs b/examples/basic/core/physics/utils.rs new file mode 100644 index 0000000..7886710 --- /dev/null +++ b/examples/basic/core/physics/utils.rs @@ -0,0 +1,175 @@ +use bevy::prelude::*; +use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues}; +// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/src/util/trait_extension.rs + +pub(crate) trait Vec3Ext: Copy { + fn is_approx_zero(self) -> bool; + fn split(self, up: Vec3) -> SplitVec3; +} +impl Vec3Ext for Vec3 { + #[inline] + fn is_approx_zero(self) -> bool { + self.length_squared() < 1e-5 + } + + #[inline] + fn split(self, up: Vec3) -> SplitVec3 { + let vertical = up * self.dot(up); + let horizontal = self - vertical; + SplitVec3 { + vertical, + horizontal, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct SplitVec3 { + pub(crate) vertical: Vec3, + pub(crate) horizontal: Vec3, +} + +pub(crate) trait Vec2Ext: Copy { + fn is_approx_zero(self) -> bool; + fn x0y(self) -> Vec3; +} +impl Vec2Ext for Vec2 { + #[inline] + fn is_approx_zero(self) -> bool { + self.length_squared() < 1e-5 + } + + #[inline] + fn x0y(self) -> Vec3 { + Vec3::new(self.x, 0., self.y) + } +} + +pub(crate) trait MeshExt { + fn transform(&mut self, transform: Transform); + fn transformed(&self, transform: Transform) -> Mesh; + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]>; + fn search_in_children<'a>( + parent: Entity, + children: &'a Query<&Children>, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> Vec<(Entity, &'a Mesh)>; +} + +impl MeshExt for Mesh { + fn transform(&mut self, transform: Transform) { + for coords in self.read_coords_mut(Mesh::ATTRIBUTE_POSITION.clone()) { + let vec3 = (*coords).into(); + let transformed = transform.transform_point(vec3); + *coords = transformed.into(); + } + for normal in self.read_coords_mut(Mesh::ATTRIBUTE_NORMAL.clone()) { + let vec3 = (*normal).into(); + let transformed = transform.rotation.mul_vec3(vec3); + *normal = transformed.into(); + } + } + + fn transformed(&self, transform: Transform) -> Mesh { + let mut mesh = self.clone(); + mesh.transform(transform); + mesh + } + + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]> { + // Guaranteed by Bevy for the current usage + match self + .attribute_mut(id) + .expect("Failed to read unknown mesh attribute") + { + VertexAttributeValues::Float32x3(values) => values, + // Guaranteed by Bevy for the current usage + _ => unreachable!(), + } + } + + fn search_in_children<'a>( + parent: Entity, + children_query: &'a Query<&Children>, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> Vec<(Entity, &'a Mesh)> { + if let Ok(children) = children_query.get(parent) { + let mut result: Vec<_> = children + .iter() + .filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh))) + .map(|(entity, mesh_handle)| { + ( + entity, + meshes + .get(mesh_handle) + .expect("Failed to get mesh from handle"), + ) + }) + .map(|(entity, mesh)| { + assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList); + (entity, mesh) + }) + .collect(); + let mut inner_result = children + .iter() + .flat_map(|entity| { + Self::search_in_children(*entity, children_query, meshes, mesh_handles) + }) + .collect(); + result.append(&mut inner_result); + result + } else { + Vec::new() + } + } +} + +pub(crate) trait F32Ext: Copy { + fn is_approx_zero(self) -> bool; + fn squared(self) -> f32; + fn lerp(self, other: f32, ratio: f32) -> f32; +} + +impl F32Ext for f32 { + #[inline] + fn is_approx_zero(self) -> bool { + self.abs() < 1e-5 + } + + #[inline] + fn squared(self) -> f32 { + self * self + } + + #[inline] + fn lerp(self, other: f32, ratio: f32) -> f32 { + self.mul_add(1. - ratio, other * ratio) + } +} + +pub(crate) trait TransformExt: Copy { + fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform; + fn lerp(self, other: Transform, ratio: f32) -> Transform; +} + +impl TransformExt for Transform { + fn horizontally_looking_at(self, target: Vec3, up: Vec3) -> Transform { + let direction = target - self.translation; + let horizontal_direction = direction - up * direction.dot(up); + let look_target = self.translation + horizontal_direction; + self.looking_at(look_target, up) + } + + fn lerp(self, other: Transform, ratio: f32) -> Transform { + let translation = self.translation.lerp(other.translation, ratio); + let rotation = self.rotation.slerp(other.rotation, ratio); + let scale = self.scale.lerp(other.scale, ratio); + Transform { + translation, + rotation, + scale, + } + } +} diff --git a/examples/basic/core/physics/utils_old.rs b/examples/basic/core/physics/utils_old.rs new file mode 100644 index 0000000..c210dd3 --- /dev/null +++ b/examples/basic/core/physics/utils_old.rs @@ -0,0 +1,75 @@ +use bevy::prelude::*; +use bevy::render::mesh::{MeshVertexAttributeId, PrimitiveTopology, VertexAttributeValues}; +// TAKEN VERBATIB FROM https://github.com/janhohenheim/foxtrot/blob/6e31fc02652fc9d085a4adde0a73ab007dbbb0dc/src/util/trait_extension.rs + +pub trait Vec3Ext { + #[allow(clippy::wrong_self_convention)] // Because [`Vec3`] is [`Copy`] + fn is_approx_zero(self) -> bool; + fn x0z(self) -> Vec3; +} +impl Vec3Ext for Vec3 { + fn is_approx_zero(self) -> bool { + [self.x, self.y, self.z].iter().all(|&x| x.abs() < 1e-5) + } + fn x0z(self) -> Vec3 { + Vec3::new(self.x, 0., self.z) + } +} + +pub trait MeshExt { + fn transform(&mut self, transform: Transform); + fn transformed(&self, transform: Transform) -> Mesh; + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]>; + fn search_in_children<'a>( + children: &'a Children, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> (Entity, &'a Mesh); +} + +impl MeshExt for Mesh { + fn transform(&mut self, transform: Transform) { + for attribute in [Mesh::ATTRIBUTE_POSITION, Mesh::ATTRIBUTE_NORMAL] { + for coords in self.read_coords_mut(attribute.clone()) { + let vec3 = (*coords).into(); + let transformed = transform.transform_point(vec3); + *coords = transformed.into(); + } + } + } + + fn transformed(&self, transform: Transform) -> Mesh { + let mut mesh = self.clone(); + mesh.transform(transform); + mesh + } + + fn read_coords_mut(&mut self, id: impl Into) -> &mut Vec<[f32; 3]> { + match self.attribute_mut(id).unwrap() { + VertexAttributeValues::Float32x3(values) => values, + // Guaranteed by Bevy + _ => unreachable!(), + } + } + + fn search_in_children<'a>( + children: &'a Children, + meshes: &'a Assets, + mesh_handles: &'a Query<&Handle>, + ) -> (Entity, &'a Mesh) { + let entity_handles: Vec<_> = children + .iter() + .filter_map(|entity| mesh_handles.get(*entity).ok().map(|mesh| (*entity, mesh))) + .collect(); + assert_eq!( + entity_handles.len(), + 1, + "Collider must contain exactly one mesh, but found {}", + entity_handles.len() + ); + let (entity, mesh_handle) = entity_handles.first().unwrap(); + let mesh = meshes.get(mesh_handle).unwrap(); + assert_eq!(mesh.primitive_topology(), PrimitiveTopology::TriangleList); + (*entity, mesh) + } +} diff --git a/examples/basic/core/relationships/mod.rs b/examples/basic/core/relationships/mod.rs new file mode 100644 index 0000000..6345d35 --- /dev/null +++ b/examples/basic/core/relationships/mod.rs @@ -0,0 +1,11 @@ +pub mod relationships_insert_dependant_components; +pub use relationships_insert_dependant_components::*; + +use bevy::prelude::*; + +pub struct EcsRelationshipsPlugin; +impl Plugin for EcsRelationshipsPlugin { + fn build(&self, app: &mut App) { + app; + } +} \ No newline at end of file diff --git a/examples/basic/core/relationships/relationships_insert_dependant_components.rs b/examples/basic/core/relationships/relationships_insert_dependant_components.rs new file mode 100644 index 0000000..afec54e --- /dev/null +++ b/examples/basic/core/relationships/relationships_insert_dependant_components.rs @@ -0,0 +1,16 @@ +use bevy::prelude::*; + +pub fn insert_dependant_component( + mut commands: Commands, + entities_without_depency: Query<(Entity, &Name), (With, Without)>, +) { + for (entity, name) in entities_without_depency.iter() { + let name = name.clone().to_string(); + commands.entity(entity) + .insert( + Dependency::default() + ) + ; + warn!("found an entity called {} with a {} component but without an {}, please check your assets", name.clone(), std::any::type_name::(), std::any::type_name::()); + } +} \ No newline at end of file diff --git a/examples/general/game.rs b/examples/basic/game.rs similarity index 100% rename from examples/general/game.rs rename to examples/basic/game.rs diff --git a/examples/general/main.rs b/examples/basic/main.rs similarity index 97% rename from examples/general/main.rs rename to examples/basic/main.rs index 333f167..5ddc261 100644 --- a/examples/general/main.rs +++ b/examples/basic/main.rs @@ -73,7 +73,7 @@ fn setup( asset_server: Res, ) { - let tmp: Handle = asset_server.load("models/level1.glb#Scene0"); + let tmp: Handle = asset_server.load("basic/models/level1.glb#Scene0"); commands.insert_resource(AssetLoadHelper(tmp)); } diff --git a/examples/basic/test_components.rs b/examples/basic/test_components.rs new file mode 100644 index 0000000..79a45f6 --- /dev/null +++ b/examples/basic/test_components.rs @@ -0,0 +1,86 @@ +use bevy::prelude::*; + + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct UnitTest; + +#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] +#[reflect(Component)] +struct TuppleTestF32(f32); + +#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] +#[reflect(Component)] +struct TuppleTestU64(u64); + +#[derive(Component, Reflect, Default, Debug, Deref, DerefMut)] +#[reflect(Component)] +pub struct TuppleTestStr(String); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct TuppleTest2(f32, u64, String); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct TuppleTestBool(bool); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct TuppleVec2(Vec2); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct TuppleVec3(Vec3); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct TuppleVec(Vec); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct TuppleTestColor(Color); + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +struct BasicTest{ + a: f32, + b: u64, + c: String +} + +#[derive(Component, Reflect, Default, Debug, )] +#[reflect(Component)] +pub enum EnumTest{ + Metal, + Wood, + Rock, + Cloth, + Squishy, + #[default] + None +} + + +pub struct ComponentsTestPlugin; +impl Plugin for ComponentsTestPlugin { + fn build(&self, app: &mut App) { + app + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + + .register_type::() + .register_type::>() + + ; + } +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 45cf1fc..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,57 +0,0 @@ -pub mod utils; -pub use utils::*; - -pub mod gltf_to_components; -pub use gltf_to_components::*; - -pub mod process_gltfs; -pub use process_gltfs::*; - -use bevy::prelude::{ - App,Plugin, PreUpdate -}; - - -/// A Bevy plugin for extracting components from gltf files and automatically adding them to the relevant entities -/// It will automatically run every time you load a gltf file -/// Add this plugin to your Bevy app to get access to this feature -/// ``` -/// # use bevy::prelude::*; -/// # use bevy_gltf_components::ComponentsFromGltfPlugin::*; -/// -/// -/// fn main() { -/// App::new() -/// .add_plugins(DefaultPlugins) -/// .add_plugin(GltfComponentsPlugin) -/// .add_startup_system(setup) -/// .add_system(spawn_level) -/// .run(); -/// } -/// -/// fn setup( -/// mut state: ResMut, -/// asset_server: Res, -/// mut commands: bevy::prelude::Commands -/// ){ -/// asset_server.load("models/level1.glb#Scene0") -/// } -/// -/// fn spawn_level(){ -/// -///} -/// ``` -#[derive(Default)] -pub struct ComponentsFromGltfPlugin; -impl Plugin for ComponentsFromGltfPlugin { - fn build(&self, app: &mut App) { - app - .insert_resource(GltfLoadingTracker::new()) - - .add_systems(PreUpdate, ( - track_new_gltf, - process_loaded_scenes, - )) - ; - } -} diff --git a/tools/blender_auto_export/README.md b/tools/blender_auto_export/README.md deleted file mode 100644 index 4b22283..0000000 --- a/tools/blender_auto_export/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# gltf_auto_export - -For convenience I also added this [Blender addon](./blender_auto_export_gltf.py) that automatically exports your level/world from Blender to gltf whenever you save your Blend file -(actually when you save inside your level/world scene or in the "library" scene, where I personally usually store all collections to instanciate). -It is **very** barebones and messy, but it does a minimal ok job. - -### Installation: - -* in Blender go to edit => preferences => install - -![blender addon install](../../docs/blender_addon_install.png) - -* choose the path where ```blender_auto_export/blender_auto_export_gltf.py``` is stored - -![blender addon install](../../docs/blender_addon_install2.png) - -### Usage: - -* before it can automatically save to gltf, you need to configure it -* go to file => export => gltf auto export - -![blender addon use](../../docs/blender_addon_use.png) - -* set up your parameters: output path, name of your main scene etc - - ![blender addon use2](../../docs/blender_addon_use2.png) - -* click on "apply settings" -* now next time you save your blend file you will get an automatically exported gltf file diff --git a/tools/gltf_auto_export/README.md b/tools/gltf_auto_export/README.md new file mode 100644 index 0000000..37894dc --- /dev/null +++ b/tools/gltf_auto_export/README.md @@ -0,0 +1,85 @@ +# gltf_auto_export + +For convenience I also added this [Blender addon](./gltf_auto_export.py) that +- automatically exports your level/world from Blender to gltf whenever you save your Blend file. +- it also supports automatical exports of used collections as [Gltf blueprints](../../crates/bevy_gltf_blueprints/README.md) & more ! + + +## Installation: + +* in Blender go to edit => preferences => install + +![blender addon install](../../docs/blender_addon_install.png) + +* choose the path where ```gltf_auto_export/gltf_auto_export.py``` is stored + +![blender addon install](../../docs/blender_addon_install2.png) + +## Usage: + + +### Basics + +* before it can automatically save to gltf, you need to configure it +* go to file => export => gltf auto export + +![blender addon use](../../docs/blender_addon_use.png) + +* set the autoexport parameters : output path, name of your main scene etc in the **auto export** panel + +![blender addon use3](../../docs/blender_addon_use3.png) + +* and your standard gltf export parameters in the **gltf** panel + +![blender addon use2](../../docs/blender_addon_use2.png) + + +* click on "apply settings" +* now next time you save your blend file you will get an automatically exported gltf file (or more than one, depending on your settings, see below) + +### Blueprints + +You can enable this option to automatically replace all the **collection instances** inside your main scene with blueprints +- whenever you change your main scene (or your library scene , if that option is enabled), all your collection instances + * will be replaced with empties (this will not be visible to you) + * those empties will have additional custom properties / components : ```BlueprintName``` & ```SpawnHere``` + * your main scene/ level will be exported to a much more trimmed down gltf file (see next point) + * all the original collections (that you used to create the instances) will be exported as **seperate gltf files** into the "library" folder + +- this means you will have + * one small main gltf file (your level/world) + * as many gltf files as you have used collections in the main scene , in the library path you specified : + for the included [advanced](../../examples/advanced/) example's [assets](../../assets/advanced/models/), it looks something like this: + + ![library](../../docs/exported_library_files.png) + + the .blend file that they are generated from can be found [here](../../assets/advanced/advanced.blend) + +#### Process + +This is the internal logic of the export process with blueprints + +![process](../../docs/process.svg) + +ie this is an example scene... + +![](../../docs/workflow_original.jpg) + +and what actually gets exported for the main scene/world/level + +![](../../docs/workflow_empties.jpg) + +all collections instances replaced with empties, and all those collections exported to gltf files as seen above + + +### TODO: + +- [ ] add ability to have multiple main & library scenes +- [ ] detect which objects have been changed to only re-export those + +## License + +This tool, all its code, contents & assets is Dual-licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE_APACHE.md) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../LICENSE_MIT.md) or https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/tools/blender_auto_export/__init__.py b/tools/gltf_auto_export/__init__.py similarity index 75% rename from tools/blender_auto_export/__init__.py rename to tools/gltf_auto_export/__init__.py index 2cf9e04..5bbf3c6 100644 --- a/tools/blender_auto_export/__init__.py +++ b/tools/gltf_auto_export/__init__.py @@ -1,4 +1,4 @@ -#TODO: this is not actually in use yet, just use the blender_auto_export_gltf.py file +#TODO: this is not actually in use yet, just use the gltf_auto_export.py file bl_info = { "name": "Test glTF/glb auto-export", "author": "kaosigh", @@ -14,11 +14,11 @@ bl_info = { import bpy -from .blender_auto_export_gltf import TEST_AUTO_OT_gltf -from .blender_auto_export_gltf import deps_update_handler -from .blender_auto_export_gltf import save_handler -from .blender_auto_export_gltf import get_changedScene -from .blender_auto_export_gltf import set_ChangedScene +from .gltf_auto_export import TEST_AUTO_OT_gltf +from .gltf_auto_export import deps_update_handler +from .gltf_auto_export import save_handler +from .gltf_auto_export import get_changedScene +from .gltf_auto_export import set_ChangedScene # Only needed if you want to add into a dynamic menu diff --git a/tools/gltf_auto_export/gltf_auto_export.py b/tools/gltf_auto_export/gltf_auto_export.py new file mode 100644 index 0000000..20174bf --- /dev/null +++ b/tools/gltf_auto_export/gltf_auto_export.py @@ -0,0 +1,911 @@ +bl_info = { + "name": "gltf_auto_export", + "author": "kaosigh", + "version": (0, 1), + "blender": (3, 4, 0), + "location": "File > Import-Export", + "description": "glTF/glb auto-export", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Import-Export" +} + +import os +import bpy +from bpy.types import Operator, AddonPreferences +from bpy.app.handlers import persistent +from bpy_extras.io_utils import ExportHelper +from bpy.props import (BoolProperty, + IntProperty, + StringProperty, + EnumProperty, + CollectionProperty + ) + + + + + +#see here for original gltf exporter infos https://github.com/KhronosGroup/glTF-Blender-IO/blob/main/addons/io_scene_gltf2/__init__.py +@persistent +def deps_update_handler(scene, depsgraph): + + #print("depsgraph_update_post", scene.name) + """ + print("-------------") + changed_objects = [] + for obj in depsgraph.updates: + if isinstance(obj.id, bpy.types.Object): + print("object changed, amazing", obj.id, obj.id.name) + changed_objects.append(obj) + """ + changed = scene.name or "" + # bpy.context.scene.changedObjects = changed_objects + bpy.context.scene.changedScene = changed + +@persistent +def save_handler(dummy): + print("-------------") + print("saved", bpy.data.filepath) + auto_export() + + +def get_changedScene(self): + return self["changedScene"] + +def set_changedScene(self, value): + self["changedScene"] = value + + +#def get_changedObjects(self): +# return self["changedObjects"] + +#def set_changedObjects(self, value): +# self["changedObjects"] = value + +#https://docs.blender.org/api/current/bpy.ops.export_scene.html#bpy.ops.export_scene.gltf +def export_gltf (path, export_settings): + settings = {**export_settings, "filepath": path} + bpy.ops.export_scene.gltf(**settings) + + +##################################################### +#### Helpers #### + +def get_collection_hierarchy(root_col, levels=1): + """Read hierarchy of the collections in the scene""" + level_lookup = {} + def recurse(root_col, parent, depth): + if depth > levels: + return + if isinstance(parent, bpy.types.Collection): + level_lookup.setdefault(parent, []).append(root_col) + for child in root_col.children: + recurse(child, root_col, depth + 1) + recurse(root_col, root_col.children, 0) + return level_lookup + +# the active collection is a View Layer concept, so you actually have to find the active LayerCollection +# which must be done recursively +def find_layer_collection_recursive(find, col): + for c in col.children: + if c.collection == find: + return c + return None + +#Recursivly transverse layer_collection for a particular name +def recurLayerCollection(layerColl, collName): + found = None + if (layerColl.name == collName): + return layerColl + for layer in layerColl.children: + found = recurLayerCollection(layer, collName) + if found: + return found + + +# Makes an empty, at location, stores it in existing collection, from https://blender.stackexchange.com/questions/51290/how-to-add-empty-object-not-using-bpy-ops +def make_empty(name, location, coll_name): #string, vector, string of existing coll + empty_obj = bpy.data.objects.new( "empty", None, ) + empty_obj.name = name + empty_obj.empty_display_size = 1 + bpy.data.collections[coll_name].objects.link(empty_obj) + empty_obj.location = location + return empty_obj + + +def make_empty2(name, location, collection): + object_data = None #bpy.data.meshes.new("NewMesh") #None + empty_obj = bpy.data.objects.new( name, object_data ) + empty_obj.name = name + empty_obj.location = location + + + empty_obj.empty_display_size = 2 + empty_obj.empty_display_type = 'PLAIN_AXES' + collection.objects.link( empty_obj ) + return empty_obj + +def make_empty3(name, location, collection): + original_active_object = bpy.context.active_object + bpy.ops.object.empty_add(type='PLAIN_AXES', location=location) + empty_obj = bpy.context.active_object + empty_obj.name = name + collection.objects.link( empty_obj ) + bpy.context.view_layer.objects.active = original_active_object + return empty_obj + + +# generate a copy of a scene that replaces collection instances with empties +# FIXME: will not preserve original names +# alternative: copy original names before creating a new scene, & reset them +# or create empties, hide original ones, and do the same renaming trick +def generate_hollow_scene(scene): + root_collection = scene.collection + temp_scene = bpy.data.scenes.new(name="temp_scene") + copy_root_collection = temp_scene.collection + scene_objects = [o for o in root_collection.objects] + + + found = find_layer_collection_recursive(copy_root_collection, bpy.context.view_layer.layer_collection) + if found: + print("FOUND COLLECTION") + # once it's found, set the active layer collection to the one we found + bpy.context.view_layer.active_layer_collection = found + + #original_names = {} + original_names = [] + for object in scene_objects: + if object.instance_type == 'COLLECTION': + collection_name = object.instance_collection.name + + #original_names[object.name] = object.name# + "____bak" + #print("custom properties", object, object.keys(), object.items()) + #for k, e in object.items(): + # print("custom properties ", k, e) + print("object location", object.location) + original_name = object.name + original_names.append(original_name) + + object.name = original_name + "____bak" + empty_obj = make_empty3(original_name, object.location, copy_root_collection) + """we inject the collection/blueprint name, as a component called 'BlueprintName', but we only do this in the empty, not the original object""" + empty_obj['BlueprintName'] = '"'+collection_name+'"' + empty_obj['SpawnHere'] = '' + + for k, v in object.items(): + empty_obj[k] = v + else: + copy_root_collection.objects.link(object) + + # bpy.data.scenes.remove(temp_scene) + # objs = bpy.data.objects + #objs.remove(objs["Cube"], do_unlink=True) + return (temp_scene, original_names) + +# clear & remove "hollow scene" +def clear_hollow_scene(temp_scene, original_scene, original_names): + # reset original names + root_collection = original_scene.collection + scene_objects = [o for o in root_collection.objects] + + for object in scene_objects: + if object.instance_type == 'COLLECTION': + print("object name to reset", object.name) + if object.name.endswith("____bak"): + print("reseting") + object.name = object.name.replace("____bak", "") + + # remove empties (only needed when we go via ops ????) + root_collection = temp_scene.collection + scene_objects = [o for o in root_collection.objects] + for object in scene_objects: + if object.type == 'EMPTY': + bpy.data.objects.remove(object, do_unlink=True) + + bpy.data.scenes.remove(temp_scene) + + +# returns the list of the collections in use for a given scene +def get_used_collections(scene): + root_collection = scene.collection + + scene_objects = [o for o in root_collection.objects] + collection_names = set() + used_collections = [] + for object in scene_objects: + print("object ", object) + if object.instance_type == 'COLLECTION': + print("THIS OBJECT IS A COLLECTION") + # print("instance_type" ,object.instance_type) + collection_name = object.instance_collection.name + print("instance collection", object.instance_collection.name) + #object.instance_collection.users_scene + # del object['blueprint'] + # object['BlueprintName'] = '"'+collection_name+'"' + if not collection_name in collection_names: + collection_names.add(collection_name) + used_collections.append(object.instance_collection) + + print("scene objects", scene_objects) + return (collection_names, used_collections) + + +def generate_gltf_export_preferences(addon_prefs): + # default values + gltf_export_preferences = dict( + export_format= 'GLB', #'GLB', 'GLTF_SEPARATE', 'GLTF_EMBEDDED' + check_existing=False, + + use_selection=False, + use_visible=True, # Export visible and hidden objects. See Object/Batch Export to skip. + use_renderable=False, + use_active_collection= False, + use_active_collection_with_nested=False, + use_active_scene = False, + + export_texcoords=True, + export_normals=True, + # here add draco settings + export_draco_mesh_compression_enable = False, + + export_tangents=False, + #export_materials + export_colors=True, + export_attributes=True, + #use_mesh_edges + #use_mesh_vertices + export_cameras=True, + export_extras=True, # For custom exported properties. + export_lights=True, + export_yup=True, + export_skins=True, + export_morph=False, + export_apply=False, + export_animations=False + ) + + for key in addon_prefs.__annotations__.keys(): + if str(key) not in AutoExportGltfPreferenceNames: + print("overriding setting", key, "value", getattr(addon_prefs,key)) + gltf_export_preferences[key] = getattr(addon_prefs,key) + + return gltf_export_preferences + +###################################################### +#### Export logic ##### + +def export_used_collections(scene, folder_path, addon_prefs, gltf_export_preferences): + (collection_names, used_collections) = get_used_collections(scene) + library_scene = getattr(addon_prefs, "export_library_scene_name") + print("used collection names", collection_names, used_collections) + + # set active scene to be the library scene (hack for now) + bpy.context.window.scene = bpy.data.scenes[library_scene] + # save current active collection + active_collection = bpy.context.view_layer.active_layer_collection + + for collection_name in list(collection_names): + print("exporting collection", collection_name) + + layer_collection = bpy.context.view_layer.layer_collection + layerColl = recurLayerCollection(layer_collection, collection_name) + # set active collection to the collection + bpy.context.view_layer.active_layer_collection = layerColl + + gltf_output_path = os.path.join(folder_path, collection_name) + + export_settings = { **gltf_export_preferences, 'use_active_scene': True, 'use_active_collection': True} #'use_visible': False, + export_gltf(gltf_output_path, export_settings) + + # reset active collection to the one we save before + bpy.context.view_layer.active_layer_collection = active_collection + + +def export_main(scene, folder_path, addon_prefs): + output_name = getattr(addon_prefs,"export_main_output_name") + gltf_export_preferences = generate_gltf_export_preferences(addon_prefs) + print("exporting to", folder_path, output_name) + + export_blueprints = getattr(addon_prefs,"export_blueprints") + export_blueprints_path = os.path.join(folder_path, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path + + # backup current active scene + old_current_scene = bpy.context.scene + # backup current selections + old_selections = bpy.context.selected_objects + + + if export_blueprints : + print("-----EXPORTING BLUEPRINTS----") + print("LIBRARY EXPORT", export_blueprints_path ) + + try: + export_used_collections(scene, export_blueprints_path, addon_prefs, gltf_export_preferences) + except Exception as error: + print("failed to export collections to gltf: ", error) + + (hollow_scene, object_names) = generate_hollow_scene(scene) + #except Exception: + # print("failed to create hollow scene") + + # set active scene to be the given scene + bpy.context.window.scene = hollow_scene + + gltf_output_path = os.path.join(folder_path, output_name) + + export_settings = { **gltf_export_preferences, + 'use_active_scene': True, + 'use_active_collection':True, + 'use_active_collection_with_nested':True, + 'use_visible': False, + 'use_renderable': False, + 'export_apply':True + } + export_gltf(gltf_output_path, export_settings) + + if export_blueprints : + clear_hollow_scene(hollow_scene, scene, object_names) + + # reset current scene from backup + bpy.context.window.scene = old_current_scene + # reset selections + for obj in old_selections: + obj.select_set(True) + + +"""Main function""" +def auto_export(): + file_path = bpy.data.filepath + # Get the folder + folder_path = os.path.dirname(file_path) + # get the preferences for our addon + addon_prefs = bpy.context.preferences.addons[__name__].preferences + + print("last changed", bpy.context.scene.changedScene) + + # optimised variation + last_changed = bpy.context.scene.changedScene + + export_main_scene_name = getattr(addon_prefs,"export_main_scene_name") + export_on_library_changes = getattr(addon_prefs,"export_on_library_changes") + export_library_scene_name = getattr(addon_prefs,"export_library_scene_name") + + # export the main game world + game_scene = bpy.data.scenes[export_main_scene_name] + + # most recent change was in the main scene (game world/ level) + if last_changed == export_main_scene_name: + print("game world changed, exporting game gltf only") + export_main(game_scene, folder_path, addon_prefs) + + # if the library has changed, so will likely the game world that uses the library assets + if last_changed == export_library_scene_name and export_library_scene_name != "" and export_on_library_changes: + print("library changed") + export_main(game_scene, folder_path, addon_prefs) + + +###################################################### +## ui logic & co + +AutoExportGltfPreferenceNames = [ + 'auto_export', + 'export_main_scene_name', + 'export_main_output_name', + 'export_on_library_changes', + 'export_library_scene_name', + 'export_blueprints', + 'export_blueprints_path' +] + +class AutoExportGltfAddonPreferences(AddonPreferences): + # this must match the add-on name, use '__package__' + # when defining this in a submodule of a python package. + bl_idname = __name__ + + auto_export: BoolProperty( + name='Auto export', + description='Automatically export to gltf on save', + default=True + ) + export_main_scene_name: StringProperty( + name='Main scene', + description='The name of the main scene/level/world to auto export', + default='Scene' + ) + export_main_output_name: StringProperty( + name='Glb output name', + description='The glb output name for the main scene to auto export', + default='world' + ) + export_on_library_changes: BoolProperty( + name='Export on library changes', + description='Export main scene on library changes', + default=False + ) + export_library_scene_name: StringProperty( + name='Library scene', + description='The name of the library scene to auto export', + default='Library' + ) + # blueprint settings + export_blueprints: BoolProperty( + name='Export Blueprints', + description='Replaces collection instances with an Empty with a BlueprintName custom property', + default=False + ) + export_blueprints_path: StringProperty( + name='Blueprints path', + description='path to export the blueprints to (relative to this Blend file)', + default='' + ) + + + ##### + export_format: EnumProperty( + name='Format', + items=(('GLB', 'glTF Binary (.glb)', + 'Exports a single file, with all data packed in binary form. ' + 'Most efficient and portable, but more difficult to edit later'), + ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)', + 'Exports a single file, with all data packed in JSON. ' + 'Less efficient than binary, but easier to edit later'), + ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)', + 'Exports multiple files, with separate JSON, binary and texture data. ' + 'Easiest to edit later')), + description=( + 'Output format and embedding options. Binary is most efficient, ' + 'but JSON (embedded or separate) may be easier to edit later' + ), + default='GLB' + ) + export_copyright: StringProperty( + name='Copyright', + description='Legal rights and conditions for the model', + default='' + ) + + export_image_format: EnumProperty( + name='Images', + items=(('AUTO', 'Automatic', + 'Save PNGs as PNGs and JPEGs as JPEGs. ' + 'If neither one, use PNG'), + ('JPEG', 'JPEG Format (.jpg)', + 'Save images as JPEGs. (Images that need alpha are saved as PNGs though.) ' + 'Be aware of a possible loss in quality'), + ('NONE', 'None', + 'Don\'t export images'), + ), + description=( + 'Output format for images. PNG is lossless and generally preferred, but JPEG might be preferable for web ' + 'applications due to the smaller file size. Alternatively they can be omitted if they are not needed' + ), + default='AUTO' + ) + + export_texture_dir: StringProperty( + name='Textures', + description='Folder to place texture files in. Relative to the .gltf file', + default='', + ) + + """ + export_jpeg_quality: IntProperty( + name='JPEG quality', + description='Quality of JPEG export', + default=75, + min=0, + max=100 + ) + """ + + export_keep_originals: BoolProperty( + name='Keep original', + description=('Keep original textures files if possible. ' + 'WARNING: if you use more than one texture, ' + 'where pbr standard requires only one, only one texture will be used. ' + 'This can lead to unexpected results' + ), + default=False, + ) + + export_texcoords: BoolProperty( + name='UVs', + description='Export UVs (texture coordinates) with meshes', + default=True + ) + + export_normals: BoolProperty( + name='Normals', + description='Export vertex normals with meshes', + default=True + ) + + export_draco_mesh_compression_enable: BoolProperty( + name='Draco mesh compression', + description='Compress mesh using Draco', + default=False + ) + + export_draco_mesh_compression_level: IntProperty( + name='Compression level', + description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)', + default=6, + min=0, + max=10 + ) + + export_draco_position_quantization: IntProperty( + name='Position quantization bits', + description='Quantization bits for position values (0 = no quantization)', + default=14, + min=0, + max=30 + ) + + export_draco_normal_quantization: IntProperty( + name='Normal quantization bits', + description='Quantization bits for normal values (0 = no quantization)', + default=10, + min=0, + max=30 + ) + + export_draco_texcoord_quantization: IntProperty( + name='Texcoord quantization bits', + description='Quantization bits for texture coordinate values (0 = no quantization)', + default=12, + min=0, + max=30 + ) + + export_draco_color_quantization: IntProperty( + name='Color quantization bits', + description='Quantization bits for color values (0 = no quantization)', + default=10, + min=0, + max=30 + ) + + export_draco_generic_quantization: IntProperty( + name='Generic quantization bits', + description='Quantization bits for generic coordinate values like weights or joints (0 = no quantization)', + default=12, + min=0, + max=30 + ) + + export_tangents: BoolProperty( + name='Tangents', + description='Export vertex tangents with meshes', + default=False + ) + + export_materials: EnumProperty( + name='Materials', + items=(('EXPORT', 'Export', + 'Export all materials used by included objects'), + ('PLACEHOLDER', 'Placeholder', + 'Do not export materials, but write multiple primitive groups per mesh, keeping material slot information'), + ('NONE', 'No export', + 'Do not export materials, and combine mesh primitive groups, losing material slot information')), + description='Export materials', + default='EXPORT' + ) + + export_original_specular: BoolProperty( + name='Export original PBR Specular', + description=( + 'Export original glTF PBR Specular, instead of Blender Principled Shader Specular' + ), + default=False, + ) + + export_colors: BoolProperty( + name='Vertex Colors', + description='Export vertex colors with meshes', + default=True + ) + + export_attributes: BoolProperty( + name='Attributes', + description='Export Attributes (when starting with underscore)', + default=False + ) + + use_mesh_edges: BoolProperty( + name='Loose Edges', + description=( + 'Export loose edges as lines, using the material from the first material slot' + ), + default=False, + ) + + use_mesh_vertices: BoolProperty( + name='Loose Points', + description=( + 'Export loose points as glTF points, using the material from the first material slot' + ), + default=False, + ) + + export_cameras: BoolProperty( + name='Cameras', + description='Export cameras', + default=True + ) + + use_selection: BoolProperty( + name='Selected Objects', + description='Export selected objects only', + default=False + ) + + use_visible: BoolProperty( + name='Visible Objects', + description='Export visible objects only', + default=True + ) + + use_renderable: BoolProperty( + name='Renderable Objects', + description='Export renderable objects only', + default=False + ) + + + export_apply: BoolProperty( + name='Export Apply Modifiers', + description='Apply modifiers (excluding Armatures) to mesh objects -' + 'WARNING: prevents exporting shape keys', + default=True + ) + + export_yup: BoolProperty( + name='+Y Up', + description='Export using glTF convention, +Y up', + default=True + ) + + use_visible: BoolProperty( + name='Visible Objects', + description='Export visible objects only', + default=False + ) + + use_renderable: BoolProperty( + name='Renderable Objects', + description='Export renderable objects only', + default=False + ) + + export_extras: BoolProperty( + name='Custom Properties', + description='Export custom properties as glTF extras', + default=True + ) + + export_animations: BoolProperty( + name='Animations', + description='Exports active actions and NLA tracks as glTF animations', + default=False + ) + +class AutoExportGLTF(Operator, ExportHelper): + """test""" + bl_idname = "export_scenes.auto_gltf" + bl_label = "Apply settings" + bl_options = {'PRESET', 'UNDO'} + + # ExportHelper mixin class uses this + filename_ext = '' + + filter_glob: StringProperty( + default='*.glb;*.gltf', + options={'HIDDEN'} + ) + """ + auto_export: BoolProperty( + name='Auto export', + description='Automatically export to gltf on save', + default=True + ) + export_main_scene_name: StringProperty( + name='Main scene', + description='The name of the main scene/level/world to auto export', + default='Scene' + ) + export_main_output_name: StringProperty( + name='Glb output name', + description='The glb output name for the main scene to auto export', + default='world' + ) + export_on_library_changes: BoolProperty( + name='Export on library changes', + description='Export main scene on library changes', + default=False + ) + export_library_scene_name: StringProperty( + name='Library scene', + description='The name of the library scene to auto export', + default='Library' + ) + # blueprint settings + export_blueprints: BoolProperty( + name='Export Blueprints', + description='Replaces collection instances with an Empty with a BlueprintName custom property', + default=False + ) + export_blueprints_path: StringProperty( + name='Blueprints path', + description='path to export the blueprints to (relative to this Blend file)', + default='' + )""" + + def draw(self, context): + pass + + def execute(self, context): + preferences = context.preferences + return {'FINISHED'} + +class GLTF_PT_auto_export_main(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "" + bl_parent_id = "FILE_PT_operator" + bl_options = {'HIDE_HEADER'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + + +class GLTF_PT_auto_export_root(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Auto export" + bl_parent_id = "GLTF_PT_auto_export_main" + #bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" + + def draw_header(self, context): + sfile = context.space_data + operator = sfile.active_operator + self.layout.prop(operator, "auto_export", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + #operator = sfile.active_operator + operator = bpy.context.preferences.addons[__name__].preferences + + layout.active = operator.auto_export + layout.prop(operator, "export_main_scene_name") + layout.prop(operator, "export_library_scene_name") + + layout.prop(operator, "export_main_output_name") + layout.prop(operator, "export_on_library_changes") + +class GLTF_PT_auto_export_blueprints(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Blueprints" + bl_parent_id = "GLTF_PT_auto_export_root" + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" #"EXPORT_SCENE_OT_gltf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + addon_prefs = bpy.context.preferences.addons[__name__].preferences + + layout.prop(addon_prefs, "export_blueprints") + layout.prop(addon_prefs, "export_blueprints_path") + +class GLTF_PT_auto_export_gltf(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Gltf" + bl_parent_id = "GLTF_PT_auto_export_main" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENES_OT_auto_gltf" #"EXPORT_SCENE_OT_gltf" + + def draw(self, context): + preferences = context.preferences + addon_prefs = preferences.addons[__name__].preferences + layout = self.layout + + #preferences = context.preferences + #print("ADDON PREFERENCES ", list(preferences.addons.keys())) + #print("standard blender gltf prefs", list(preferences.addons["io_scene_gltf2"].preferences.keys())) + # we get the addon preferences from the standard gltf exporter & use those : + addon_prefs_gltf = preferences.addons["io_scene_gltf2"].preferences + + #addon_prefs = preferences.addons[__name__].preferences + + # print("KEYS", dir(addon_prefs)) + #print("BLAS", addon_prefs.__annotations__) + #print(addon_prefs.__dict__) + for key in addon_prefs.__annotations__.keys(): + if key not in AutoExportGltfPreferenceNames: + layout.prop(addon_prefs, key) + #for key in addon_prefs_gltf.__annotations__.keys(): + # layout.prop(addon_prefs_gltf, key) + +def menu_func_import(self, context): + self.layout.operator(AutoExportGLTF.bl_idname, text="glTF auto Export (.glb/gltf)") + +classes = [ + AutoExportGLTF, + AutoExportGltfAddonPreferences, + GLTF_PT_auto_export_main, + GLTF_PT_auto_export_root, + GLTF_PT_auto_export_blueprints, + GLTF_PT_auto_export_gltf +] + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + # setup handlers for updates & saving + bpy.app.handlers.depsgraph_update_post.append(deps_update_handler) + bpy.app.handlers.save_post.append(save_handler) + bpy.types.Scene.changedScene = bpy.props.StringProperty(get=get_changedScene, set=set_changedScene) + #bpy.types.Scene.changedObjects = bpy.props.CollectionProperty(get=get_changedObjects, set=set_changedObjects) + + # add our addon to the toolbar + bpy.types.TOPBAR_MT_file_export.append(menu_func_import) + + + +def unregister(): + for cls in classes: + bpy.utils.unregister_class(cls) + + bpy.types.TOPBAR_MT_file_export.remove(menu_func_import) + + # remove handlers & co + bpy.app.handlers.depsgraph_update_post.remove(deps_update_handler) + bpy.app.handlers.save_post.remove(save_handler) + del bpy.types.Scene.changedScene + #del bpy.types.Scene.changedObjects + +if __name__ == "__main__": + register() \ No newline at end of file diff --git a/tools/blender_auto_export/blender_auto_export_gltf.py b/tools/gltf_auto_export/old.py similarity index 59% rename from tools/blender_auto_export/blender_auto_export_gltf.py rename to tools/gltf_auto_export/old.py index 862f084..1a67c9a 100644 --- a/tools/blender_auto_export/blender_auto_export_gltf.py +++ b/tools/gltf_auto_export/old.py @@ -1,5 +1,5 @@ bl_info = { - "name": "blender_auto_export_gltf", + "name": "gltf_auto_export_gltf", "author": "kaosigh", "version": (0, 1), "blender": (3, 4, 0), @@ -24,7 +24,6 @@ from bpy.props import (BoolProperty, ) #see here for original gltf exporter infos https://github.com/KhronosGroup/glTF-Blender-IO/blob/main/addons/io_scene_gltf2/__init__.py - @persistent def deps_update_handler(scene): print("-------------") @@ -109,7 +108,6 @@ def export_library_split(scene, folder_path, gltf_export_preferences): # reset current scene from backup bpy.context.window.scene = old_current_scene - def debug_test(scene): root_collection = scene.collection collections_lookup = get_collection_hierarchy(root_collection, 1) @@ -238,18 +236,215 @@ def export_library_merged(scene, folder_path, gltf_export_preferences): # reset current scene from backup bpy.context.window.scene = old_current_scene -def export_main(scene, folder_path, gltf_export_preferences, output_name): + +# the active collection is a View Layer concept, so you actually have to find the active LayerCollection +# which must be done recursively +def find_layer_collection_recursive(find, col): + for c in col.children: + if c.collection == find: + return c + return None + + +# Makes an empty, at location, stores it in existing collection, from https://blender.stackexchange.com/questions/51290/how-to-add-empty-object-not-using-bpy-ops +def make_empty(name, location, coll_name): #string, vector, string of existing coll + empty_obj = bpy.data.objects.new( "empty", None, ) + empty_obj.name = name + empty_obj.empty_display_size = 1 + bpy.data.collections[coll_name].objects.link(empty_obj) + empty_obj.location = location + return empty_obj + + +def make_empty2(name, location, collection): + object_data = None #bpy.data.meshes.new("NewMesh") #None + empty_obj = bpy.data.objects.new( name, object_data ) + empty_obj.name = name + empty_obj.location = location + + + empty_obj.empty_display_size = 2 + empty_obj.empty_display_type = 'PLAIN_AXES' + collection.objects.link( empty_obj ) + return empty_obj + +def make_empty3(name, location, collection): + original_active_object = bpy.context.active_object + bpy.ops.object.empty_add(type='PLAIN_AXES', location=location) + empty_obj = bpy.context.active_object + empty_obj.name = name + collection.objects.link( empty_obj ) + bpy.context.view_layer.objects.active = original_active_object + return empty_obj + +# generate a copy of a scene that replaces collection instances with empties +# FIXME: will not preserve original names +# alternative: copy original names before creating a new scene, & reset them +# or create empties, hide original ones, and do the same renaming trick +def generate_hollow_scene(scene): + root_collection = scene.collection + temp_scene = bpy.data.scenes.new(name="temp_scene") + copy_root_collection = temp_scene.collection + scene_objects = [o for o in root_collection.objects] + + + found = find_layer_collection_recursive(copy_root_collection, bpy.context.view_layer.layer_collection) + if found: + print("FOUND COLLECTION") + # once it's found, set the active layer collection to the one we found + bpy.context.view_layer.active_layer_collection = found + + #original_names = {} + original_names = [] + for object in scene_objects: + if object.instance_type == 'COLLECTION': + collection_name = object.instance_collection.name + + #original_names[object.name] = object.name# + "____bak" + #print("custom properties", object, object.keys(), object.items()) + #for k, e in object.items(): + # print("custom properties ", k, e) + print("object location", object.location) + original_name = object.name + original_names.append(original_name) + + object.name = original_name + "____bak" + empty_obj = make_empty3(original_name, object.location, copy_root_collection) + """we inject the collection/blueprint name, as a component called 'BlueprintName', but we only do this in the empty, not the original object""" + empty_obj['BlueprintName'] = '"'+collection_name+'"' + empty_obj['SpawnHere'] = '' + + for k, v in object.items(): + empty_obj[k] = v + else: + copy_root_collection.objects.link(object) + + # bpy.data.scenes.remove(temp_scene) + # objs = bpy.data.objects + #objs.remove(objs["Cube"], do_unlink=True) + return (temp_scene, original_names) + +def clear_hollow_scene(temp_scene, original_scene, original_names): + # reset original names + root_collection = original_scene.collection + scene_objects = [o for o in root_collection.objects] + + for object in scene_objects: + if object.instance_type == 'COLLECTION': + print("object name to reset", object.name) + if object.name.endswith("____bak"): + print("reseting") + object.name = object.name.replace("____bak", "") + + # remove empties (only needed when we go via ops ????) + root_collection = temp_scene.collection + scene_objects = [o for o in root_collection.objects] + for object in scene_objects: + if object.type == 'EMPTY': + bpy.data.objects.remove(object, do_unlink=True) + + bpy.data.scenes.remove(temp_scene) + +#Recursivly transverse layer_collection for a particular name +def recurLayerCollection(layerColl, collName): + found = None + if (layerColl.name == collName): + return layerColl + for layer in layerColl.children: + found = recurLayerCollection(layer, collName) + if found: + return found + +def get_used_collections(scene): + root_collection = scene.collection + + scene_objects = [o for o in root_collection.objects] + collection_names = set() + used_collections = [] + for object in scene_objects: + print("object ", object) + if object.instance_type == 'COLLECTION': + print("THIS OBJECT IS A COLLECTION") + # print("instance_type" ,object.instance_type) + collection_name = object.instance_collection.name + print("instance collection", object.instance_collection.name) + #object.instance_collection.users_scene + # del object['blueprint'] + # object['BlueprintName'] = '"'+collection_name+'"' + if not collection_name in collection_names: + collection_names.add(collection_name) + used_collections.append(object.instance_collection) + + print("scene objects", scene_objects) + return (collection_names, used_collections) + +def export_used_collections(scene, folder_path, gltf_export_preferences): + (collection_names, used_collections) = get_used_collections(scene) + print("used collection names", collection_names, used_collections) + + # set active scene to be the library scene (hack for now) + bpy.context.window.scene = bpy.data.scenes["library"] + # save current active collection + active_collection = bpy.context.view_layer.active_layer_collection + + for collection_name in list(collection_names): + print("exporting collection", collection_name) + + layer_collection = bpy.context.view_layer.layer_collection + layerColl = recurLayerCollection(layer_collection, collection_name) + # set active collection to the collection + bpy.context.view_layer.active_layer_collection = layerColl + + print("layercoll", layerColl) + gltf_output_path = os.path.join(folder_path, collection_name) + + export_settings = { **gltf_export_preferences, 'use_active_scene': True, 'use_active_collection': True} #'use_visible': False, + export_gltf(gltf_output_path, export_settings) + + # reset active collection to the one we save before + bpy.context.view_layer.active_layer_collection = active_collection + +def export_main(scene, folder_path, gltf_export_preferences, output_name, addon_prefs): print("exporting to", folder_path, output_name) + export_blueprints = getattr(addon_prefs,"export_blueprints") + export_blueprints_path = os.path.join(folder_path, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path + # backup current active scene old_current_scene = bpy.context.scene - # set active scene to be the given scene - bpy.context.window.scene = scene + + if export_blueprints : + print("-----EXPORTING BLUEPRINTS----") + print("LIBRARY EXPORT", export_blueprints_path ) + + try: + #gltf_output_path = os.path.join(folder_path, "library") + #export_gltf(gltf_output_path, export_settings) + export_used_collections(scene, export_blueprints_path, gltf_export_preferences) + except Exception: + print("failed to export collections to gltf") + + (hollow_scene, object_names) = generate_hollow_scene(scene) + #except Exception: + # print("failed to create hollow scene") + + # set active scene to be the given scene + bpy.context.window.scene = hollow_scene gltf_output_path = os.path.join(folder_path, output_name) - export_settings = { **gltf_export_preferences, 'use_active_scene': True} + export_settings = { **gltf_export_preferences, + 'use_active_scene': True, + 'use_active_collection':True, + 'use_active_collection_with_nested':True, + 'use_visible': False, + 'use_renderable': False, + 'export_apply':True + } export_gltf(gltf_output_path, export_settings) + if export_blueprints : + clear_hollow_scene(hollow_scene, scene, object_names) + # reset current scene from backup bpy.context.window.scene = old_current_scene @@ -320,11 +515,11 @@ def auto_export(): ) for key in addon_prefs.__annotations__.keys(): - if key is not "export_on_library_changes" and key is not "export_main_scene_name" and key is not "export_main_output_name" and key is not "export_library_scene_name": #FIXME: ugh, cleanup + if key != "export_on_library_changes" and key != "export_main_scene_name" and key != "export_main_output_name" and key != "export_library_scene_name" and key != "export_blueprints" and key != "export_blueprints_path": #FIXME: ugh, cleanup gltf_export_preferences[key] = getattr(addon_prefs,key) print("overriding setting", key, "value", getattr(addon_prefs,key)) - # testing (we want an in-memory scene, not one that is visible in the ui) + # (we want an in-memory scene, not one that is visible in the ui) #invisible_scene = bpy.types.Scene("foo") @@ -338,10 +533,9 @@ def auto_export(): export_main_scene_name = getattr(addon_prefs,"export_main_scene_name") export_main_output_name = getattr(addon_prefs,"export_main_output_name") export_on_library_changes = getattr(addon_prefs,"export_on_library_changes") - export_library_scene_name = getattr(addon_prefs,"export_library_scene_name") - print("exporting ??", export_on_library_changes, export_main_scene_name, export_main_output_name) + # print("ADD ON PARAMS FOR EXPORT ??", export_on_library_changes, export_main_scene_name, export_main_output_name, export_blueprints) print("last changed", bpy.context.scene.changedScene) # optimised variation last_changed = bpy.context.scene.changedScene #get_changedScene() @@ -350,8 +544,8 @@ def auto_export(): game_scene = bpy.data.scenes[export_main_scene_name] print("game world changed, exporting game gltf only") - export_main(game_scene, folder_path, gltf_export_preferences, export_main_output_name) - if last_changed == export_library_scene_name and export_library_scene_name is not "" : # if the library has changed, so will likely the game world that uses the library assets + export_main(game_scene, folder_path, gltf_export_preferences, export_main_output_name, addon_prefs) + if last_changed == export_library_scene_name and export_library_scene_name != "" : # if the library has changed, so will likely the game world that uses the library assets print("library changed, exporting both game & library gltf") library_scene = bpy.data.scenes[export_library_scene_name] @@ -360,7 +554,7 @@ def auto_export(): # export the main game world if export_on_library_changes: game_scene = bpy.data.scenes[export_main_scene_name] - export_main(game_scene, folder_path, gltf_export_preferences, export_main_output_name) + export_main(game_scene, folder_path, gltf_export_preferences, export_main_output_name, addon_prefs) return {'FINISHED'} @@ -384,7 +578,14 @@ class AutoExportGltfAddonPreferences(AddonPreferences): # this must match the add-on name, use '__package__' # when defining this in a submodule of a python package. bl_idname = __name__ - + ui_tab: EnumProperty( + items=(('GENERAL', "General", "General settings"), + ('MESHES', "Meshes", "Mesh settings"), + ('OBJECTS', "Objects", "Object settings"), + ('ANIMATION', "Animation", "Animation settings")), + name="ui_tab", + description="Export setting categories", + ) export_format: EnumProperty( name='Format', items=(('GLB', 'glTF Binary (.glb)', @@ -425,6 +626,19 @@ class AutoExportGltfAddonPreferences(AddonPreferences): default='' ) + # blueprint settings + export_blueprints: BoolProperty( + name='Export Blueprints', + description='Replaces collection instances with an Empty with a BlueprintName custom property', + default=False + ) + + export_blueprints_path: StringProperty( + name='Export Blueprints path', + description='path to export the blueprints to', + default='' + ) + ##### export_copyright: StringProperty( @@ -663,7 +877,7 @@ class AutoExportGltfAddonPreferences(AddonPreferences): -class TEST_AUTO_OT_gltf(Operator, ExportHelper): +class AutoExportGLTF2(Operator, ExportHelper): """test""" bl_idname = "export_scenes.auto_gltf" bl_label = "Apply settings" @@ -681,6 +895,7 @@ class TEST_AUTO_OT_gltf(Operator, ExportHelper): # to the class instance from the operator setting before calling. def draw(self, context): + pass layout = self.layout preferences = context.preferences addon_prefs = preferences.addons[__name__].preferences @@ -716,9 +931,184 @@ class TEST_AUTO_OT_gltf(Operator, ExportHelper): # Only needed if you want to add into a dynamic menu def menu_func_import(self, context): - self.layout.operator(TEST_AUTO_OT_gltf.bl_idname, text="glTF auto Export (.glb/gltf)") + self.layout.operator(AutoExportGLTF2.bl_idname, text="glTF auto Export (.glb/gltf)") -classes = [TEST_AUTO_OT_gltf, AutoExportGltfAddonPreferences] + +class GLTF_PT_export_main(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "" + bl_parent_id = "FILE_PT_operator" + bl_options = {'HIDE_HEADER'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENE_OT_gltf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + + layout.prop(operator, 'export_format') + if operator.export_format == 'GLTF_SEPARATE': + layout.prop(operator, 'export_keep_originals') + if operator.export_keep_originals is False: + layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER') + + layout.prop(operator, 'export_copyright') + layout.prop(operator, 'will_save_settings') + + +class GLTF_PT_export_transform(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Transform" + bl_parent_id = "FILE_PT_operator" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENE_OT_gltf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + + layout.prop(operator, 'export_yup') + +class GLTF_PT_export_include(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Include" + bl_parent_id = "FILE_PT_operator" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENE_OT_gltf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + + col = layout.column(heading = "Limit to", align = True) + col.prop(operator, 'use_selection') + col.prop(operator, 'use_visible') + col.prop(operator, 'use_renderable') + col.prop(operator, 'use_active_collection') + if operator.use_active_collection: + col.prop(operator, 'use_active_collection_with_nested') + col.prop(operator, 'use_active_scene') + + col = layout.column(heading = "Data", align = True) + col.prop(operator, 'export_extras') + col.prop(operator, 'export_cameras') + col.prop(operator, 'export_lights') + +class GLTF_PT_export_animation(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Animation" + bl_parent_id = "FILE_PT_operator" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENE_OT_gltf" + + def draw_header(self, context): + sfile = context.space_data + operator = sfile.active_operator + self.layout.prop(operator, "export_animations", text="") + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + + layout.active = operator.export_animations + + layout.prop(operator, 'export_animation_mode') + if operator.export_animation_mode == "ACTIVE_ACTIONS": + layout.prop(operator, 'export_nla_strips_merged_animation_name') + + row = layout.row() + row.active = operator.export_force_sampling and operator.export_animation_mode in ['ACTIONS', 'ACTIVE_ACTIONS'] + row.prop(operator, 'export_bake_animation') + if operator.export_animation_mode == "SCENE": + layout.prop(operator, 'export_anim_scene_split_object') + +class GLTF_PT_export_animation_notes(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Notes" + bl_parent_id = "GLTF_PT_export_animation" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + + return operator.bl_idname == "EXPORT_SCENE_OT_gltf" and \ + operator.export_animation_mode in ["NLA_TRACKS", "SCENE"] + + def draw(self, context): + operator = context.space_data.active_operator + layout = self.layout + if operator.export_animation_mode == "SCENE": + layout.label(text="Scene mode uses full bake mode:") + layout.label(text="- sampling is active") + layout.label(text="- baking all objects is active") + layout.label(text="- Using scene frame range") + elif operator.export_animation_mode == "NLA_TRACKS": + layout.label(text="Track mode uses full bake mode:") + layout.label(text="- sampling is active") + layout.label(text="- baking all objects is active") + +classes = [ + AutoExportGLTF2, + AutoExportGltfAddonPreferences, + + # + # GLTF_PT_export_main, + # GLTF_PT_export_include, + # GLTF_PT_export_transform, + + # GLTF_PT_export_animation, + # GLTF_PT_export_animation_notes, + + + #panel1 + +] def register(): for cls in classes: @@ -732,7 +1122,6 @@ def register(): bpy.app.handlers.depsgraph_update_post.append(deps_update_handler) bpy.app.handlers.save_post.append(save_handler) - #bpy.types.TOPBAR_MT_file_export.append(menu_func_import) bpy.types.Scene.changedScene = bpy.props.StringProperty(get=get_changedScene, set=set_ChangedScene)