diff --git a/Cargo.lock b/Cargo.lock index f8c513f..b2244a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,7 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "getrandom 0.3.4", "once_cell", "version_check", @@ -138,7 +138,7 @@ checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", "bitflags 2.11.0", - "cfg-if", + "cfg-if 1.0.4", "libc", ] @@ -399,7 +399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ "autocfg", - "cfg-if", + "cfg-if 1.0.4", "concurrent-queue", "futures-io", "futures-lite", @@ -607,7 +607,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "cfg-if", + "cfg-if 1.0.4", "console_error_panic_hook", "ctrlc", "downcast-rs 2.0.2", @@ -1349,7 +1349,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "spin", + "spin 0.10.0", "wasm-bindgen", "wasm-bindgen-futures", "web-time", @@ -1823,7 +1823,7 @@ dependencies = [ "bevy_tasks", "bevy_window", "bytemuck", - "cfg-if", + "cfg-if 1.0.4", "js-sys", "tracing", "wasm-bindgen", @@ -1832,6 +1832,29 @@ dependencies = [ "winit", ] +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", + "which", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -1905,7 +1928,7 @@ dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if", + "cfg-if 1.0.4", "constant_time_eq", "cpufeatures", ] @@ -2078,6 +2101,12 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.4" @@ -2128,6 +2157,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cocoa" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.7.0", + "core-graphics 0.19.2", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" +dependencies = [ + "bitflags 2.11.0", + "block", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "objc", +] + [[package]] name = "codespan-reporting" version = "0.12.0" @@ -2177,7 +2234,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "wasm-bindgen", ] @@ -2226,13 +2283,23 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", + "libc", +] + [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] @@ -2242,16 +2309,34 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.7.0", + "foreign-types 0.3.2", + "libc", +] + [[package]] name = "core-graphics" version = "0.23.2" @@ -2261,7 +2346,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -2287,6 +2372,31 @@ dependencies = [ "libc", ] +[[package]] +name = "core-media-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273bf3fc5bf51fd06a7766a84788c1540b6527130a0bce39e00567d6ab9f31f1" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if 0.1.10", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "metal 0.18.0", + "objc", +] + [[package]] name = "core2" version = "0.4.0" @@ -2312,7 +2422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" dependencies = [ "bitflags 1.3.2", - "core-foundation-sys", + "core-foundation-sys 0.8.7", "coreaudio-sys", ] @@ -2322,7 +2432,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" dependencies = [ - "bindgen", + "bindgen 0.72.1", ] [[package]] @@ -2333,7 +2443,7 @@ checksum = "964eb3e10ea8b0d29c797086aab3ca730f75e06dced0cb980642fd274a5cca30" dependencies = [ "block", "core-foundation 0.9.4", - "core-foundation-sys", + "core-foundation-sys 0.8.7", "coremidi-sys", ] @@ -2343,7 +2453,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9504310988d938e49fff1b5f1e56e3dafe39bb1bae580c19660b58b83a191e" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.7", ] [[package]] @@ -2353,7 +2463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" dependencies = [ "alsa", - "core-foundation-sys", + "core-foundation-sys 0.8.7", "coreaudio-rs", "dasp_sample", "jni", @@ -2384,7 +2494,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", ] [[package]] @@ -2585,6 +2695,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -2795,6 +2911,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2837,6 +2965,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2844,7 +2981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -2858,6 +2995,12 @@ dependencies = [ "syn", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -2918,6 +3061,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + [[package]] name = "futures-task" version = "0.3.32" @@ -2957,13 +3106,26 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if 1.0.4", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "js-sys", "libc", "r-efi 5.3.0", @@ -2977,7 +3139,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "libc", "r-efi 6.0.0", "wasip2", @@ -3195,7 +3357,7 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "crunchy", "num-traits", "serde", @@ -3286,6 +3448,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "icu_locale_core" version = "2.1.1" @@ -3449,7 +3620,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if", + "cfg-if 1.0.4", "combine", "jni-sys", "log", @@ -3576,6 +3747,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -3687,7 +3864,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "windows-link", ] @@ -3890,7 +4067,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "rayon", ] @@ -3918,6 +4095,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e198a0ee42bdbe9ef2c09d0b9426f3b2b47d90d93a4a9b0395c4cea605e92dc0" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa", + "core-graphics 0.19.2", + "foreign-types 0.3.2", + "log", + "objc", +] + [[package]] name = "metal" version = "0.33.0" @@ -3927,7 +4119,7 @@ dependencies = [ "bitflags 2.11.0", "block", "core-graphics-types 0.2.0", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", @@ -3988,6 +4180,31 @@ dependencies = [ "pxfm", ] +[[package]] +name = "mozjpeg" +version = "0.10.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7891b80aaa86097d38d276eb98b3805d6280708c4e0a1e6f6aed9380c51fec9" +dependencies = [ + "arrayvec", + "bytemuck", + "libc", + "mozjpeg-sys", + "rgb", +] + +[[package]] +name = "mozjpeg-sys" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0dc668bf9bf888c88e2fb1ab16a406d2c380f1d082b20d51dd540ab2aa70c1" +dependencies = [ + "cc", + "dunce", + "libc", + "nasm-rs", +] + [[package]] name = "naga" version = "28.0.0" @@ -3997,7 +4214,7 @@ dependencies = [ "arrayvec", "bit-set", "bitflags 2.11.0", - "cfg-if", + "cfg-if 1.0.4", "cfg_aliases", "codespan-reporting", "half", @@ -4033,6 +4250,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "nannou_webcam" +version = "0.1.0" +source = "git+https://github.com/nannou-org/nannou?branch=bevy-refactor#da69830a7fd1b40045b14aa18b28d5d51e02ee49" +dependencies = [ + "bevy", + "console_error_panic_hook", + "flume", + "getrandom 0.3.4", + "nokhwa", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "nasm-rs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" +dependencies = [ + "jobserver", + "log", +] + [[package]] name = "ndk" version = "0.8.0" @@ -4099,7 +4349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.11.0", - "cfg-if", + "cfg-if 1.0.4", "cfg_aliases", "libc", ] @@ -4111,11 +4361,77 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags 2.11.0", - "cfg-if", + "cfg-if 1.0.4", "cfg_aliases", "libc", ] +[[package]] +name = "nokhwa" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cae50786bfa1214ed441f98addbea51ca1b9aaa9e4bf5369cda36654b3efaa" +dependencies = [ + "flume", + "image", + "nokhwa-bindings-linux", + "nokhwa-bindings-macos", + "nokhwa-bindings-windows", + "nokhwa-core", + "paste", + "thiserror 2.0.18", +] + +[[package]] +name = "nokhwa-bindings-linux" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd666aaa41d14357817bd9a981773a73c4d00b34d344cfc244e47ebd397b1ec" +dependencies = [ + "nokhwa-core", + "v4l", +] + +[[package]] +name = "nokhwa-bindings-macos" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de78eb4a2d47a68f490899aa0516070d7a972f853ec2bb374ab53be0bd39b60f" +dependencies = [ + "block", + "cocoa-foundation", + "core-foundation 0.10.1", + "core-media-sys", + "core-video-sys", + "flume", + "nokhwa-core", + "objc", + "once_cell", +] + +[[package]] +name = "nokhwa-bindings-windows" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899799275c93ef69bbe8cb888cf6f8249abe751cbc50be5299105022aec14a1c" +dependencies = [ + "nokhwa-core", + "once_cell", + "windows 0.62.2", +] + +[[package]] +name = "nokhwa-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109975552bbd690894f613bce3d408222911e317197c72b2e8b9a1912dc261ae" +dependencies = [ + "bytes", + "image", + "mozjpeg", + "thiserror 2.0.18", +] + [[package]] name = "nom" version = "7.1.3" @@ -4285,6 +4601,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -4645,6 +4962,15 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "oboe" version = "0.6.1" @@ -4749,7 +5075,7 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "libc", "redox_syscall 0.5.18", "smallvec", @@ -4782,6 +5108,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -4888,7 +5220,7 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "concurrent-queue", "hermit-abi", "pin-project-lite", @@ -4975,8 +5307,25 @@ version = "0.1.0" dependencies = [ "bevy", "glfw", + "js-sys", + "processing_core", "processing_midi", "processing_render", + "processing_webcam", + "tracing", + "tracing-subscriber", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "processing_core" +version = "0.1.0" +dependencies = [ + "bevy", + "raw-window-handle", + "thiserror 2.0.18", + "tracing", ] [[package]] @@ -5004,6 +5353,7 @@ dependencies = [ "glfw", "png", "processing", + "processing_webcam", "pyo3", ] @@ -5020,11 +5370,8 @@ dependencies = [ "naga", "objc2 0.6.4", "objc2-app-kit 0.3.2", - "processing_midi", + "processing_core", "raw-window-handle", - "thiserror 2.0.18", - "tracing", - "tracing-subscriber", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -5045,6 +5392,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "processing_webcam" +version = "0.1.0" +dependencies = [ + "bevy", + "nannou_webcam", + "processing_core", + "processing_render", +] + [[package]] name = "profiling" version = "1.0.17" @@ -5257,7 +5614,7 @@ dependencies = [ "av1-grain", "bitstream-io", "built", - "cfg-if", + "cfg-if 1.0.4", "interpolate_name", "itertools 0.14.0", "libc", @@ -5403,6 +5760,9 @@ name = "rgb" version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] [[package]] name = "rodio" @@ -5699,6 +6059,15 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spin" version = "0.10.0" @@ -5908,7 +6277,7 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", ] [[package]] @@ -5934,7 +6303,7 @@ dependencies = [ "arrayref", "arrayvec", "bytemuck", - "cfg-if", + "cfg-if 1.0.4", "log", "tiny-skia-path", ] @@ -6098,7 +6467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76902d2a8d5f9f55a81155c08971734071968c90f2d9bfe645fe700579b2950" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.4", "tracing-core", "tracing-subscriber", ] @@ -6210,6 +6579,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v4l" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fbfea44a46799d62c55323f3c55d06df722fbe577851d848d328a1041c3403" +dependencies = [ + "bitflags 1.3.2", + "libc", + "v4l2-sys-mit", +] + +[[package]] +name = "v4l2-sys-mit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6779878362b9bacadc7893eac76abe69612e8837ef746573c4a5239daf11990b" +dependencies = [ + "bindgen 0.65.1", +] + [[package]] name = "v_frame" version = "0.3.9" @@ -6290,7 +6679,7 @@ version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -6303,7 +6692,7 @@ version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ - "cfg-if", + "cfg-if 1.0.4", "futures-util", "js-sys", "once_cell", @@ -6551,7 +6940,7 @@ dependencies = [ "arrayvec", "bitflags 2.11.0", "bytemuck", - "cfg-if", + "cfg-if 1.0.4", "cfg_aliases", "document-features", "hashbrown 0.16.1", @@ -6643,7 +7032,7 @@ dependencies = [ "bitflags 2.11.0", "block", "bytemuck", - "cfg-if", + "cfg-if 1.0.4", "cfg_aliases", "core-graphics-types 0.2.0", "glow", @@ -6656,7 +7045,7 @@ dependencies = [ "libc", "libloading", "log", - "metal", + "metal 0.33.0", "naga", "ndk-sys 0.6.0+11769913", "objc", @@ -6720,6 +7109,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "winapi" version = "0.3.9" @@ -7245,7 +7646,7 @@ dependencies = [ "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", - "core-graphics", + "core-graphics 0.23.2", "cursor-icon", "dpi", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index ff08ae4..5596ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ workspace = true default = ["wayland"] wayland = ["processing_render/wayland"] x11 = ["processing_render/x11"] +webcam = ["dep:processing_webcam"] [workspace] resolver = "3" @@ -26,14 +27,25 @@ bevy_naga_reflect = { git = "https://github.com/tychedelia/bevy_naga_reflect" } naga = { version = "28", features = ["wgsl-in"] } wesl = { version = "0.3", default-features = false } processing = { path = "." } +processing_core = { path = "crates/processing_core" } processing_pyo3 = { path = "crates/processing_pyo3" } processing_render = { path = "crates/processing_render" } processing_midi = { path = "crates/processing_midi" } +processing_webcam = { path = "crates/processing_webcam" } [dependencies] bevy = { workspace = true } +processing_core = { workspace = true } processing_render = { workspace = true } processing_midi = { workspace = true } +processing_webcam = { workspace = true, optional = true } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = { version = "0.3", features = ["Window"] } [dev-dependencies] glfw = "0.60.0" @@ -89,6 +101,11 @@ path = "examples/midi.rs" name = "gltf_load" path = "examples/gltf_load.rs" +[[example]] +name = "webcam" +path = "examples/webcam.rs" +required-features = ["webcam"] + [[example]] name = "stroke_2d" path = "examples/stroke_2d.rs" diff --git a/crates/processing_core/Cargo.toml b/crates/processing_core/Cargo.toml new file mode 100644 index 0000000..791a656 --- /dev/null +++ b/crates/processing_core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "processing_core" +version = "0.1.0" +edition = "2024" + +[lints] +workspace = true + +[dependencies] +bevy = { workspace = true } +raw-window-handle = "0.6" +thiserror = "2" +tracing = "0.1" diff --git a/crates/processing_render/src/config.rs b/crates/processing_core/src/config.rs similarity index 85% rename from crates/processing_render/src/config.rs rename to crates/processing_core/src/config.rs index 97aee72..a10edd0 100644 --- a/crates/processing_render/src/config.rs +++ b/crates/processing_core/src/config.rs @@ -1,7 +1,3 @@ -//! Options object for configuring various aspects of libprocessing. -//! -//! To add a new Config just add a new enum with associated value - use bevy::prelude::Resource; use std::collections::HashMap; diff --git a/crates/processing_render/src/error.rs b/crates/processing_core/src/error.rs similarity index 95% rename from crates/processing_render/src/error.rs rename to crates/processing_core/src/error.rs index 2e52a83..40dfbad 100644 --- a/crates/processing_render/src/error.rs +++ b/crates/processing_core/src/error.rs @@ -36,6 +36,8 @@ pub enum ProcessingError { UnknownMaterialProperty(String), #[error("GLTF load error: {0}")] GltfLoadError(String), + #[error("Webcam not connected")] + WebcamNotConnected, #[error("Shader compilation error: {0}")] ShaderCompilationError(String), #[error("Shader not found")] diff --git a/crates/processing_core/src/lib.rs b/crates/processing_core/src/lib.rs new file mode 100644 index 0000000..9de4d39 --- /dev/null +++ b/crates/processing_core/src/lib.rs @@ -0,0 +1,49 @@ +pub mod config; +pub mod error; + +use std::cell::RefCell; +use std::sync::OnceLock; + +use bevy::app::App; +use tracing::debug; + +static IS_INIT: OnceLock<()> = OnceLock::new(); + +thread_local! { + static APP: RefCell> = const { RefCell::new(None) }; +} + +pub fn app_mut(cb: impl FnOnce(&mut App) -> error::Result) -> error::Result { + let res = APP.with(|app_cell| { + let mut app_borrow = app_cell.borrow_mut(); + let app = app_borrow + .as_mut() + .ok_or(error::ProcessingError::AppAccess)?; + cb(app) + })?; + Ok(res) +} + +pub fn is_already_init() -> error::Result { + let is_init = IS_INIT.get().is_some(); + let thread_has_app = APP.with(|app_cell| app_cell.borrow().is_some()); + if is_init && !thread_has_app { + return Err(error::ProcessingError::AppAccess); + } + if is_init && thread_has_app { + debug!("App already initialized"); + return Ok(true); + } + Ok(false) +} + +pub fn set_app(app: App) { + APP.with(|app_cell| { + IS_INIT.get_or_init(|| ()); + *app_cell.borrow_mut() = Some(app); + }); +} + +pub fn take_app() -> Option { + APP.with(|app_cell| app_cell.borrow_mut().take()) +} diff --git a/crates/processing_pyo3/Cargo.toml b/crates/processing_pyo3/Cargo.toml index cb0c15e..26616ff 100644 --- a/crates/processing_pyo3/Cargo.toml +++ b/crates/processing_pyo3/Cargo.toml @@ -14,10 +14,12 @@ crate-type = ["cdylib"] default = ["wayland"] wayland = ["processing/wayland", "glfw/wayland"] x11 = ["processing/x11"] +webcam = ["processing/webcam", "dep:processing_webcam"] [dependencies] pyo3 = "0.27.0" processing = { workspace = true } +processing_webcam = { workspace = true, optional = true } bevy = { workspace = true, features = ["file_watcher"] } glfw = { version = "0.60.0"} png = "0.18" diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index e39f839..477d24f 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -59,7 +59,14 @@ impl Light { #[pyclass] #[derive(Debug)] pub struct Image { - entity: Entity, + pub(crate) entity: Entity, +} + +impl Image { + #[expect(dead_code)] // it's only used by webcam atm + pub(crate) fn from_entity(entity: Entity) -> Self { + Self { entity } + } } impl Drop for Image { diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index bdd9759..1bf1b0a 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -13,6 +13,8 @@ mod gltf; mod graphics; pub(crate) mod material; pub(crate) mod shader; +#[cfg(feature = "webcam")] +mod webcam; use graphics::{Geometry, Graphics, Image, Light, Topology, get_graphics, get_graphics_mut}; use material::Material; @@ -93,6 +95,12 @@ fn processing(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(emissive, m)?)?; m.add_function(wrap_pyfunction!(unlit, m)?)?; + #[cfg(feature = "webcam")] + { + m.add_class::()?; + m.add_function(wrap_pyfunction!(create_webcam, m)?)?; + } + Ok(()) } @@ -570,3 +578,14 @@ fn emissive(module: &Bound<'_, PyModule>, args: &Bound<'_, PyTuple>) -> PyResult fn unlit(module: &Bound<'_, PyModule>) -> PyResult<()> { graphics!(module).unlit() } + +#[cfg(feature = "webcam")] +#[pyfunction] +#[pyo3(signature = (width=None, height=None, framerate=None))] +fn create_webcam( + width: Option, + height: Option, + framerate: Option, +) -> PyResult { + webcam::Webcam::new(width, height, framerate) +} diff --git a/crates/processing_pyo3/src/webcam.rs b/crates/processing_pyo3/src/webcam.rs new file mode 100644 index 0000000..3cc06ba --- /dev/null +++ b/crates/processing_pyo3/src/webcam.rs @@ -0,0 +1,55 @@ +use bevy::prelude::Entity; +use processing_webcam::{ + WebcamFormat, webcam_create, webcam_create_with_format, webcam_destroy, webcam_image, + webcam_is_connected, webcam_resolution, +}; +use pyo3::{exceptions::PyRuntimeError, prelude::*}; + +use crate::graphics::Image; + +#[pyclass(unsendable)] +pub struct Webcam { + entity: Entity, +} + +#[pymethods] +impl Webcam { + #[new] + #[pyo3(signature = (width=None, height=None, framerate=None))] + pub fn new(width: Option, height: Option, framerate: Option) -> PyResult { + let entity = match (width, height, framerate) { + (Some(w), Some(h), Some(fps)) => webcam_create_with_format(WebcamFormat::Exact { + resolution: bevy::math::UVec2::new(w, h), + framerate: fps, + }), + (Some(w), Some(h), None) => { + webcam_create_with_format(WebcamFormat::Resolution(bevy::math::UVec2::new(w, h))) + } + (None, None, Some(fps)) => webcam_create_with_format(WebcamFormat::FrameRate(fps)), + _ => webcam_create(), + } + .map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + + Ok(Self { entity }) + } + + pub fn is_connected(&self) -> PyResult { + webcam_is_connected(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn resolution(&self) -> PyResult<(u32, u32)> { + webcam_resolution(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + + pub fn image(&self) -> PyResult { + let entity = + webcam_image(self.entity).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?; + Ok(Image::from_entity(entity)) + } +} + +impl Drop for Webcam { + fn drop(&mut self) { + let _ = webcam_destroy(self.entity); + } +} diff --git a/crates/processing_render/Cargo.toml b/crates/processing_render/Cargo.toml index afccd18..9f10c24 100644 --- a/crates/processing_render/Cargo.toml +++ b/crates/processing_render/Cargo.toml @@ -18,12 +18,9 @@ naga = { workspace = true } wesl = { workspace = true } lyon = "1.0" raw-window-handle = "0.6" -thiserror = "2" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } half = "2.7" crossbeam-channel = "0.5" -processing_midi = { workspace = true } +processing_core = { workspace = true } [build-dependencies] wesl = { workspace = true, features = ["package"] } diff --git a/crates/processing_render/src/geometry/attribute.rs b/crates/processing_render/src/geometry/attribute.rs index 3e26472..27395b5 100644 --- a/crates/processing_render/src/geometry/attribute.rs +++ b/crates/processing_render/src/geometry/attribute.rs @@ -7,7 +7,7 @@ use bevy::{ render::render_resource::VertexFormat, }; -use crate::error::{ProcessingError, Result}; +use processing_core::error::{ProcessingError, Result}; use super::{Geometry, hash_attr_name}; diff --git a/crates/processing_render/src/geometry/layout.rs b/crates/processing_render/src/geometry/layout.rs index 0d355f8..bf17555 100644 --- a/crates/processing_render/src/geometry/layout.rs +++ b/crates/processing_render/src/geometry/layout.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use super::BuiltinAttributes; -use crate::error::{ProcessingError, Result}; +use processing_core::error::{ProcessingError, Result}; // bevy requires an attribute id for each unique vertex attribute. we don't really want to // expose this to users, so we hash the attribute name to generate a unique id. in theory diff --git a/crates/processing_render/src/geometry/mod.rs b/crates/processing_render/src/geometry/mod.rs index a11fd1e..fc8d4b9 100644 --- a/crates/processing_render/src/geometry/mod.rs +++ b/crates/processing_render/src/geometry/mod.rs @@ -16,8 +16,8 @@ use bevy::{ render::render_resource::PrimitiveTopology, }; -use crate::error::{ProcessingError, Result}; use crate::render::primitive::{box_mesh, sphere_mesh}; +use processing_core::error::{ProcessingError, Result}; pub struct GeometryPlugin; diff --git a/crates/processing_render/src/gltf.rs b/crates/processing_render/src/gltf.rs index 47056cc..3eca339 100644 --- a/crates/processing_render/src/gltf.rs +++ b/crates/processing_render/src/gltf.rs @@ -13,11 +13,11 @@ use bevy::{ scene::SceneSpawner, }; -use crate::config::{Config, ConfigKey}; -use crate::error::{ProcessingError, Result}; use crate::geometry::{BuiltinAttributes, Geometry, layout::VertexLayout}; use crate::graphics; use crate::render::material::UntypedMaterial; +use processing_core::config::{Config, ConfigKey}; +use processing_core::error::{ProcessingError, Result}; #[derive(Component)] pub struct GltfNodeTransform(pub Transform); diff --git a/crates/processing_render/src/graphics.rs b/crates/processing_render/src/graphics.rs index a351a73..2dda6ee 100644 --- a/crates/processing_render/src/graphics.rs +++ b/crates/processing_render/src/graphics.rs @@ -27,7 +27,6 @@ use bevy::{ use crate::{ Flush, - error::{ProcessingError, Result}, image::{Image, create_readback_buffer, pixel_size, pixels_to_bytes}, render::{ RenderState, @@ -35,6 +34,7 @@ use crate::{ }, surface::Surface, }; +use processing_core::error::{ProcessingError, Result}; pub struct GraphicsPlugin; diff --git a/crates/processing_render/src/image.rs b/crates/processing_render/src/image.rs index b4f7c21..60f64c0 100644 --- a/crates/processing_render/src/image.rs +++ b/crates/processing_render/src/image.rs @@ -24,8 +24,8 @@ use bevy::{ }; use half::f16; -use crate::config::{Config, ConfigKey}; -use crate::error::{ProcessingError, Result}; +use processing_core::config::{Config, ConfigKey}; +use processing_core::error::{ProcessingError, Result}; pub struct ImagePlugin; @@ -112,7 +112,6 @@ pub fn is_loaded(world: &World, handle: &Handle) -> bool { ) } -#[cfg(target_arch = "wasm32")] pub fn from_handle( In(handle): In>, world: &mut World, @@ -365,15 +364,13 @@ pub fn prepare_update_region( pub fn destroy( In(entity): In, mut commands: Commands, - mut p_images: Query<&mut Image>, - mut images: ResMut>, + p_images: Query<&Image>, mut p_image_textures: ResMut, ) -> Result<()> { - let p_image = p_images - .get_mut(entity) + p_images + .get(entity) .map_err(|_| ProcessingError::ImageNotFound)?; - images.remove(&p_image.handle); p_image_textures.remove(&entity); commands.entity(entity).despawn(); Ok(()) diff --git a/crates/processing_render/src/lib.rs b/crates/processing_render/src/lib.rs index d9071b0..296e6b3 100644 --- a/crates/processing_render/src/lib.rs +++ b/crates/processing_render/src/lib.rs @@ -1,5 +1,3 @@ -pub mod config; -pub mod error; pub mod geometry; pub mod gltf; mod graphics; @@ -8,53 +6,82 @@ pub mod light; pub mod material; pub mod render; pub mod sketch; -mod surface; +pub(crate) mod surface; pub mod transform; -use std::{cell::RefCell, num::NonZero, path::PathBuf, sync::OnceLock}; +use std::path::PathBuf; -use config::*; - -#[cfg(not(target_arch = "wasm32"))] -use bevy::log::tracing_subscriber; -use bevy::render::RenderPlugin; use bevy::{ - app::{App, AppExit}, asset::{AssetEventSystems, io::AssetSourceBuilder}, prelude::*, render::render_resource::{Extent3d, TextureFormat}, }; -use render::material::{add_custom_materials, add_standard_materials}; -use render::{activate_cameras, clear_transient_meshes, flush_draw_commands}; -use tracing::debug; +use processing_core::app_mut; +use processing_core::config::*; +use processing_core::error; use crate::geometry::{AttributeFormat, AttributeValue}; use crate::graphics::flush; -use crate::{ - graphics::GraphicsPlugin, image::ImagePlugin, light::LightPlugin, render::command::DrawCommand, - surface::SurfacePlugin, -}; +use crate::render::command::DrawCommand; -use processing_midi::MidiPlugin; +#[derive(Component)] +pub struct Flush; -static IS_INIT: OnceLock<()> = OnceLock::new(); +pub struct ProcessingRenderPlugin; -thread_local! { - static APP: RefCell> = const { RefCell::new(None) }; -} +impl Plugin for ProcessingRenderPlugin { + fn build(&self, app: &mut App) { + use render::material::{add_custom_materials, add_standard_materials}; + use render::{activate_cameras, clear_transient_meshes, flush_draw_commands}; -#[derive(Component)] -pub struct Flush; + let config = app.world().resource::().clone(); -fn app_mut(cb: impl FnOnce(&mut App) -> error::Result) -> error::Result { - let res = APP.with(|app_cell| { - let mut app_borrow = app_cell.borrow_mut(); - let app = app_borrow - .as_mut() - .ok_or(error::ProcessingError::AppAccess)?; - cb(app) - })?; - Ok(res) + if let Some(asset_path) = config.get(ConfigKey::AssetRootPath) { + app.register_asset_source( + "assets_directory", + AssetSourceBuilder::platform_default(asset_path, None), + ); + } + + let has_sketch_file = config + .get(ConfigKey::SketchFileName) + .is_some_and(|f| !f.is_empty()); + if has_sketch_file { + if let Some(sketch_path) = config.get(ConfigKey::SketchRootPath) { + app.register_asset_source( + "sketch_directory", + AssetSourceBuilder::platform_default(sketch_path, None), + ); + } + } + + if has_sketch_file { + app.add_plugins(sketch::LivecodePlugin); + } + + app.add_plugins(( + image::ImagePlugin, + graphics::GraphicsPlugin, + surface::SurfacePlugin, + geometry::GeometryPlugin, + light::LightPlugin, + material::MaterialPlugin, + bevy::pbr::wireframe::WireframePlugin::default(), + material::custom::CustomMaterialPlugin, + )); + + app.add_systems(First, (clear_transient_meshes, activate_cameras)) + .add_systems( + Update, + ( + flush_draw_commands, + add_standard_materials, + add_custom_materials, + ) + .chain() + .before(AssetEventSystems), + ); + } } /// Create a WebGPU surface from a macOS NSWindow handle. @@ -216,164 +243,6 @@ pub fn surface_resize(graphics_entity: Entity, width: u32, height: u32) -> error }) } -fn create_app(config: Config) -> App { - let mut app = App::new(); - - app.insert_resource(config.clone()); - - if let Some(asset_path) = config.get(ConfigKey::AssetRootPath) { - app.register_asset_source( - "assets_directory", - AssetSourceBuilder::platform_default(asset_path, None), - ); - } - - let has_sketch_file = config - .get(ConfigKey::SketchFileName) - .is_some_and(|f| !f.is_empty()); - if has_sketch_file && let Some(sketch_path) = config.get(ConfigKey::SketchRootPath) { - app.register_asset_source( - "sketch_directory", - AssetSourceBuilder::platform_default(sketch_path, None), - ); - } - - #[cfg(not(target_arch = "wasm32"))] - let plugins = DefaultPlugins - .build() - .set(RenderPlugin { - synchronous_pipeline_compilation: true, - ..default() - }) - .disable::() - .disable::() - .disable::() - .set(WindowPlugin { - primary_window: None, - exit_condition: bevy::window::ExitCondition::DontExit, - ..default() - }); - - #[cfg(target_arch = "wasm32")] - let plugins = DefaultPlugins - .build() - .disable::() - .disable::() - .set(WindowPlugin { - primary_window: None, - exit_condition: bevy::window::ExitCondition::DontExit, - ..default() - }); - - app.add_plugins(plugins); - - if has_sketch_file { - app.add_plugins(sketch::LivecodePlugin); - } - - app.add_plugins(( - ImagePlugin, - GraphicsPlugin, - SurfacePlugin, - geometry::GeometryPlugin, - LightPlugin, - material::MaterialPlugin, - MidiPlugin, - bevy::pbr::wireframe::WireframePlugin::default(), - material::custom::CustomMaterialPlugin, - )); - app.add_systems(First, (clear_transient_meshes, activate_cameras)) - .add_systems( - Update, - ( - flush_draw_commands, - add_standard_materials, - add_custom_materials, - ) - .chain() - .before(AssetEventSystems), - ); - - app -} - -fn is_already_init() -> error::Result { - let is_init = IS_INIT.get().is_some(); - let thread_has_app = APP.with(|app_cell| app_cell.borrow().is_some()); - if is_init && !thread_has_app { - return Err(error::ProcessingError::AppAccess); - } - if is_init && thread_has_app { - debug!("App already initialized"); - return Ok(true); - } - Ok(false) -} - -fn set_app(app: App) { - APP.with(|app_cell| { - IS_INIT.get_or_init(|| ()); - *app_cell.borrow_mut() = Some(app); - }); -} - -/// Initialize the app, if not already initialized. Must be called from the main thread and cannot -/// be called concurrently from multiple threads. -#[cfg(not(target_arch = "wasm32"))] -pub fn init(config: Config) -> error::Result<()> { - if is_already_init()? { - return Ok(()); - } - setup_tracing(config.get(ConfigKey::LogLevel).map(|s| s.as_str()))?; - - let mut app = create_app(config); - // contrary to what the following methods might imply, this is just finishing plugin setup - // which normally happens in the app runner (i.e. in a "normal" bevy app), but since we don't - // have one we need to do it manually here - app.finish(); - app.cleanup(); - // also, we need to run the main schedule once to ensure all systems are initialized before we - // return from init, to ensure any plugins that need to do setup in their first update can rely - // on that - app.update(); - set_app(app); - - Ok(()) -} - -/// Initialize the app asynchronously -#[cfg(target_arch = "wasm32")] -pub async fn init(config: Config) -> error::Result<()> { - use bevy::app::PluginsState; - - if is_already_init()? { - return Ok(()); - } - setup_tracing(config.get(ConfigKey::LogLevel).map(|s| s.as_str()))?; - - let mut app = create_app(config); - - // we need to avoid blocking the main thread while waiting for plugins to initialize - while app.plugins_state() == PluginsState::Adding { - // yield to event loop - wasm_bindgen_futures::JsFuture::from(js_sys::Promise::new(&mut |resolve, _| { - web_sys::window() - .unwrap() - .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, 0) - .unwrap(); - })) - .await - .unwrap(); - } - - app.finish(); - app.cleanup(); - app.update(); - set_app(app); - - Ok(()) -} - /// Create a new graphics surface for rendering. pub fn graphics_create( surface_entity: Entity, @@ -502,44 +371,6 @@ pub fn graphics_update_region( }) } -pub fn exit(exit_code: u8) -> error::Result<()> { - app_mut(|app| { - app.world_mut().write_message(match exit_code { - 0 => AppExit::Success, - _ => AppExit::Error(NonZero::new(exit_code).unwrap()), - }); - - // one final update to process the exit message - app.update(); - Ok(()) - })?; - - // we need to drop the app in a deterministic manner to ensure resources are cleaned up - // otherwise we'll get wgpu graphics backend errors on exit - APP.with(|app_cell| { - let app = app_cell.borrow_mut().take(); - drop(app); - }); - - Ok(()) -} - -fn setup_tracing(log_level: Option<&str>) -> error::Result<()> { - // TODO: figure out wasm compatible tracing subscriber - #[cfg(not(target_arch = "wasm32"))] - { - use tracing_subscriber::EnvFilter; - - let filter = EnvFilter::try_new(log_level.unwrap_or("info")) - .unwrap_or_else(|_| EnvFilter::new("info")); - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_env_filter(filter) - .finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - Ok(()) -} - /// Record a drawing command for a window pub fn graphics_record_command(graphics_entity: Entity, cmd: DrawCommand) -> error::Result<()> { app_mut(|app| { diff --git a/crates/processing_render/src/material/custom.rs b/crates/processing_render/src/material/custom.rs index 3266211..56617b1 100644 --- a/crates/processing_render/src/material/custom.rs +++ b/crates/processing_render/src/material/custom.rs @@ -42,10 +42,10 @@ use bevy_naga_reflect::dynamic_shader::DynamicShader; use bevy::shader::Shader as ShaderAsset; -use crate::config::{Config, ConfigKey}; -use crate::error::{ProcessingError, Result}; use crate::material::MaterialValue; use crate::render::material::UntypedMaterial; +use processing_core::config::{Config, ConfigKey}; +use processing_core::error::{ProcessingError, Result}; #[derive(Asset, TypePath, Clone)] pub struct CustomMaterial { diff --git a/crates/processing_render/src/material/mod.rs b/crates/processing_render/src/material/mod.rs index 954d30b..288751a 100644 --- a/crates/processing_render/src/material/mod.rs +++ b/crates/processing_render/src/material/mod.rs @@ -3,8 +3,8 @@ pub mod pbr; use bevy::prelude::*; -use crate::error::{ProcessingError, Result}; use crate::render::material::UntypedMaterial; +use processing_core::error::{ProcessingError, Result}; pub struct MaterialPlugin; diff --git a/crates/processing_render/src/material/pbr.rs b/crates/processing_render/src/material/pbr.rs index ed40533..df3df23 100644 --- a/crates/processing_render/src/material/pbr.rs +++ b/crates/processing_render/src/material/pbr.rs @@ -1,7 +1,7 @@ use bevy::prelude::*; use super::MaterialValue; -use crate::error::{ProcessingError, Result}; +use processing_core::error::{ProcessingError, Result}; /// Set a property on a StandardMaterial by name. pub fn set_property( diff --git a/crates/processing_render/src/sketch.rs b/crates/processing_render/src/sketch.rs index 26ee32e..056f19e 100644 --- a/crates/processing_render/src/sketch.rs +++ b/crates/processing_render/src/sketch.rs @@ -9,7 +9,7 @@ use bevy::{ }; use std::path::Path; -use crate::config::{Config, ConfigKey}; +use processing_core::config::{Config, ConfigKey}; /// Plugin that registers the Sketch asset type and its loader. pub struct LivecodePlugin; diff --git a/crates/processing_render/src/surface.rs b/crates/processing_render/src/surface.rs index f87001f..6ca44da 100644 --- a/crates/processing_render/src/surface.rs +++ b/crates/processing_render/src/surface.rs @@ -31,14 +31,11 @@ use raw_window_handle::{ RawWindowHandle, WindowHandle, }; -use crate::{ - error, - error::{ProcessingError, Result}, - image::{Image, ImageTextures}, -}; - +use processing_core::error::{self, ProcessingError, Result}; use std::ptr::NonNull; +use crate::image::{Image, ImageTextures}; + #[derive(Component, Debug, Clone)] pub struct Surface; diff --git a/crates/processing_render/src/transform.rs b/crates/processing_render/src/transform.rs index 9209c77..5b902c5 100644 --- a/crates/processing_render/src/transform.rs +++ b/crates/processing_render/src/transform.rs @@ -1,6 +1,6 @@ use bevy::prelude::*; -use crate::error::{ProcessingError, Result}; +use processing_core::error::{ProcessingError, Result}; pub fn set_position( In((entity, x, y, z)): In<(Entity, f32, f32, f32)>, diff --git a/crates/processing_webcam/Cargo.toml b/crates/processing_webcam/Cargo.toml new file mode 100644 index 0000000..196b55a --- /dev/null +++ b/crates/processing_webcam/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "processing_webcam" +version = "0.1.0" +edition = "2024" + +[lints] +workspace = true + +[dependencies] +bevy = { workspace = true } +nannou_webcam = { git = "https://github.com/nannou-org/nannou", branch = "bevy-refactor" } +processing_core = { workspace = true } +processing_render = { workspace = true } diff --git a/crates/processing_webcam/src/lib.rs b/crates/processing_webcam/src/lib.rs new file mode 100644 index 0000000..7b66dfc --- /dev/null +++ b/crates/processing_webcam/src/lib.rs @@ -0,0 +1,105 @@ +use bevy::ecs::system::RunSystemOnce; +use bevy::prelude::*; + +pub use nannou_webcam::{ + Webcam, WebcamConnected, WebcamDevice, WebcamDeviceAdded, WebcamDeviceRemoved, + WebcamDisconnected, WebcamError, WebcamFormat, WebcamPlugin, WebcamStream, + WebcamSupportedFormat, +}; + +use processing_core::app_mut; +use processing_core::error::{ProcessingError, Result}; +use processing_render::image; + +#[derive(Component)] +pub struct ProcessingWebcam; + +fn create(In(()): In<()>, mut commands: Commands) -> Entity { + commands.spawn((ProcessingWebcam, Webcam::default())).id() +} + +fn create_with_format(In(format): In, mut commands: Commands) -> Entity { + commands + .spawn(( + ProcessingWebcam, + Webcam { + format, + ..default() + }, + )) + .id() +} + +fn create_image(In(entity): In, world: &mut World) -> Result { + let stream = world + .get::(entity) + .ok_or(ProcessingError::WebcamNotConnected)?; + let handle = stream.image.clone(); + + let child = world + .run_system_once_with(image::from_handle, handle) + .unwrap()?; + world.entity_mut(entity).add_child(child); + Ok(child) +} + +fn is_connected(In(entity): In, streams: Query<&WebcamStream>) -> Result { + Ok(streams.get(entity).is_ok()) +} + +fn resolution(In(entity): In, streams: Query<&WebcamStream>) -> Result<(u32, u32)> { + let stream = streams + .get(entity) + .map_err(|_| ProcessingError::WebcamNotConnected)?; + Ok((stream.resolution.x, stream.resolution.y)) +} + +fn destroy(In(entity): In, mut commands: Commands) -> Result<()> { + commands.entity(entity).despawn(); + Ok(()) +} + +pub fn webcam_create() -> Result { + app_mut(|app| Ok(app.world_mut().run_system_cached_with(create, ()).unwrap())) +} + +pub fn webcam_create_with_format(format: WebcamFormat) -> Result { + app_mut(|app| { + Ok(app + .world_mut() + .run_system_cached_with(create_with_format, format) + .unwrap()) + }) +} + +pub fn webcam_is_connected(entity: Entity) -> Result { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(is_connected, entity) + .unwrap() + }) +} + +pub fn webcam_image(entity: Entity) -> Result { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(create_image, entity) + .unwrap() + }) +} + +pub fn webcam_resolution(entity: Entity) -> Result<(u32, u32)> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(resolution, entity) + .unwrap() + }) +} + +pub fn webcam_destroy(entity: Entity) -> Result<()> { + app_mut(|app| { + app.world_mut() + .run_system_cached_with(destroy, entity) + .unwrap() + }) +} diff --git a/examples/glfw.rs b/examples/glfw.rs index 6387d1d..55e3fda 100644 --- a/examples/glfw.rs +++ b/examples/glfw.rs @@ -1,7 +1,7 @@ /// Minimal GLFW helper for Processing examples use bevy::prelude::Entity; use glfw::{Glfw, GlfwReceiver, PWindow, WindowEvent, WindowMode}; -use processing_render::error::Result; +use processing_core::error::Result; pub struct GlfwContext { glfw: Glfw, diff --git a/examples/webcam.rs b/examples/webcam.rs new file mode 100644 index 0000000..b189275 --- /dev/null +++ b/examples/webcam.rs @@ -0,0 +1,51 @@ +mod glfw; + +use glfw::GlfwContext; +use processing::prelude::*; +use processing_render::render::command::DrawCommand; +use processing_webcam::{webcam_create, webcam_destroy, webcam_image, webcam_is_connected}; + +fn main() { + match sketch() { + Ok(_) => { + eprintln!("Sketch completed successfully"); + exit(0).unwrap(); + } + Err(e) => { + eprintln!("Sketch error: {:?}", e); + exit(1).unwrap(); + } + }; +} + +fn sketch() -> error::Result<()> { + let width = 640; + let height = 480; + + let mut glfw_ctx = GlfwContext::new(width, height)?; + init(Config::default())?; + + let surface = glfw_ctx.create_surface(width, height)?; + let graphics = graphics_create(surface, width, height, TextureFormat::Rgba16Float)?; + + let webcam = webcam_create()?; + let mut image_entity = None; + + while glfw_ctx.poll_events() { + graphics_begin_draw(graphics)?; + + // Once connected, grab an image entity from the webcam stream + if image_entity.is_none() && webcam_is_connected(webcam)? { + image_entity = Some(webcam_image(webcam)?); + } + + if let Some(img) = image_entity { + graphics_record_command(graphics, DrawCommand::BackgroundImage(img))?; + } + + graphics_end_draw(graphics)?; + } + + webcam_destroy(webcam)?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index b9d7209..d7281e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,144 @@ pub mod prelude; + +use std::num::NonZero; + +use bevy::app::{App, AppExit}; +use bevy::prelude::*; +use bevy::render::RenderPlugin; + +use processing_core::config::{Config, ConfigKey}; +use processing_core::error; + +fn create_app(config: Config) -> App { + let mut app = App::new(); + + app.insert_resource(config.clone()); + + #[cfg(not(target_arch = "wasm32"))] + let plugins = DefaultPlugins + .build() + .set(RenderPlugin { + synchronous_pipeline_compilation: true, + ..default() + }) + .disable::() + .disable::() + .disable::() + .set(WindowPlugin { + primary_window: None, + exit_condition: bevy::window::ExitCondition::DontExit, + ..default() + }); + + #[cfg(target_arch = "wasm32")] + let plugins = DefaultPlugins + .build() + .disable::() + .disable::() + .set(WindowPlugin { + primary_window: None, + exit_condition: bevy::window::ExitCondition::DontExit, + ..default() + }); + + app.add_plugins(plugins); + app.add_plugins(processing_midi::MidiPlugin); + app.add_plugins(processing_render::ProcessingRenderPlugin); + + #[cfg(feature = "webcam")] + app.add_plugins(processing_webcam::WebcamPlugin); + + app +} + +/// Initialize the app, if not already initialized. Must be called from the main thread and cannot +/// be called concurrently from multiple threads. +#[cfg(not(target_arch = "wasm32"))] +pub fn init(config: Config) -> error::Result<()> { + if processing_core::is_already_init()? { + return Ok(()); + } + setup_tracing(config.get(ConfigKey::LogLevel).map(|s| s.as_str()))?; + + let mut app = create_app(config); + // contrary to what the following methods might imply, this is just finishing plugin setup + // which normally happens in the app runner (i.e. in a "normal" bevy app), but since we don't + // have one we need to do it manually here + app.finish(); + app.cleanup(); + // also, we need to run the main schedule once to ensure all systems are initialized before we + // return from init, to ensure any plugins that need to do setup in their first update can rely + // on that + app.update(); + processing_core::set_app(app); + + Ok(()) +} + +/// Initialize the app asynchronously +#[cfg(target_arch = "wasm32")] +pub async fn init(config: Config) -> error::Result<()> { + use bevy::app::PluginsState; + + if processing_core::is_already_init()? { + return Ok(()); + } + setup_tracing(config.get(ConfigKey::LogLevel).map(|s| s.as_str()))?; + + let mut app = create_app(config); + + // we need to avoid blocking the main thread while waiting for plugins to initialize + while app.plugins_state() == PluginsState::Adding { + // yield to event loop + wasm_bindgen_futures::JsFuture::from(js_sys::Promise::new(&mut |resolve, _| { + web_sys::window() + .unwrap() + .set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, 0) + .unwrap(); + })) + .await + .unwrap(); + } + + app.finish(); + app.cleanup(); + app.update(); + processing_core::set_app(app); + + Ok(()) +} + +pub fn exit(exit_code: u8) -> error::Result<()> { + processing_core::app_mut(|app| { + app.world_mut().write_message(match exit_code { + 0 => AppExit::Success, + _ => AppExit::Error(NonZero::new(exit_code).unwrap()), + }); + + // one final update to process the exit message + app.update(); + Ok(()) + })?; + + // we need to drop the app in a deterministic manner to ensure resources are cleaned up + // otherwise we'll get wgpu graphics backend errors on exit + drop(processing_core::take_app()); + + Ok(()) +} + +fn setup_tracing(log_level: Option<&str>) -> error::Result<()> { + // TODO: figure out wasm compatible tracing subscriber + #[cfg(not(target_arch = "wasm32"))] + { + use tracing_subscriber::EnvFilter; + + let filter = EnvFilter::try_new(log_level.unwrap_or("info")) + .unwrap_or_else(|_| EnvFilter::new("info")); + let subscriber = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(filter) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; + } + Ok(()) +} diff --git a/src/prelude.rs b/src/prelude.rs index c23d694..b20bc44 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,7 +1,9 @@ pub use bevy::prelude::default; pub use bevy::render::render_resource::TextureFormat; +pub use processing_core::{config::*, error}; pub use processing_render::{ - config::*, render::command::{DrawCommand, StrokeCapMode, StrokeJoinMode}, *, }; + +pub use crate::{exit, init};