diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..712ed190 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_PYTHON = "python" \ No newline at end of file diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 63224a05..d0f67806 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -36,6 +36,7 @@ jobs: with: path: ./website/public deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: build environment: diff --git a/Cargo.lock b/Cargo.lock index fda5283c..b87e0977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,8 +15,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "const-random", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -103,9 +101,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" [[package]] name = "arbitrary" @@ -145,17 +143,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "assert_type_match" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-channel" version = "2.3.1" @@ -203,107 +190,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bevy_color" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00aa2966c7ca0c7dd39f5ba8f3b1eaa5c2005a93ffdefb7a4090150d8327678" -dependencies = [ - "bevy_math", - "bevy_reflect", - "bytemuck", - "derive_more", - "encase", -] - -[[package]] -name = "bevy_macro_utils" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bdb3a681c24abace65bf18ed467ad8befbedb42468b32e459811bfdb01e506c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "toml_edit", -] - -[[package]] -name = "bevy_math" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edec18d90e6bab27b5c6131ee03172ece75b7edd0abe4e482a26d6db906ec357" -dependencies = [ - "derive_more", - "glam", - "itertools 0.13.0", - "smallvec", -] - -[[package]] -name = "bevy_ptr" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa65df6a190b7dfc84d79f09cf02d47ae046fa86a613e202c31559e06d8d3710" - -[[package]] -name = "bevy_reflect" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab3264acc3b6f48bc23fbd09fdfea6e5d9b7bfec142e4f3333f532acf195bca" -dependencies = [ - "assert_type_match", - "bevy_ptr", - "bevy_reflect_derive", - "bevy_utils", - "derive_more", - "disqualified", - "downcast-rs", - "erased-serde", - "serde", - "smallvec", - "smol_str", -] - -[[package]] -name = "bevy_reflect_derive" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f83876a322130ab38a47d5dcf75258944bf76b3387d1acdb3750920fda63e2" -dependencies = [ - "bevy_macro_utils", - "proc-macro2", - "quote", - "syn", - "uuid", -] - -[[package]] -name = "bevy_utils" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f01088c048960ea50ee847c3f668942ecf49ed26be12a1585a5e59b6a941d9a" -dependencies = [ - "ahash", - "bevy_utils_proc_macros", - "getrandom", - "hashbrown 0.14.5", - "thread_local", - "tracing", - "web-time", -] - -[[package]] -name = "bevy_utils_proc_macros" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0c3244d543cc964545b7aa074f6fb18a915a7121cf3de5d7ed37a4aae8662d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -429,6 +315,46 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clap" +version = "4.5.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -439,6 +365,33 @@ dependencies = [ "unicode-width 0.1.14", ] +[[package]] +name = "color" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c387f6cef110ee8eaf12fca5586d3d303c07c594f4a5f02c768b6470b70dbd" + +[[package]] +name = "color-print" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4" +dependencies = [ + "color-print-proc-macro", +] + +[[package]] +name = "color-print-proc-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -473,32 +426,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "const_panic" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81" - [[package]] name = "core-foundation" version = "0.9.4" @@ -581,33 +508,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "disqualified" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" - [[package]] name = "document-features" version = "0.2.10" @@ -618,10 +518,10 @@ dependencies = [ ] [[package]] -name = "downcast-rs" -version = "1.2.1" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" @@ -629,37 +529,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "encase" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a05902cf601ed11d564128448097b98ebe3c6574bd7b6a653a3d56d54aa020" -dependencies = [ - "const_panic", - "encase_derive", - "thiserror 1.0.69", -] - -[[package]] -name = "encase_derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "181d475b694e2dd56ae919ce7699d344d1fd259292d590c723a50d1189a2ea85" -dependencies = [ - "encase_derive_impl", -] - -[[package]] -name = "encase_derive_impl" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -695,16 +564,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "erased-serde" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" -dependencies = [ - "serde", - "typeid", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -829,10 +688,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", - "wasi", - "wasm-bindgen", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -858,9 +727,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.29.2" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +checksum = "17fcdf9683c406c2fc4d124afd29c0d595e22210d633cbdb8695ba9935ab1dc6" dependencies = [ "bytemuck", ] @@ -955,7 +824,6 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", - "serde", ] [[package]] @@ -1050,6 +918,12 @@ dependencies = [ "web-time", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "interpolate_name" version = "0.2.4" @@ -1061,6 +935,15 @@ dependencies = [ "syn", ] +[[package]] +name = "inventory" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb" +dependencies = [ + "rustversion", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1076,15 +959,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1255,6 +1129,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.31.0" @@ -1540,6 +1423,70 @@ dependencies = [ "syn", ] +[[package]] +name = "pyo3" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" +dependencies = [ + "cfg-if", + "indoc", + "inventory", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "qoi" version = "0.4.1" @@ -1591,7 +1538,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -1606,8 +1553,8 @@ version = "0.0.0" dependencies = [ "anyhow", "async-channel", - "bevy_color", "bytemuck", + "color", "env_logger", "glam", "image", @@ -1622,6 +1569,27 @@ dependencies = [ "wgpu-profiler", ] +[[package]] +name = "ranim-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "color-print", + "dunce", + "pyo3", + "ranim", + "ranimpy", +] + +[[package]] +name = "ranimpy" +version = "0.1.0" +dependencies = [ + "pyo3", + "ranim", +] + [[package]] name = "rav1e" version = "0.7.1" @@ -1870,15 +1838,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -1903,6 +1862,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -2014,16 +1979,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tiff" version = "0.9.1" @@ -2035,15 +1990,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tiny-skia-path" version = "0.11.4" @@ -2104,25 +2050,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - [[package]] name = "ttf-parser" version = "0.24.1" @@ -2132,12 +2059,6 @@ dependencies = [ "core_maths", ] -[[package]] -name = "typeid" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" - [[package]] name = "unicode-bidi" version = "0.3.17" @@ -2198,6 +2119,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "usvg" version = "0.44.0" @@ -2233,11 +2160,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" dependencies = [ - "getrandom", + "getrandom 0.3.1", ] [[package]] @@ -2269,6 +2196,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2640,6 +2576,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "xml-rs" version = "0.8.23" @@ -2652,6 +2597,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xtask-build-examples" +version = "0.1.0" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 4e399d2c..a89ad59f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,27 +8,31 @@ readme = "README.md" license = "MIT" keywords = ["animation", "manim"] -# [workspace] -# members = ["packages/*"] +[workspace] +members = ["packages/*", "xtasks/*"] + +[workspace.dependencies] +ranim = { path = "." } +ranimpy = { path = "packages/ranimpy" } [dependencies] # ranim_derive.workspace = true async-channel = "2.3.1" bytemuck = { version = "1.20.0", features = ["derive"] } env_logger = "0.11.6" -glam = { version = "0.29.2", features = ["bytemuck"] } +glam = { version = "0.30.0", features = ["bytemuck"] } image = "0.25.5" itertools = "0.14" log = "0.4.25" pollster = "0.4.0" -uuid = { version = "1.12.1", features = ["v4"] } +uuid = { version = "1.13.2", features = ["v4"] } wgpu = "24.0.1" anyhow = "1.0.95" -bevy_color = "0.15.2" usvg = "0.44.0" regex = "1.11.1" wgpu-profiler = "0.21.0" indicatif = "0.17.11" +color = "0.2.3" # Enable a small amount of optimization in the dev profile. [profile.dev] diff --git a/examples/arc/main.rs b/examples/arc/main.rs index 39714177..416bcb8c 100644 --- a/examples/arc/main.rs +++ b/examples/arc/main.rs @@ -1,9 +1,9 @@ -use bevy_color::{Alpha, Srgba}; use env_logger::Env; use glam::vec2; -use ranim::animation::entity::fading::fade_in; -use ranim::animation::timeline::Timeline; +use ranim::animation::fading::fade_in; +use ranim::color::HueDirection; use ranim::items::vitem::Arc; +use ranim::timeline::Timeline; use ranim::{prelude::*, AppOptions, TimelineConstructor}; struct ArcScene; @@ -19,8 +19,8 @@ impl TimelineConstructor for ArcScene { let frame_size = (1920.0, 1080.0); let frame_start = vec2(frame_size.0 as f32 / -2.0, frame_size.1 as f32 / -2.0); - let start_color = Srgba::hex("FF8080FF").unwrap(); - let end_color = Srgba::hex("58C4DDFF").unwrap(); + let start_color = color!("#FF8080FF"); + let end_color = color!("#58C4DDFF"); let nrow = 10; let ncol = 10; @@ -33,7 +33,11 @@ impl TimelineConstructor for ArcScene { for j in 0..ncol { let angle = std::f32::consts::PI * j as f32 / (ncol - 1) as f32 * 360.0 / 180.0; let radius = step_y / 2.0; - let color = start_color.lerp(&end_color, i as f32 / (nrow - 1) as f32); + let color = start_color.lerp( + end_color, + i as f32 / (nrow - 1) as f32, + HueDirection::Increasing, + ); let offset = frame_start + vec2( j as f32 * step_x + step_x / 2.0 + j as f32 * gap + padding, diff --git a/examples/arc_between_points/main.rs b/examples/arc_between_points/main.rs index 50dca40b..dd06c71a 100644 --- a/examples/arc_between_points/main.rs +++ b/examples/arc_between_points/main.rs @@ -1,13 +1,13 @@ use std::time::Instant; -use bevy_color::Srgba; use env_logger::Env; use glam::{vec2, Mat2}; use log::info; -use ranim::animation::entity::creation::Color; -use ranim::animation::entity::fading::fade_in; -use ranim::animation::timeline::Timeline; +use ranim::animation::creation::Color; +use ranim::animation::fading::fade_in; +use ranim::color::HueDirection; use ranim::items::vitem::ArcBetweenPoints; +use ranim::timeline::Timeline; use ranim::{prelude::*, AppOptions, SceneDesc, TimelineConstructor}; pub struct ArcBetweenPointsScene; @@ -21,8 +21,8 @@ impl TimelineConstructor for ArcBetweenPointsScene { fn construct(&mut self, timeline: &mut Timeline) { let center = vec2(0.0, 0.0); - let start_color = Srgba::hex("FF8080FF").unwrap(); - let end_color = Srgba::hex("58C4DDFF").unwrap(); + let start_color = color!("#FF8080FF"); + let end_color = color!("#58C4DDFF"); let ntan = 16; let nrad = 5; @@ -37,7 +37,11 @@ impl TimelineConstructor for ArcBetweenPointsScene { let angle = angle_step * (i + 1) as f32; for j in 0..ntan { - let color = start_color.lerp(&end_color, j as f32 / (ntan - 1) as f32); + let color = start_color.lerp( + end_color, + j as f32 / (ntan - 1) as f32, + HueDirection::Increasing, + ); let vec = Mat2::from_angle(std::f32::consts::PI * 2.0 / ntan as f32 * j as f32) * vec2(rad, 0.0); let mut arc = ArcBetweenPoints { diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 886f7b39..318a15d0 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -1,17 +1,15 @@ use std::f32; -use bevy_color::Srgba; use env_logger::Env; use glam::{vec3, Vec3}; -use ranim::animation::entity::creation::{uncreate, unwrite, write, Color}; -use ranim::animation::entity::fading::{fade_in, fade_out}; -use ranim::animation::entity::interpolate::interpolate; -use ranim::animation::timeline::Timeline; - +use ranim::animation::creation::{uncreate, unwrite, write, Color}; +use ranim::animation::fading::{fade_in, fade_out}; +use ranim::animation::interpolate::interpolate; use ranim::color::palettes::manim; use ranim::items::svg_item::SvgItem; use ranim::items::vitem::{Arc, Polygon}; use ranim::items::Rabject; +use ranim::timeline::Timeline; use ranim::{prelude::*, typst_svg, AppOptions, SceneDesc, TimelineConstructor}; const SVG: &str = include_str!("../../assets/Ghostscript_Tiger.svg"); @@ -60,7 +58,7 @@ impl TimelineConstructor for MainScene { ]) .build(); polygon - .set_color(Srgba::hex("FF8080FF").unwrap()) + .set_color(color!("#FF8080FF")) .set_fill_opacity(0.5) .rotate(std::f32::consts::FRAC_PI_2, Vec3::Z); diff --git a/examples/palettes/main.rs b/examples/palettes/main.rs index 672f23c0..dc3e2ef2 100644 --- a/examples/palettes/main.rs +++ b/examples/palettes/main.rs @@ -1,9 +1,9 @@ use env_logger::Env; use glam::vec3; -use ranim::animation::entity::creation::Color; -use ranim::animation::timeline::Timeline; +use ranim::animation::creation::Color; use ranim::color::palettes::manim::*; use ranim::items::vitem::Rectangle; +use ranim::timeline::Timeline; use ranim::{prelude::*, TimelineConstructor}; struct Palettes; diff --git a/examples/test_scene/main.rs b/examples/test_scene/main.rs index 3f0ada94..28322d10 100644 --- a/examples/test_scene/main.rs +++ b/examples/test_scene/main.rs @@ -6,11 +6,8 @@ use env_logger::Env; use glam::Vec3; use ranim::{ animation::{ - entity::{ - creation::{create, unwrite, write}, - freeze::freeze, - }, - timeline::Timeline, + creation::{create, unwrite, write}, + freeze::freeze, }, components::TransformAnchor, items::{ @@ -19,6 +16,7 @@ use ranim::{ Rabject, }, prelude::*, + timeline::Timeline, AppOptions, TimelineConstructor, }; diff --git a/justfile b/justfile index 74b27443..d86b2dc0 100644 --- a/justfile +++ b/justfile @@ -16,4 +16,11 @@ examples: just example palettes clean: - -rm *.log \ No newline at end of file + -rm *.log + +fmt: + cargo fmt --all + +lint: + cargo clippy --workspace --all-targets -- -D warnings + cargo fmt --all --check \ No newline at end of file diff --git a/packages/ranim-cli/.cargo/config.toml b/packages/ranim-cli/.cargo/config.toml new file mode 100644 index 00000000..712ed190 --- /dev/null +++ b/packages/ranim-cli/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_PYTHON = "python" \ No newline at end of file diff --git a/packages/ranim-cli/Cargo.toml b/packages/ranim-cli/Cargo.toml new file mode 100644 index 00000000..bcf12f4b --- /dev/null +++ b/packages/ranim-cli/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ranim-cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +ranimpy.workspace = true +ranim.workspace = true +dunce = "1.0.5" +color-print = "0.3.7" +anyhow = "1.0.96" +pyo3 = { version = "0.23.4", features = ["extension-module"] } +clap = { version = "4.5.30", features = ["derive"] } + +[[bin]] +name = "ranim" +path = "src/main.rs" diff --git a/packages/ranim-cli/src/main.rs b/packages/ranim-cli/src/main.rs new file mode 100644 index 00000000..4e1091c5 --- /dev/null +++ b/packages/ranim-cli/src/main.rs @@ -0,0 +1,277 @@ +use std::{ffi::CString, path::PathBuf}; + +use color_print::cprintln; +use pyo3::{ + ffi::c_str, + types::{PyAnyMethods, PyDictMethods, PyModule, PyModuleMethods}, + Bound, PyAny, PyResult, Python, +}; +use ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; +use ranimpy::{ranimpy as ranimpy_module, timeline::PyTimeline}; + +/// 从 Python 模块中获取符合条件的 timeline 构建函数 +/// 条件: +/// 1. 函数名以 timeline_ 开头 +/// 2. 无参数 +/// 3. 返回类型为 ranimpy.Timeline +fn get_timeline_funcs<'py>( + py: &Python<'py>, + module: &Bound<'py, PyModule>, +) -> PyResult)>> { + let dict = module.dict(); + let timeline_type = py.import("ranimpy")?.getattr("Timeline")?; + + let mut result = Vec::new(); + + for (_, obj) in dict.iter() { + let Ok(func) = obj.downcast::() else { + continue; + }; + + // 检查函数名 + let name = func.getattr("__name__")?.to_string(); + if !name.starts_with("timeline_") { + continue; + } + + // 检查参数个数 + if let Ok(code) = func.getattr("__code__") { + if code.getattr("co_argcount")?.extract::()? != 0 { + continue; + } + } + + // 检查返回类型注解 + let Ok(return_type) = func + .getattr("__annotations__") + .and_then(|annotations| annotations.get_item("return")) + else { + continue; + }; + + if !return_type.is(&timeline_type) { + continue; + } + + // 提取 timeline_xxx 中的 xxx 部分 + let name = name.strip_prefix("timeline_").unwrap().to_string(); + result.push((name, obj)); + } + + Ok(result) +} + +use clap::Parser; + +/// ranim CLI 参数 +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +#[command(arg_required_else_help = true)] +struct Args { + /// Python 源文件路径 + #[arg(value_name = "INPUT_FILE")] + input_file: PathBuf, + + /// 虚拟环境目录路径 + #[arg(value_name = "VENV_DIR")] + venv_dir: Option, +} + +fn main() -> anyhow::Result<()> { + // let args = args_os() + // .into_iter() + // .skip_while(|arg| !arg.to_str().unwrap().contains("ranim")) + // .collect::>(); + // println!("{:?}", args); + let args = Args::parse(); + + // 验证输入文件扩展名 + if args.input_file.extension() != Some("py".as_ref()) { + anyhow::bail!("Input file must have .py extension"); + } + + let content = std::fs::read_to_string(&args.input_file).expect("failed to read from file"); + let content = CString::new(content).expect("failed to convert to CString"); + + pyo3::append_to_inittab!(ranimpy_module); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let sys = PyModule::import(py, "sys")?; + + let executable = sys.getattr("executable")?; + let version = sys.getattr("version")?; + cprintln!("pyo3 sys.executable: {}", executable); + cprintln!("pyo3 sys.version: {}", version); + + let path = sys.getattr("path")?; + if let Some(venv_dir) = args.venv_dir { + cprintln!("using venv {:?}", venv_dir); + let site_packages_path = + dunce::canonicalize(venv_dir.join("Lib/site-packages")).unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),))?; + } + cprintln!("pyo3 sys.path: {}", path); + + cprintln!("[ranim]: loading module {:?}...", args.input_file); + let module = PyModule::from_code(py, &content, c_str!("scene.py"), c_str!("scene"))?; + + cprintln!("[ranim]: getting timeline funcs..."); + let timeline_funcs = get_timeline_funcs(&py, &module)?; + + cprintln!("[ranim]: building timelines..."); + let timelines = timeline_funcs + .into_iter() + .map(|(name, func)| { + cprintln!("[ranim]: building timeline {}...", name); + let timeline = func + .call0() + .map_err(|err| anyhow::anyhow!("{:?}", err)) + .and_then(|t| { + t.downcast_into::() + .map_err(|err| anyhow::anyhow!("{:?}", err)) + })?; + Ok::<_, anyhow::Error>((name, timeline)) + }) + .collect::>>()?; + cprintln!( + "[ranim]: built timelines: {:?}", + timelines.iter().map(|(name, _)| name).collect::>() + ); + + cprintln!("[ranim]: rendering timelines..."); + for (name, timeline) in timelines { + cprintln!("[ranim]: rendering timeline {}...", name); + let mut timeline = timeline.borrow_mut(); + let mut app = RanimRenderApp::new(&AppOptions { + output_dir: PathBuf::from(format!("output/{}", name)), + ..Default::default() + }); + if timeline.elapsed_secs() == 0.0 { + timeline.forward(0.1); + } + let duration_secs = timeline.elapsed_secs(); + app.render_anim( + AnimWithParams::new(timeline.inner.clone()) + .with_duration(duration_secs) + .with_rate_func(linear), + ); + } + + Ok(()) + }) +} + +#[cfg(test)] +mod test { + use pyo3::PyResult; + + use super::*; + + #[test] + fn test_downcast() -> PyResult<()> { + pyo3::append_to_inittab!(ranimpy_module); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let sys = PyModule::import(py, "sys")?; + + let executable = sys.getattr("executable")?; + let path = sys.getattr("path")?; + let version = sys.getattr("version")?; + // if args.len() == 2 { + // let venv_dir = Path::new(&args[1]); + let site_packages_path = dunce::canonicalize("../../.venv/Lib/site-packages").unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),)) + .unwrap(); + // } + println!("pyo3 sys.executable: {}", executable); + println!("pyo3 sys.path: {}", path); + println!("pyo3 sys.version: {}", version); + + let module = PyModule::from_code( + py, + c_str!( + r#" + import ranimpy + + def build_timeline(): + return ranimpy.Timeline() + "# + ), + c_str!("scene.py"), + c_str!("scene"), + )?; + + let timeline = module.getattr("build_timeline")?.call0()?; + assert!(timeline.downcast_into::().is_ok()); + + Ok(()) + }) + } + + #[test] + fn test_python_module_methods() -> PyResult<()> { + pyo3::append_to_inittab!(ranimpy_module); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let sys = PyModule::import(py, "sys")?; + + let executable = sys.getattr("executable")?; + let path = sys.getattr("path")?; + let version = sys.getattr("version")?; + // if args.len() == 2 { + // let venv_dir = Path::new(&args[1]); + let site_packages_path = dunce::canonicalize("../../.venv/Lib/site-packages").unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),)) + .unwrap(); + // } + println!("pyo3 sys.executable: {}", executable); + println!("pyo3 sys.path: {}", path); + println!("pyo3 sys.version: {}", version); + + let module = PyModule::from_code( + py, + c_str!( + r#" +import ranimpy +a = 1 + +def foo1() -> int: + return 0 + +def foo2(a: int): + pass + +def timeline_foo1(): + return ranimpy.Timeline() + +def timeline_foo2() -> ranimpy.Timeline: + return ranimpy.Timeline() + +def timeline_foo3(): + pass + +def timeline_foo4(): + a = 0 + +def timeline_foo5() -> int: + pass + +def timeline_foo6(a) -> ranimpy.Timeline: + return ranimpy.Timeline() + +foo1() +foo2(foo1()) + "# + ), + c_str!("scene.py"), + c_str!("scene"), + )?; + + let timelines = get_timeline_funcs(&py, &module)?; + assert!(timelines.len() == 1); + assert!(timelines[0].0 == "foo2"); + + Ok(()) + }) + } +} diff --git a/packages/ranimpy/.cargo/config.toml b/packages/ranimpy/.cargo/config.toml new file mode 100644 index 00000000..712ed190 --- /dev/null +++ b/packages/ranimpy/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_PYTHON = "python" \ No newline at end of file diff --git a/packages/ranimpy/Cargo.toml b/packages/ranimpy/Cargo.toml new file mode 100644 index 00000000..dc91dfbb --- /dev/null +++ b/packages/ranimpy/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ranimpy" +version = "0.1.0" +edition = "2021" + +[lib] +name = "ranimpy" +crate-type = ["cdylib", "rlib"] + +[dependencies] +ranim.workspace = true +pyo3 = { version = "0.23.4", features = ["extension-module", "multiple-pymethods"] } diff --git a/packages/ranimpy/examples/hello_ranimpy/main.py b/packages/ranimpy/examples/hello_ranimpy/main.py new file mode 100644 index 00000000..814b21a4 --- /dev/null +++ b/packages/ranimpy/examples/hello_ranimpy/main.py @@ -0,0 +1,27 @@ +import ranimpy + +""" + + + +""" +# print(ranim) +# print(ranim.Timeline) +# print(ranim.Timeline()) + +def timeline_test() -> ranimpy.Timeline: + timeline = ranimpy.Timeline() + + with open("../../assets/Ghostscript_Tiger.svg") as f: + svg = f.read() + + svg = ranimpy.SvgItem(svg) + svg.shift((100, 100, 0)) + + timeline.show(svg) + timeline.forward(1.0) + + return timeline + +if __name__ == '__main__': + ranimpy.render_timeline(timeline_test(), "./") \ No newline at end of file diff --git a/packages/ranimpy/pyproject.toml b/packages/ranimpy/pyproject.toml new file mode 100644 index 00000000..503f812d --- /dev/null +++ b/packages/ranimpy/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "ranimpy" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/packages/ranimpy/src/items.rs b/packages/ranimpy/src/items.rs new file mode 100644 index 00000000..1c94a3a6 --- /dev/null +++ b/packages/ranimpy/src/items.rs @@ -0,0 +1,157 @@ +use pyo3::{ + pyclass, pymethods, + types::{PyModule, PyModuleMethods}, + Bound, PyResult, +}; +use ranim::{ + glam::vec3, + items::{svg_item::SvgItem, vitem::VItem, Rabject}, +}; + +pub fn items(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + Ok(()) +} + +// MARK: SvgItem + +/// SvgItem +#[pyclass] +#[pyo3(name = "SvgItem")] +pub struct PySvgItem { + pub(crate) inner: Rabject, +} + +#[pymethods] +impl PySvgItem { + #[new] + pub fn new(svg_str: &str) -> Self { + Self { + inner: Rabject::new(SvgItem::from_svg(svg_str)), + } + } +} + +// MARK: VItem + +#[pyclass] +#[pyo3(name = "VItem")] +pub struct PyVItem { + pub(crate) inner: Rabject, +} + +#[pymethods] +impl PyVItem { + #[new] + pub fn new(vpoints: Vec<[f32; 3]>) -> Self { + let vpoints = vpoints.iter().map(|v| vec3(v[0], v[1], v[2])).collect(); + Self { + inner: Rabject::new(VItem::from_vpoints(vpoints)), + } + } +} +// MARK: macro impl_transformable + +use ranim::components::TransformAnchor; +use ranim::glam::{IVec3, Vec3}; +use ranim::prelude::*; + +macro_rules! impl_transformable { + ($py_class:ident) => { + #[pymethods] + impl $py_class { + // 基础变换方法 + fn shift(&mut self, shift: (f32, f32, f32)) { + self.inner.shift(Vec3::from(shift)); + } + + fn scale(&mut self, scale: (f32, f32, f32)) { + self.inner.scale(Vec3::from(scale)); + } + + // fn scale_by_anchor( + // &mut self, + // scale: (f32, f32, f32), + // anchor: TransformAnchor + // ) { + // self.inner.scale_by_anchor(Vec3::from(scale), anchor); + // } + + // 旋转方法 + fn rotate(&mut self, angle: f32, axis: (f32, f32, f32)) { + self.inner.rotate(angle, Vec3::from(axis)); + } + + fn rotate_by_point( + &mut self, + angle: f32, + axis: (f32, f32, f32), + anchor: (f32, f32, f32), + ) { + self.inner.rotate_by_anchor( + angle, + Vec3::from(axis), + TransformAnchor::Point(Vec3::from(anchor)), + ); + } + + fn rotate_by_edge( + &mut self, + angle: f32, + axis: (f32, f32, f32), + anchor: (i32, i32, i32), + ) { + self.inner.rotate_by_anchor( + angle, + Vec3::from(axis), + TransformAnchor::Edge(IVec3::from(anchor)), + ); + } + // 定位方法 + fn put_center_on(&mut self, point: (f32, f32, f32)) { + self.inner.put_center_on(Vec3::from(point)); + } + + fn put_start_and_end_on(&mut self, start: (f32, f32, f32), end: (f32, f32, f32)) { + self.inner + .put_start_and_end_on(Vec3::from(start), Vec3::from(end)); + } + + // 获取信息方法 + #[getter] + fn start_position(&self) -> Option<(f32, f32, f32)> { + self.inner.get_start_position().map(Into::into) + } + + #[getter] + fn end_position(&self) -> Option<(f32, f32, f32)> { + self.inner.get_end_position().map(Into::into) + } + + #[getter] + fn bounding_box(&self) -> Vec<(f32, f32, f32)> { + self.inner + .get_bounding_box() + .iter() + .map(|v| (*v).into()) + .collect() + } + + fn get_bounding_box_point(&self, edge: (i32, i32, i32)) -> (f32, f32, f32) { + self.inner.get_bounding_box_point(IVec3::from(edge)).into() + } + + fn get_bounding_box_corners(&self) -> Vec<(f32, f32, f32)> { + self.inner + .get_bounding_box() + .iter() + .map(|v| (*v).into()) + .collect() + } + } + }; +} + +impl_transformable! {PyVItem} +impl_transformable! {PySvgItem} diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs new file mode 100644 index 00000000..dff20bd2 --- /dev/null +++ b/packages/ranimpy/src/lib.rs @@ -0,0 +1,46 @@ +pub mod items; +pub mod timeline; + +use std::path::PathBuf; + +use pyo3::{ + pyfunction, pymodule, + types::{PyModule, PyModuleMethods}, + wrap_pyfunction, Bound, PyResult, +}; +use ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; +use timeline::PyTimeline; + +#[pyfunction] +fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { + let options = AppOptions { + output_dir, + ..Default::default() + }; + + let mut app = RanimRenderApp::new(&options); + let mut timeline = timeline.borrow_mut(); + if timeline.elapsed_secs() == 0.0 { + timeline.forward(0.1); + } + let duration_secs = timeline.elapsed_secs(); + app.render_anim( + AnimWithParams::new(timeline.inner.clone()) + .with_duration(duration_secs) + .with_rate_func(linear), + ); +} + +#[pymodule] +pub fn ranimpy(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(render_timeline, m)?)?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + // m.add_wrapped(wrap_pymodule!(items::items))?; + // let submodule = PyModule::new(py, "items")?; + // items::items(&submodule)?; + // m.add_submodule(&submodule)?; + Ok(()) +} diff --git a/packages/ranimpy/src/timeline.rs b/packages/ranimpy/src/timeline.rs new file mode 100644 index 00000000..abba16d6 --- /dev/null +++ b/packages/ranimpy/src/timeline.rs @@ -0,0 +1,35 @@ +use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny}; +use ranim::timeline::Timeline; + +use crate::items::{PySvgItem, PyVItem}; + +#[pyclass] +#[pyo3(name = "Timeline")] +#[derive(Debug)] +pub struct PyTimeline { + pub inner: Timeline, +} + +#[pymethods] +impl PyTimeline { + #[new] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + inner: Timeline::new(), + } + } + pub fn show(&mut self, rabject: &Bound<'_, PyAny>) { + if let Ok(vitem) = rabject.downcast::() { + self.inner.show(&vitem.borrow().inner); + } else if let Ok(svg_item) = rabject.downcast::() { + self.inner.show(&svg_item.borrow().inner); + } + } + pub fn forward(&mut self, secs: f32) { + self.inner.forward(secs); + } + pub fn elapsed_secs(&self) -> f32 { + self.inner.elapsed_secs() + } +} diff --git a/src/animation/entity/composition.rs b/src/animation/composition.rs similarity index 100% rename from src/animation/entity/composition.rs rename to src/animation/composition.rs diff --git a/src/animation/entity/creation.rs b/src/animation/creation.rs similarity index 94% rename from src/animation/entity/creation.rs rename to src/animation/creation.rs index 1d50c0af..b2a83872 100644 --- a/src/animation/entity/creation.rs +++ b/src/animation/creation.rs @@ -1,12 +1,8 @@ -use std::ops::Range; - -use bevy_color::Srgba; - -use crate::animation::AnimWithParams; +use super::{AnimWithParams, EntityAnim, PureEvaluator}; use crate::items::{Entity, Rabject}; use crate::prelude::Interpolatable; - -use crate::animation::entity::{EntityAnim, PureEvaluator}; +use color::{AlphaColor, Srgb}; +use std::ops::Range; pub fn create( rabject: &Rabject, @@ -183,18 +179,18 @@ pub trait Empty { pub trait Fill { fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self; - fn fill_color(&self) -> Srgba; - fn set_fill_color(&mut self, color: Srgba) -> &mut Self; + fn fill_color(&self) -> AlphaColor; + fn set_fill_color(&mut self, color: AlphaColor) -> &mut Self; } pub trait Stroke { fn set_stroke_width(&mut self, width: f32) -> &mut Self; - fn set_stroke_color(&mut self, color: Srgba) -> &mut Self; + fn set_stroke_color(&mut self, color: AlphaColor) -> &mut Self; fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self; } pub trait Color: Fill + Stroke { - fn set_color(&mut self, color: Srgba) -> &mut Self { + fn set_color(&mut self, color: AlphaColor) -> &mut Self { self.set_fill_color(color); self.set_stroke_color(color); self diff --git a/src/animation/entity.rs b/src/animation/entity.rs deleted file mode 100644 index e07aa78e..00000000 --- a/src/animation/entity.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! The EntityAnimation is applied to an entity -//! -pub mod composition; -pub mod creation; -pub mod fading; -pub mod freeze; -pub mod interpolate; - -use std::rc::Rc; - -use freeze::{freeze, Blank}; -use itertools::Itertools; - -use crate::{ - context::WgpuContext, - items::{Entity, Rabject}, - render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, - utils::PipelinesStorage, -}; - -use super::{Anim, AnimWithParams, Animator, StaticAnim}; - -pub struct EntityTimeline { - // pub(super) rabject_id: Id, - pub(super) cur_freeze_anim: StaticAnim, - pub(super) is_showing: bool, - pub(super) cur_anim_idx: Option, - pub(super) anims: Vec, - pub(super) end_secs: Vec, - pub(super) elapsed_secs: f32, -} - -impl EntityTimeline { - pub fn new(rabject: &Rabject) -> Self { - Self { - // rabject_id: rabject.id, - cur_freeze_anim: Rc::new(Box::new(freeze(rabject))), - cur_anim_idx: None, - is_showing: true, - anims: Vec::new(), - end_secs: Vec::new(), - elapsed_secs: 0.0, - } - } - fn push(&mut self, anim: AnimWithParams) { - let duration = anim.params.duration_secs; - self.anims.push(Box::new(anim)); - - let end_sec = self.end_secs.last().copied().unwrap_or(0.0) + duration; - self.end_secs.push(end_sec); - self.elapsed_secs += duration; - } - - /// Simply [`Self::append_freeze`] after used [`super::timeline::Timeline::show`], - /// and [`Self::append_blank`] after used [`super::timeline::Timeline::hide`]. - pub fn forward(&mut self, secs: f32) { - if self.is_showing { - self.append_freeze(secs); - } else { - self.append_blank(secs); - } - } - /// Append a freeze animation to the timeline - /// - /// A freeze animation just keeps the last frame of the previous animation - pub fn append_freeze(&mut self, secs: f32) { - self.push(AnimWithParams::new(self.cur_freeze_anim.clone()).with_duration(secs)) - } - /// Append a blank animation to the timeline - /// - /// A blank animation renders nothing - pub fn append_blank(&mut self, secs: f32) { - self.push(AnimWithParams::new(Blank).with_duration(secs)); - } - /// Append an animation to the timeline - pub fn append_anim( - &mut self, - mut anim: AnimWithParams>, - ) -> Rabject { - anim.update_alpha(1.0); - let end_rabject = anim.anim.rabject.clone(); - - self.cur_freeze_anim = Rc::new(Box::new(freeze(&end_rabject))); - self.push(anim); - end_rabject - } -} - -impl Animator for EntityTimeline { - fn update_alpha(&mut self, alpha: f32) { - // TODO: handle no anim - if self.anims.is_empty() { - return; - } - // trace!("update_alpha: {alpha}, {}", self.elapsed_secs); - let cur_sec = alpha * self.elapsed_secs; - let (idx, (anim, end_sec)) = self - .anims - .iter_mut() - .zip(self.end_secs.iter()) - .find_position(|(_, end_sec)| **end_sec >= cur_sec) - .unwrap(); - // trace!("{cur_sec}[{idx}] {:?}", self.end_secs); - self.cur_anim_idx = Some(idx); - - let start_sec = if idx > 0 { - self.end_secs.get(idx - 1).cloned() - } else { - None - } - .unwrap_or(0.0); - let alpha = (cur_sec - start_sec) / (end_sec - start_sec); - anim.update_alpha(alpha); - } -} - -impl Renderable for EntityTimeline { - fn render( - &self, - ctx: &WgpuContext, - render_instances: &mut RenderInstances, - pipelines: &mut PipelinesStorage, - encoder: &mut wgpu::CommandEncoder, - uniforms_bind_group: &wgpu::BindGroup, - render_textures: &RenderTextures, - camera: &CameraFrame, - ) { - if let Some(idx) = self.cur_anim_idx { - self.anims[idx].render( - ctx, - render_instances, - pipelines, - encoder, - uniforms_bind_group, - render_textures, - camera, - ); - } - } -} - -/// An animator that animates an entity -pub trait PureEvaluator { - fn eval_alpha(&self, alpha: f32) -> T; -} - -impl PureEvaluator for T { - fn eval_alpha(&self, _alpha: f32) -> T { - self.clone() - } -} -impl PureEvaluator for Rabject { - fn eval_alpha(&self, _alpha: f32) -> T { - self.data.clone() - } -} - -/// An animation that is applied to an entity -/// -/// This type implements [`Animator`] and [`Renderable`] -#[derive(Clone)] -pub struct EntityAnim { - rabject: Rabject, - evaluator: Rc>>, -} - -impl Animator for EntityAnim { - fn update_alpha(&mut self, alpha: f32) { - self.rabject.data = self.evaluator.eval_alpha(alpha); - } -} - -impl Renderable for EntityAnim { - fn render( - &self, - ctx: &WgpuContext, - render_instances: &mut RenderInstances, - pipelines: &mut PipelinesStorage, - encoder: &mut wgpu::CommandEncoder, - uniforms_bind_group: &wgpu::BindGroup, - render_textures: &RenderTextures, - camera: &CameraFrame, - ) { - self.rabject.render( - ctx, - render_instances, - pipelines, - encoder, - uniforms_bind_group, - render_textures, - camera, - ); - } -} - -impl EntityAnim { - pub fn new(rabject: Rabject, func: impl PureEvaluator + 'static) -> Self { - Self { - rabject, - evaluator: Rc::new(Box::new(func)), - } - } - pub fn rabject(&self) -> &Rabject { - &self.rabject - } -} diff --git a/src/animation/entity/fading.rs b/src/animation/fading.rs similarity index 94% rename from src/animation/entity/fading.rs rename to src/animation/fading.rs index ac1f9b36..0b1b3363 100644 --- a/src/animation/entity/fading.rs +++ b/src/animation/fading.rs @@ -1,9 +1,7 @@ use crate::items::{Entity, Rabject}; use crate::prelude::Interpolatable; -use crate::animation::entity::{EntityAnim, PureEvaluator}; - -use crate::animation::AnimWithParams; +use super::{AnimWithParams, EntityAnim, PureEvaluator}; pub fn fade_in( rabject: &Rabject, diff --git a/src/animation/entity/freeze.rs b/src/animation/freeze.rs similarity index 84% rename from src/animation/entity/freeze.rs rename to src/animation/freeze.rs index 3d5acbdb..8be32f44 100644 --- a/src/animation/entity/freeze.rs +++ b/src/animation/freeze.rs @@ -1,19 +1,14 @@ +use super::{AnimWithParams, Animator, EntityAnim}; use crate::{ - animation::Animator, items::{Entity, Rabject}, - render::RenderTextures, + render::{RenderTextures, Renderable}, }; -use super::EntityAnim; -use crate::animation::AnimWithParams; - pub fn freeze(rabject: &Rabject) -> AnimWithParams> { let data = rabject.data.clone(); AnimWithParams::new(EntityAnim::new(rabject.clone(), data)) } -use crate::render::Renderable; - pub struct Blank; impl Renderable for Blank { diff --git a/src/animation/entity/interpolate.rs b/src/animation/interpolate.rs similarity index 90% rename from src/animation/entity/interpolate.rs rename to src/animation/interpolate.rs index e9712412..b62341d2 100644 --- a/src/animation/entity/interpolate.rs +++ b/src/animation/interpolate.rs @@ -1,9 +1,7 @@ -use crate::items::ConvertIntoRabject; -use crate::{interpolate::Interpolatable, items::Entity}; - -use crate::animation::{ - entity::{EntityAnim, PureEvaluator, Rabject}, - AnimWithParams, +use super::{AnimWithParams, EntityAnim, PureEvaluator, Rabject}; +use crate::{ + interpolate::Interpolatable, + items::{ConvertIntoRabject, Entity}, }; pub fn interpolate>( diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 0ae7b4fe..bcd306db 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -1,10 +1,14 @@ -pub mod entity; -pub mod timeline; +pub mod composition; +pub mod creation; +pub mod fading; +pub mod freeze; +pub mod interpolate; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; use crate::{ context::WgpuContext, + items::{Entity, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{rate_functions::smooth, PipelinesStorage}, }; @@ -18,11 +22,11 @@ pub trait Animator: Renderable { } /// An `Anim` is a box of [`Animator`] -pub type Anim = Box; +pub type Anim = Arc>>; /// An `StaticAnim` is a box of [`Renderable`] inside a `Rc` /// /// This implements [`Animator`] but does nothing on `update_alpha` -pub type StaticAnim = Rc>; +pub type StaticAnim = Arc>; impl Renderable for StaticAnim { fn render( @@ -52,6 +56,76 @@ impl Animator for StaticAnim { } } +/// An animator that animates an entity +pub trait PureEvaluator: Send + Sync { + fn eval_alpha(&self, alpha: f32) -> T; +} + +impl PureEvaluator for T { + fn eval_alpha(&self, _alpha: f32) -> T { + self.clone() + } +} +impl PureEvaluator for Rabject { + fn eval_alpha(&self, _alpha: f32) -> T { + self.data.clone() + } +} + +// MARK: EntityAnim + +/// An animation that is applied to an entity +/// +/// This type implements [`Animator`] and [`Renderable`] +#[derive(Clone)] +pub struct EntityAnim { + pub(crate) rabject: Rabject, + evaluator: Arc>>, +} + +impl Animator for EntityAnim { + fn update_alpha(&mut self, alpha: f32) { + self.rabject.data = self.evaluator.eval_alpha(alpha); + } +} + +impl Renderable for EntityAnim { + fn render( + &self, + ctx: &WgpuContext, + render_instances: &mut RenderInstances, + pipelines: &mut PipelinesStorage, + encoder: &mut wgpu::CommandEncoder, + uniforms_bind_group: &wgpu::BindGroup, + render_textures: &RenderTextures, + camera: &CameraFrame, + ) { + self.rabject.render( + ctx, + render_instances, + pipelines, + encoder, + uniforms_bind_group, + render_textures, + camera, + ); + } +} + +impl EntityAnim { + pub fn new(rabject: Rabject, func: impl PureEvaluator + 'static) -> Self { + Self { + rabject, + evaluator: Arc::new(Box::new(func)), + } + } + pub fn rabject(&self) -> &Rabject { + &self.rabject + } +} + +// MARK: AnimParams + /// The param of an animation #[derive(Debug, Clone)] pub struct AnimParams { diff --git a/src/color/mod.rs b/src/color/mod.rs index 93b08590..2828e566 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -1,3 +1,35 @@ -pub use bevy_color::*; +use color::{AlphaColor, OpaqueColor, Srgb}; pub mod palettes; +pub use ::color::HueDirection; + +pub mod prelude { + pub use super::{rgb, rgb8, rgba, rgba8}; + pub use crate::color; +} + +pub const fn rgb8(r: u8, g: u8, b: u8) -> AlphaColor { + OpaqueColor::from_rgb8(r, g, b).with_alpha(1.0) +} + +pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> AlphaColor { + AlphaColor::from_rgba8(r, g, b, a) +} + +pub const fn rgb(r: f32, g: f32, b: f32) -> AlphaColor { + OpaqueColor::new([r, g, b]).with_alpha(1.0) +} + +pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> AlphaColor { + AlphaColor::new([r, g, b, a]) +} + +#[macro_export] +macro_rules! color { + ($color_str:expr) => {{ + use ::color::{parse_color, Srgb}; + parse_color($color_str) + .expect("Invalid color string") + .to_alpha_color::() + }}; +} diff --git a/src/color/palettes.rs b/src/color/palettes.rs index 178f285c..362b67e8 100644 --- a/src/color/palettes.rs +++ b/src/color/palettes.rs @@ -1,129 +1,131 @@ -pub use bevy_color::palettes::*; +pub use color::palette::css; pub mod manim { - use bevy_color::Srgba; + use color::{AlphaColor, Srgb}; + + use crate::color::rgb; /// #1C758A ///
- pub const BLUE_E: Srgba = Srgba::rgb(0.11, 0.46, 0.54); + pub const BLUE_E: AlphaColor = rgb(0.11, 0.46, 0.54); /// #29ABCA ///
- pub const BLUE_D: Srgba = Srgba::rgb(0.16, 0.67, 0.79); + pub const BLUE_D: AlphaColor = rgb(0.16, 0.67, 0.79); /// #58C4DD ///
- pub const BLUE_C: Srgba = Srgba::rgb(0.35, 0.77, 0.87); + pub const BLUE_C: AlphaColor = rgb(0.35, 0.77, 0.87); /// #9CDCEB ///
- pub const BLUE_B: Srgba = Srgba::rgb(0.61, 0.86, 0.92); + pub const BLUE_B: AlphaColor = rgb(0.61, 0.86, 0.92); /// #C7E9F1 ///
- pub const BLUE_A: Srgba = Srgba::rgb(0.78, 0.91, 0.95); + pub const BLUE_A: AlphaColor = rgb(0.78, 0.91, 0.95); ///
- pub const TEAL_E: Srgba = Srgba::rgb(0.29, 0.66, 0.56); + pub const TEAL_E: AlphaColor = rgb(0.29, 0.66, 0.56); ///
- pub const TEAL_D: Srgba = Srgba::rgb(0.33, 0.76, 0.66); + pub const TEAL_D: AlphaColor = rgb(0.33, 0.76, 0.66); ///
- pub const TEAL_C: Srgba = Srgba::rgb(0.36, 0.82, 0.70); + pub const TEAL_C: AlphaColor = rgb(0.36, 0.82, 0.70); ///
- pub const TEAL_B: Srgba = Srgba::rgb(0.46, 0.87, 0.75); + pub const TEAL_B: AlphaColor = rgb(0.46, 0.87, 0.75); ///
- pub const TEAL_A: Srgba = Srgba::rgb(0.68, 0.92, 0.84); + pub const TEAL_A: AlphaColor = rgb(0.68, 0.92, 0.84); ///
- pub const GREEN_E: Srgba = Srgba::rgb(0.41, 0.61, 0.32); + pub const GREEN_E: AlphaColor = rgb(0.41, 0.61, 0.32); ///
- pub const GREEN_D: Srgba = Srgba::rgb(0.47, 0.69, 0.37); + pub const GREEN_D: AlphaColor = rgb(0.47, 0.69, 0.37); ///
- pub const GREEN_C: Srgba = Srgba::rgb(0.51, 0.76, 0.40); + pub const GREEN_C: AlphaColor = rgb(0.51, 0.76, 0.40); ///
- pub const GREEN_B: Srgba = Srgba::rgb(0.65, 0.81, 0.55); + pub const GREEN_B: AlphaColor = rgb(0.65, 0.81, 0.55); ///
- pub const GREEN_A: Srgba = Srgba::rgb(0.79, 0.89, 0.68); + pub const GREEN_A: AlphaColor = rgb(0.79, 0.89, 0.68); ///
- pub const YELLOW_E: Srgba = Srgba::rgb(0.91, 0.76, 0.11); + pub const YELLOW_E: AlphaColor = rgb(0.91, 0.76, 0.11); ///
- pub const YELLOW_D: Srgba = Srgba::rgb(0.96, 0.83, 0.27); + pub const YELLOW_D: AlphaColor = rgb(0.96, 0.83, 0.27); ///
- pub const YELLOW_C: Srgba = Srgba::rgb(1.00, 1.00, 0.00); + pub const YELLOW_C: AlphaColor = rgb(1.00, 1.00, 0.00); ///
- pub const YELLOW_B: Srgba = Srgba::rgb(1.00, 0.92, 0.58); + pub const YELLOW_B: AlphaColor = rgb(1.00, 0.92, 0.58); ///
- pub const YELLOW_A: Srgba = Srgba::rgb(1.00, 0.95, 0.71); + pub const YELLOW_A: AlphaColor = rgb(1.00, 0.95, 0.71); ///
- pub const GOLD_E: Srgba = Srgba::rgb(0.78, 0.55, 0.28); + pub const GOLD_E: AlphaColor = rgb(0.78, 0.55, 0.28); ///
- pub const GOLD_D: Srgba = Srgba::rgb(0.88, 0.63, 0.35); + pub const GOLD_D: AlphaColor = rgb(0.88, 0.63, 0.35); ///
- pub const GOLD_C: Srgba = Srgba::rgb(0.94, 0.68, 0.37); + pub const GOLD_C: AlphaColor = rgb(0.94, 0.68, 0.37); ///
- pub const GOLD_B: Srgba = Srgba::rgb(0.98, 0.72, 0.46); + pub const GOLD_B: AlphaColor = rgb(0.98, 0.72, 0.46); ///
- pub const GOLD_A: Srgba = Srgba::rgb(0.97, 0.78, 0.59); + pub const GOLD_A: AlphaColor = rgb(0.97, 0.78, 0.59); ///
- pub const RED_E: Srgba = Srgba::rgb(0.81, 0.31, 0.27); + pub const RED_E: AlphaColor = rgb(0.81, 0.31, 0.27); ///
- pub const RED_D: Srgba = Srgba::rgb(0.90, 0.35, 0.30); + pub const RED_D: AlphaColor = rgb(0.90, 0.35, 0.30); ///
- pub const RED_C: Srgba = Srgba::rgb(0.99, 0.38, 0.33); + pub const RED_C: AlphaColor = rgb(0.99, 0.38, 0.33); ///
- pub const RED_B: Srgba = Srgba::rgb(1.00, 0.50, 0.50); + pub const RED_B: AlphaColor = rgb(1.00, 0.50, 0.50); ///
- pub const RED_A: Srgba = Srgba::rgb(0.97, 0.63, 0.64); + pub const RED_A: AlphaColor = rgb(0.97, 0.63, 0.64); ///
- pub const MAROON_E: Srgba = Srgba::rgb(0.58, 0.26, 0.31); + pub const MAROON_E: AlphaColor = rgb(0.58, 0.26, 0.31); ///
- pub const MAROON_D: Srgba = Srgba::rgb(0.64, 0.30, 0.38); + pub const MAROON_D: AlphaColor = rgb(0.64, 0.30, 0.38); ///
- pub const MAROON_C: Srgba = Srgba::rgb(0.77, 0.37, 0.45); + pub const MAROON_C: AlphaColor = rgb(0.77, 0.37, 0.45); ///
- pub const MAROON_B: Srgba = Srgba::rgb(0.92, 0.57, 0.67); + pub const MAROON_B: AlphaColor = rgb(0.92, 0.57, 0.67); ///
- pub const MAROON_A: Srgba = Srgba::rgb(0.93, 0.67, 0.76); + pub const MAROON_A: AlphaColor = rgb(0.93, 0.67, 0.76); ///
- pub const PURPLE_E: Srgba = Srgba::rgb(0.39, 0.26, 0.45); + pub const PURPLE_E: AlphaColor = rgb(0.39, 0.26, 0.45); ///
- pub const PURPLE_D: Srgba = Srgba::rgb(0.44, 0.33, 0.51); + pub const PURPLE_D: AlphaColor = rgb(0.44, 0.33, 0.51); ///
- pub const PURPLE_C: Srgba = Srgba::rgb(0.60, 0.45, 0.68); + pub const PURPLE_C: AlphaColor = rgb(0.60, 0.45, 0.68); ///
- pub const PURPLE_B: Srgba = Srgba::rgb(0.69, 0.54, 0.78); + pub const PURPLE_B: AlphaColor = rgb(0.69, 0.54, 0.78); ///
- pub const PURPLE_A: Srgba = Srgba::rgb(0.79, 0.64, 0.91); + pub const PURPLE_A: AlphaColor = rgb(0.79, 0.64, 0.91); ///
- pub const GREY_E: Srgba = Srgba::rgb(0.13, 0.13, 0.13); + pub const GREY_E: AlphaColor = rgb(0.13, 0.13, 0.13); ///
- pub const GREY_D: Srgba = Srgba::rgb(0.27, 0.27, 0.27); + pub const GREY_D: AlphaColor = rgb(0.27, 0.27, 0.27); ///
- pub const GREY_C: Srgba = Srgba::rgb(0.53, 0.53, 0.53); + pub const GREY_C: AlphaColor = rgb(0.53, 0.53, 0.53); ///
- pub const GREY_B: Srgba = Srgba::rgb(0.73, 0.73, 0.73); + pub const GREY_B: AlphaColor = rgb(0.73, 0.73, 0.73); ///
- pub const GREY_A: Srgba = Srgba::rgb(0.87, 0.87, 0.87); + pub const GREY_A: AlphaColor = rgb(0.87, 0.87, 0.87); ///
- pub const WHITE: Srgba = Srgba::rgb(1.00, 1.00, 1.00); + pub const WHITE: AlphaColor = rgb(1.00, 1.00, 1.00); ///
- pub const BLACK: Srgba = Srgba::rgb(0.00, 0.00, 0.00); + pub const BLACK: AlphaColor = rgb(0.00, 0.00, 0.00); ///
- pub const GREEN_SCREEN: Srgba = Srgba::rgb(0.00, 1.00, 0.00); + pub const GREEN_SCREEN: AlphaColor = rgb(0.00, 1.00, 0.00); ///
- pub const GREY_BROWN: Srgba = Srgba::rgb(0.45, 0.39, 0.34); + pub const GREY_BROWN: AlphaColor = rgb(0.45, 0.39, 0.34); ///
- pub const LIGHT_BROWN: Srgba = Srgba::rgb(0.80, 0.52, 0.25); + pub const LIGHT_BROWN: AlphaColor = rgb(0.80, 0.52, 0.25); ///
- pub const PINK: Srgba = Srgba::rgb(0.82, 0.28, 0.74); + pub const PINK: AlphaColor = rgb(0.82, 0.28, 0.74); ///
- pub const LIGHT_PINK: Srgba = Srgba::rgb(0.86, 0.46, 0.80); + pub const LIGHT_PINK: AlphaColor = rgb(0.86, 0.46, 0.80); ///
- pub const ORANGE: Srgba = Srgba::rgb(1.00, 0.53, 0.18); + pub const ORANGE: AlphaColor = rgb(1.00, 0.53, 0.18); } diff --git a/src/components/rgba.rs b/src/components/rgba.rs index d4a3d7dd..b9e19661 100644 --- a/src/components/rgba.rs +++ b/src/components/rgba.rs @@ -1,6 +1,7 @@ use std::ops::{Deref, DerefMut}; -use bevy_color::{ColorToComponents, LinearRgba}; +// use bevy_color::{ColorToComponents, LinearRgba}; +use color::{AlphaColor, ColorSpace, LinearSrgb, Srgb}; use glam::{vec4, Vec4}; use crate::prelude::{Interpolatable, Opacity}; @@ -29,13 +30,27 @@ impl Opacity for ComponentVec { } } -impl From for Rgba { - fn from(value: bevy_color::Srgba) -> Self { - let rgba = LinearRgba::from(value); - Self(rgba.to_vec4()) +impl From> for Rgba { + fn from(value: AlphaColor) -> Self { + let rgba = value.convert::().components; + Self(Vec4::from_array(rgba)) } } +impl From for AlphaColor { + fn from(value: Rgba) -> AlphaColor { + let linear_rgba = value.0.to_array(); + AlphaColor::::new(linear_rgba).convert() + } +} + +// impl From for Rgba { +// fn from(value: bevy_color::Srgba) -> Self { +// let rgba = LinearRgba::from(value); +// Self(rgba.to_vec4()) +// } +// } + impl Default for Rgba { fn default() -> Self { vec4(1.0, 0.0, 0.0, 1.0).into() @@ -106,3 +121,23 @@ impl Interpolatable for Rgba { // partial.into() // } // } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_convertion() { + let approx = |a: f32, b: f32| (a - b).abs() < 0.001; + // The `rgb8` and `rgba8` should be in srgb + let color = AlphaColor::from_rgb8(85, 133, 217); + assert!(approx(color.components[0], 0.333)); + assert!(approx(color.components[1], 0.522)); + assert!(approx(color.components[2], 0.851)); + + let linear_rgba = Rgba::from(color); + assert!(approx(linear_rgba.x, 0.091)); + assert!(approx(linear_rgba.y, 0.235)); + assert!(approx(linear_rgba.z, 0.694)); + } +} diff --git a/src/interpolate.rs b/src/interpolate.rs index 491681fd..f7d47065 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -1,4 +1,4 @@ -use bevy_color::{LinearRgba, Srgba}; +use color::{AlphaColor, ColorSpace}; pub trait Interpolatable { fn lerp(&self, target: &Self, t: f32) -> Self; @@ -10,24 +10,9 @@ impl Interpolatable for f32 { } } -impl Interpolatable for LinearRgba { - fn lerp(&self, target: &Self, t: f32) -> Self { - Self { - red: self.red.lerp(&target.red, t), - green: self.green.lerp(&target.green, t), - blue: self.blue.lerp(&target.blue, t), - alpha: self.alpha.lerp(&target.alpha, t), - } - } -} - -impl Interpolatable for Srgba { +impl Interpolatable for AlphaColor { fn lerp(&self, other: &Self, t: f32) -> Self { - Self { - red: self.red.lerp(&other.red, t), - green: self.green.lerp(&other.green, t), - blue: self.blue.lerp(&other.blue, t), - alpha: self.alpha.lerp(&other.alpha, t), - } + // TODO: figure out to use `lerp_rect` or `lerp` + AlphaColor::lerp_rect(*self, *other, t) } } diff --git a/src/items/mod.rs b/src/items/mod.rs index 10a00503..8b924321 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -93,7 +93,7 @@ impl> ConvertIntoRabject for Rabject { } } -pub trait Entity: Clone + Empty { +pub trait Entity: Clone + Empty + Send + Sync { type Primitive: Extract + Default; #[allow(unused)] diff --git a/src/items/svg_item.rs b/src/items/svg_item.rs index 7de4131f..2f316f61 100644 --- a/src/items/svg_item.rs +++ b/src/items/svg_item.rs @@ -1,10 +1,11 @@ use std::{cmp::Ordering, f32, path::Path, slice::Iter, vec}; -use bevy_color::{Alpha, Srgba}; +use color::{palette::css, AlphaColor, Srgb}; use glam::{vec2, vec3, Affine2, Vec3}; use log::warn; use crate::{ + color::{rgb8, rgba}, components::{vpoint::VPoint, TransformAnchor}, prelude::{Alignable, Empty, Fill, Interpolatable, Opacity, Partial, Stroke, Transformable}, render::primitives::{svg_item::SvgItemPrimitive, Extract}, @@ -118,24 +119,14 @@ impl SvgItem { let color = parse_paint(fill.paint()).with_alpha(fill.opacity().get()); vitem.set_fill_color(color); } else { - vitem.set_fill_color(bevy_color::Srgba { - red: 0.0, - green: 0.0, - blue: 0.0, - alpha: 0.0, - }); + vitem.set_fill_color(rgba(0.0, 0.0, 0.0, 0.0)); } if let Some(stroke) = path.stroke() { let color = parse_paint(stroke.paint()).with_alpha(stroke.opacity().get()); vitem.set_stroke_color(color); vitem.set_stroke_width(stroke.width().get()); } else { - vitem.set_stroke_color(bevy_color::Srgba { - red: 0.0, - green: 0.0, - blue: 0.0, - alpha: 0.0, - }); + vitem.set_stroke_color(rgba(0.0, 0.0, 0.0, 0.0)); } vitems.push(vitem); } @@ -247,7 +238,7 @@ impl Extract for SvgItemPrimitive { // MARK: Animation impl impl Stroke for SvgItem { - fn set_stroke_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_stroke_color(&mut self, color: AlphaColor) -> &mut Self { self.vitems.iter_mut().for_each(|vitem| { vitem.set_stroke_color(color); }); @@ -268,13 +259,13 @@ impl Stroke for SvgItem { } impl Fill for SvgItem { - fn fill_color(&self) -> bevy_color::Srgba { + fn fill_color(&self) -> AlphaColor { self.vitems .first() .map(|vitem| vitem.fill_color()) - .unwrap_or(Srgba::WHITE) + .unwrap_or(css::WHITE) } - fn set_fill_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_fill_color(&mut self, color: AlphaColor) -> &mut Self { self.vitems.iter_mut().for_each(|vitem| { vitem.set_fill_color(color); }); @@ -378,17 +369,10 @@ impl Partial for SvgItem { } // MARK: misc -fn parse_paint(paint: &usvg::Paint) -> bevy_color::Srgba { +fn parse_paint(paint: &usvg::Paint) -> AlphaColor { match paint { - usvg::Paint::Color(color) => { - bevy_color::Color::srgb_u8(color.red, color.green, color.blue).to_srgba() - } - _ => bevy_color::Srgba { - red: 0.0, - green: 1.0, - blue: 0.0, - alpha: 1.0, - }, + usvg::Paint::Color(color) => rgb8(color.red, color.green, color.blue), + _ => css::GREEN, } } diff --git a/src/items/vitem.rs b/src/items/vitem.rs index 1f2b53df..161e77a1 100644 --- a/src/items/vitem.rs +++ b/src/items/vitem.rs @@ -1,4 +1,4 @@ -use bevy_color::{ColorToComponents, Srgba}; +use color::{palette::css, AlphaColor, Srgb}; use glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4, Vec4Swizzles}; use itertools::Itertools; @@ -185,13 +185,13 @@ impl Empty for VItem { } impl Fill for VItem { - fn fill_color(&self) -> bevy_color::Srgba { + fn fill_color(&self) -> AlphaColor { self.fill_rgbas .first() - .map(|&rgba| Srgba::from_vec4(*rgba)) - .unwrap_or(Srgba::WHITE) + .map(|&rgba| rgba.into()) + .unwrap_or(css::WHITE) } - fn set_fill_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_fill_color(&mut self, color: AlphaColor) -> &mut Self { self.fill_rgbas.set_all(color); self } @@ -202,7 +202,7 @@ impl Fill for VItem { } impl Stroke for VItem { - fn set_stroke_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_stroke_color(&mut self, color: AlphaColor) -> &mut Self { self.stroke_rgbas.set_all(color); self } diff --git a/src/lib.rs b/src/lib.rs index 209c3443..df85549f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,22 +6,25 @@ use std::{ time::Duration, }; -use animation::{timeline::Timeline, AnimWithParams, Animator}; +use animation::{AnimWithParams, Animator}; use context::RanimContext; use file_writer::{FileWriter, FileWriterBuilder}; pub use glam; use image::{ImageBuffer, Rgba}; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; use log::info; +use timeline::Timeline; + use render::{CameraFrame, Renderable, Renderer}; use utils::rate_functions::linear; pub mod prelude { + pub use crate::color::prelude::*; pub use crate::interpolate::Interpolatable; - pub use crate::animation::entity::creation::{Empty, Fill, Partial, Stroke}; - pub use crate::animation::entity::fading::Opacity; - pub use crate::animation::entity::interpolate::Alignable; + pub use crate::animation::creation::{Empty, Fill, Partial, Stroke}; + pub use crate::animation::fading::Opacity; + pub use crate::animation::interpolate::Alignable; pub use crate::items::Blueprint; pub use crate::RenderScene; @@ -32,6 +35,7 @@ pub mod prelude { pub mod color; mod file_writer; mod interpolate; +pub mod timeline; pub mod updater; pub mod animation; @@ -113,8 +117,6 @@ impl RenderScene for T { pub struct RanimRenderApp { ctx: RanimContext, - // world: World, - // anim: Box, renderer: Renderer, camera_frame: CameraFrame, diff --git a/src/render/mod.rs b/src/render/mod.rs index fa679daf..ddfa9993 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,16 +1,17 @@ pub mod pipelines; pub mod primitives; -use bevy_color::Color; +use color::LinearSrgb; use glam::{vec2, Mat4, Vec2, Vec3}; use primitives::RenderInstances; use crate::{ + color::rgba8, context::{RanimContext, WgpuContext}, utils::{wgpu::WgpuBuffer, PipelinesStorage}, }; -pub trait Renderable { +pub trait Renderable: Send + Sync { #[allow(clippy::too_many_arguments)] fn render( &self, @@ -83,6 +84,7 @@ impl CameraUniformsBindGroup { } } +/// A renderer for anything implementes [`Renderable`] pub struct Renderer { size: (usize, usize), camera: CameraFrame, @@ -217,13 +219,9 @@ impl Renderer { ); let uniforms_bind_group = CameraUniformsBindGroup::new(ctx, &uniforms_buffer); - let bg = Color::srgba_u8(0x33, 0x33, 0x33, 0xff).to_linear(); - let clear_color = wgpu::Color { - r: bg.red as f64, - g: bg.green as f64, - b: bg.blue as f64, - a: bg.alpha as f64, - }; + let bg = rgba8(0x33, 0x33, 0x33, 0xff).convert::(); + let [r, g, b, a] = bg.components.map(|x| x as f64); + let clear_color = wgpu::Color { r, g, b, a }; Self { camera, @@ -520,11 +518,11 @@ mod test { use crate::{ animation::{ - entity::creation::{create, uncreate}, - timeline::Timeline, + creation::{create, uncreate}, AnimWithParams, }, items::{vitem::Polygon, Blueprint}, + timeline::Timeline, utils::rate_functions::linear, AppOptions, RanimRenderApp, }; diff --git a/src/animation/timeline.rs b/src/timeline.rs similarity index 60% rename from src/animation/timeline.rs rename to src/timeline.rs index 2574aa84..d791c651 100644 --- a/src/animation/timeline.rs +++ b/src/timeline.rs @@ -1,23 +1,25 @@ -use std::{collections::HashMap, fmt::Debug, time::Duration}; - use crate::{ + animation::{ + freeze::{freeze, Blank}, + Anim, AnimWithParams, Animator, EntityAnim, StaticAnim, + }, context::WgpuContext, items::{Entity, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{Id, PipelinesStorage}, }; +use itertools::Itertools; +use std::sync::{Arc, Mutex}; +use std::{collections::HashMap, fmt::Debug, time::Duration}; -use super::{ - entity::{EntityAnim, EntityTimeline}, - AnimWithParams, Animator, -}; +// MARK: Timeline /// Timeline of all rabjects /// /// The Timeline itself is also an [`Animator`] which: /// - update all RabjectTimeline's alpha /// - render all RabjectTimeline -#[derive(Default)] +#[derive(Default, Clone)] pub struct Timeline { /// Rabject's Id -> EntityTimeline rabject_timelines: HashMap, @@ -196,3 +198,125 @@ impl Animator for Timeline { } } } + +// MARK: EntityTimeline + +#[derive(Clone)] +pub struct EntityTimeline { + // pub(super) rabject_id: Id, + pub(super) cur_freeze_anim: StaticAnim, + pub(super) is_showing: bool, + pub(super) cur_anim_idx: Option, + pub(super) anims: Vec, + pub(super) end_secs: Vec, + pub(super) elapsed_secs: f32, +} + +impl EntityTimeline { + pub fn new(rabject: &Rabject) -> Self { + Self { + // rabject_id: rabject.id, + cur_freeze_anim: Arc::new(Box::new(freeze(rabject))), + cur_anim_idx: None, + is_showing: true, + anims: Vec::new(), + end_secs: Vec::new(), + elapsed_secs: 0.0, + } + } + fn push(&mut self, anim: AnimWithParams) { + let duration = anim.params.duration_secs; + self.anims.push(Arc::new(Mutex::new(Box::new(anim)))); + + let end_sec = self.end_secs.last().copied().unwrap_or(0.0) + duration; + self.end_secs.push(end_sec); + self.elapsed_secs += duration; + } + + /// Simply [`Self::append_freeze`] after used [`super::timeline::Timeline::show`], + /// and [`Self::append_blank`] after used [`super::timeline::Timeline::hide`]. + pub fn forward(&mut self, secs: f32) { + if self.is_showing { + self.append_freeze(secs); + } else { + self.append_blank(secs); + } + } + /// Append a freeze animation to the timeline + /// + /// A freeze animation just keeps the last frame of the previous animation + pub fn append_freeze(&mut self, secs: f32) { + self.push(AnimWithParams::new(self.cur_freeze_anim.clone()).with_duration(secs)) + } + /// Append a blank animation to the timeline + /// + /// A blank animation renders nothing + pub fn append_blank(&mut self, secs: f32) { + self.push(AnimWithParams::new(Blank).with_duration(secs)); + } + /// Append an animation to the timeline + pub fn append_anim( + &mut self, + mut anim: AnimWithParams>, + ) -> Rabject { + anim.update_alpha(1.0); + let end_rabject = anim.anim.rabject.clone(); + + self.cur_freeze_anim = Arc::new(Box::new(freeze(&end_rabject))); + self.push(anim); + end_rabject + } +} + +impl Animator for EntityTimeline { + fn update_alpha(&mut self, alpha: f32) { + // TODO: handle no anim + if self.anims.is_empty() { + return; + } + // trace!("update_alpha: {alpha}, {}", self.elapsed_secs); + let cur_sec = alpha * self.elapsed_secs; + let (idx, (anim, end_sec)) = self + .anims + .iter_mut() + .zip(self.end_secs.iter()) + .find_position(|(_, end_sec)| **end_sec >= cur_sec) + .unwrap(); + // trace!("{cur_sec}[{idx}] {:?}", self.end_secs); + self.cur_anim_idx = Some(idx); + + let start_sec = if idx > 0 { + self.end_secs.get(idx - 1).cloned() + } else { + None + } + .unwrap_or(0.0); + let alpha = (cur_sec - start_sec) / (end_sec - start_sec); + anim.lock().unwrap().update_alpha(alpha); + } +} + +impl Renderable for EntityTimeline { + fn render( + &self, + ctx: &WgpuContext, + render_instances: &mut RenderInstances, + pipelines: &mut PipelinesStorage, + encoder: &mut wgpu::CommandEncoder, + uniforms_bind_group: &wgpu::BindGroup, + render_textures: &RenderTextures, + camera: &CameraFrame, + ) { + if let Some(idx) = self.cur_anim_idx { + self.anims[idx].lock().unwrap().render( + ctx, + render_instances, + pipelines, + encoder, + uniforms_bind_group, + render_textures, + camera, + ); + } + } +} diff --git a/xtasks/xtask-build-examples/Cargo.toml b/xtasks/xtask-build-examples/Cargo.toml new file mode 100644 index 00000000..095719c1 --- /dev/null +++ b/xtasks/xtask-build-examples/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "xtask-build-examples" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/xtasks/xtask-build-examples/src/main.rs b/xtasks/xtask-build-examples/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/xtasks/xtask-build-examples/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}