Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/bevy_math/.cargo-checksum.json vendored Normal file

File diff suppressed because one or more lines are too long

823
vendor/bevy_math/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,823 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[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 = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bevy_macro_utils"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052eeebcb8e7e072beea5031b227d9a290f8a7fbbb947573ab6ec81df0fb94be"
dependencies = [
"parking_lot",
"proc-macro2",
"quote",
"syn",
"toml_edit",
]
[[package]]
name = "bevy_math"
version = "0.16.1"
dependencies = [
"approx",
"bevy_reflect",
"derive_more",
"glam",
"itertools",
"libm",
"rand",
"rand_chacha",
"rand_distr",
"serde",
"smallvec",
"thiserror",
"variadics_please",
]
[[package]]
name = "bevy_platform"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7573dc824a1b08b4c93fdbe421c53e1e8188e9ca1dd74a414455fe571facb47"
dependencies = [
"cfg-if",
"critical-section",
"foldhash",
"hashbrown",
"portable-atomic",
"portable-atomic-util",
"serde",
"spin",
]
[[package]]
name = "bevy_ptr"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df7370d0e46b60e071917711d0860721f5347bc958bf325975ae6913a5dfcf01"
[[package]]
name = "bevy_reflect"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daeb91a63a1a4df00aa58da8cc4ddbd4b9f16ab8bb647c5553eb156ce36fa8c2"
dependencies = [
"assert_type_match",
"bevy_platform",
"bevy_ptr",
"bevy_reflect_derive",
"bevy_utils",
"derive_more",
"disqualified",
"downcast-rs",
"erased-serde",
"foldhash",
"glam",
"serde",
"smol_str",
"thiserror",
"uuid",
"variadics_please",
"wgpu-types",
]
[[package]]
name = "bevy_reflect_derive"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ddadc55fe16b45faaa54ab2f9cb00548013c74812e8b018aa172387103cce6"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
"quote",
"syn",
"uuid",
]
[[package]]
name = "bevy_utils"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94f7a8905a125d2017e8561beefb7f2f5e67e93ff6324f072ad87c5fd6ec3b99"
dependencies = [
"bevy_platform",
"thread_local",
]
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
dependencies = [
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[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",
]
[[package]]
name = "disqualified"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd"
[[package]]
name = "downcast-rs"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "glam"
version = "0.29.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
dependencies = [
"approx",
"bytemuck",
"libm",
"mint",
"rand",
"serde",
]
[[package]]
name = "hashbrown"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
dependencies = [
"equivalent",
"serde",
]
[[package]]
name = "indexmap"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mint"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.16",
]
[[package]]
name = "rand_distr"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
dependencies = [
"num-traits",
"rand",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "smol_str"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
dependencies = [
"serde",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"portable-atomic",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"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 = "toml_datetime"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
[[package]]
name = "toml_edit"
version = "0.22.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "uuid"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
dependencies = [
"getrandom 0.3.3",
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "variadics_please"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wgpu-types"
version = "24.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c"
dependencies = [
"bitflags",
"js-sys",
"log",
"serde",
"web-sys",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "zerocopy"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

200
vendor/bevy_math/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,200 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
rust-version = "1.85.0"
name = "bevy_math"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Provides math functionality for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
alloc = [
"itertools/use_alloc",
"serde?/alloc",
"rand?/alloc",
"rand_distr?/alloc",
]
approx = [
"dep:approx",
"glam/approx",
]
bevy_reflect = [
"dep:bevy_reflect",
"alloc",
]
curve = []
debug_glam_assert = ["glam/debug-glam-assert"]
default = [
"std",
"rand",
"curve",
]
glam_assert = ["glam/glam-assert"]
libm = [
"dep:libm",
"glam/libm",
]
mint = ["glam/mint"]
nostd-libm = [
"dep:libm",
"glam/nostd-libm",
]
rand = [
"dep:rand",
"dep:rand_distr",
"glam/rand",
]
serialize = [
"dep:serde",
"glam/serde",
]
std = [
"alloc",
"glam/std",
"derive_more/std",
"itertools/use_std",
"serde?/std",
"approx?/std",
"rand?/std",
"rand_distr?/std",
"bevy_reflect?/std",
]
[lib]
name = "bevy_math"
path = "src/lib.rs"
[dependencies.approx]
version = "0.5"
optional = true
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
features = ["glam"]
optional = true
default-features = false
[dependencies.derive_more]
version = "1"
features = [
"from",
"into",
]
default-features = false
[dependencies.glam]
version = "0.29.3"
features = ["bytemuck"]
default-features = false
[dependencies.itertools]
version = "0.14.0"
default-features = false
[dependencies.libm]
version = "0.2"
optional = true
[dependencies.rand]
version = "0.8"
optional = true
default-features = false
[dependencies.rand_distr]
version = "0.4.3"
optional = true
[dependencies.serde]
version = "1"
features = ["derive"]
optional = true
default-features = false
[dependencies.smallvec]
version = "1.11"
[dependencies.thiserror]
version = "2"
default-features = false
[dependencies.variadics_please]
version = "1.1"
[dev-dependencies.approx]
version = "0.5"
[dev-dependencies.glam]
version = "0.29.3"
features = ["approx"]
default-features = false
[dev-dependencies.rand]
version = "0.8"
[dev-dependencies.rand_chacha]
version = "0.3"
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_math/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_math/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
vendor/bevy_math/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Bevy Math
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy_math.svg)](https://crates.io/crates/bevy_math)
[![Downloads](https://img.shields.io/crates/d/bevy_math.svg)](https://crates.io/crates/bevy_math)
[![Docs](https://docs.rs/bevy_math/badge.svg)](https://docs.rs/bevy_math/latest/bevy_math/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)

39
vendor/bevy_math/clippy.toml vendored Normal file
View File

@@ -0,0 +1,39 @@
disallowed-methods = [
{ path = "f32::powi", reason = "use ops::FloatPow::squared, ops::FloatPow::cubed, or ops::powf instead for libm determinism" },
{ path = "f32::log", reason = "use ops::ln, ops::log2, or ops::log10 instead for libm determinism" },
{ path = "f32::abs_sub", reason = "deprecated and deeply confusing method" },
{ path = "f32::powf", reason = "use ops::powf instead for libm determinism" },
{ path = "f32::exp", reason = "use ops::exp instead for libm determinism" },
{ path = "f32::exp2", reason = "use ops::exp2 instead for libm determinism" },
{ path = "f32::ln", reason = "use ops::ln instead for libm determinism" },
{ path = "f32::log2", reason = "use ops::log2 instead for libm determinism" },
{ path = "f32::log10", reason = "use ops::log10 instead for libm determinism" },
{ path = "f32::cbrt", reason = "use ops::cbrt instead for libm determinism" },
{ path = "f32::hypot", reason = "use ops::hypot instead for libm determinism" },
{ path = "f32::sin", reason = "use ops::sin instead for libm determinism" },
{ path = "f32::cos", reason = "use ops::cos instead for libm determinism" },
{ path = "f32::tan", reason = "use ops::tan instead for libm determinism" },
{ path = "f32::asin", reason = "use ops::asin instead for libm determinism" },
{ path = "f32::acos", reason = "use ops::acos instead for libm determinism" },
{ path = "f32::atan", reason = "use ops::atan instead for libm determinism" },
{ path = "f32::atan2", reason = "use ops::atan2 instead for libm determinism" },
{ path = "f32::sin_cos", reason = "use ops::sin_cos instead for libm determinism" },
{ path = "f32::exp_m1", reason = "use ops::exp_m1 instead for libm determinism" },
{ path = "f32::ln_1p", reason = "use ops::ln_1p instead for libm determinism" },
{ path = "f32::sinh", reason = "use ops::sinh instead for libm determinism" },
{ path = "f32::cosh", reason = "use ops::cosh instead for libm determinism" },
{ path = "f32::tanh", reason = "use ops::tanh instead for libm determinism" },
{ path = "f32::asinh", reason = "use ops::asinh instead for libm determinism" },
{ path = "f32::acosh", reason = "use ops::acosh instead for libm determinism" },
{ path = "f32::atanh", reason = "use ops::atanh instead for libm determinism" },
# These methods have defined precision, but are only available from the standard library,
# not in core. Using these substitutes allows for no_std compatibility.
{ path = "f32::rem_euclid", reason = "use ops::rem_euclid instead for no_std compatibility" },
{ path = "f32::abs", reason = "use ops::abs instead for no_std compatibility" },
{ path = "f32::sqrt", reason = "use ops::sqrt instead for no_std compatibility" },
{ path = "f32::copysign", reason = "use ops::copysign instead for no_std compatibility" },
{ path = "f32::round", reason = "use ops::round instead for no_std compatibility" },
{ path = "f32::floor", reason = "use ops::floor instead for no_std compatibility" },
{ path = "f32::ceil", reason = "use ops::ceil instead for no_std compatibility" },
{ path = "f32::fract", reason = "use ops::fract instead for no_std compatibility" },
]

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.1799718" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BackIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1.0001708 0.02020202 1.0006722 0.030303031 1.0014874 0.04040404 1.0025996 0.05050505 1.0039923 0.060606062 1.0056486 0.07070707 1.007552 0.08080808 1.0096856 0.09090909 1.0120329 0.1010101 1.014577 0.11111111 1.0173013 0.121212125 1.020189 0.13131313 1.0232235 0.14141414 1.026388 0.15151516 1.029666 0.16161616 1.0330405 0.17171717 1.036495 0.18181819 1.0400127 0.1919192 1.043577 0.2020202 1.047171 0.21212122 1.0507782 0.22222222 1.0543817 0.23232323 1.0579649 0.24242425 1.0615112 0.25252524 1.0650038 0.26262626 1.0684259 0.27272728 1.0717609 0.28282827 1.0749921 0.2929293 1.0781027 0.3030303 1.0810761 0.3131313 1.0838957 0.32323232 1.0865445 0.33333334 1.089006 0.34343433 1.0912633 0.35353535 1.0933 0.36363637 1.0950992 0.37373737 1.0966442 0.3838384 1.0979183 0.3939394 1.0989048 0.4040404 1.099587 0.41414142 1.0999482 0.42424244 1.0999718 0.43434343 1.0996408 0.44444445 1.0989388 0.45454547 1.097849 0.46464646 1.0963547 0.47474748 1.094439 0.4848485 1.0920855 0.4949495 1.0892773 0.5050505 1.0859978 0.5151515 1.0822302 0.5252525 1.0779579 0.53535354 1.073164 0.54545456 1.067832 0.5555556 1.0619452 0.56565654 1.0554867 0.57575756 1.04844 0.5858586 1.0407882 0.5959596 1.0325147 0.6060606 1.0236028 0.61616164 1.0140359 0.6262626 1.003797 0.6363636 0.99286985 0.64646465 0.98123723 0.65656567 0.96888274 0.6666667 0.95578957 0.67676765 0.9419412 0.68686867 0.92732066 0.6969697 0.91191137 0.7070707 0.89569664 0.7171717 0.87865967 0.72727275 0.8607839 0.7373737 0.84205264 0.74747473 0.8224489 0.75757575 0.80195624 0.7676768 0.7805579 0.7777778 0.758237 0.7878788 0.73497725 0.7979798 0.71076155 0.8080808 0.68557334 0.8181818 0.6593958 0.82828283 0.6322125 0.83838385 0.6040065 0.8484849 0.57476115 0.85858583 0.5444598 0.86868685 0.5130856 0.8787879 0.48062217 0.8888889 0.44705236 0.8989899 0.4123596 0.90909094 0.37652743 0.9191919 0.33953905 0.9292929 0.30137765 0.93939394 0.2620262 0.94949496 0.22146857 0.959596 0.17968786 0.969697 0.13666725 0.97979796 0.09239054 0.989899 0.04684031 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.17926434 1.08 1.3585287" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BackInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1.000641 0.02020202 1.0024943 0.030303031 1.0054554 0.04040404 1.0094196 0.05050505 1.0142825 0.060606062 1.0199395 0.07070707 1.0262861 0.08080808 1.0332178 0.09090909 1.04063 0.1010101 1.048418 0.11111111 1.0564775 0.121212125 1.064704 0.13131313 1.0729926 0.14141414 1.0812391 0.15151516 1.0893388 0.16161616 1.0971872 0.17171717 1.1046797 0.18181819 1.1117119 0.1919192 1.1181791 0.2020202 1.1239768 0.21212122 1.1290005 0.22222222 1.1331457 0.23232323 1.1363077 0.24242425 1.1383821 0.25252524 1.1392643 0.26262626 1.1388497 0.27272728 1.1370339 0.28282827 1.1337123 0.2929293 1.1287804 0.3030303 1.1221334 0.3131313 1.113667 0.32323232 1.1032767 0.33333334 1.0908577 0.34343433 1.0763059 0.35353535 1.0595162 0.36363637 1.0403844 0.37373737 1.018806 0.3838384 0.99467623 0.3939394 0.96789074 0.4040404 0.9383449 0.41414142 0.9059341 0.42424244 0.8705539 0.43434343 0.83209985 0.44444445 0.79046714 0.45454547 0.7455514 0.46464646 0.6972482 0.47474748 0.6454528 0.4848485 0.5900605 0.4949495 0.53096724 0.5050505 0.46903276 0.5151515 0.40993953 0.5252525 0.3545472 0.53535354 0.30275178 0.54545456 0.25444847 0.5555556 0.20953274 0.56565654 0.16790026 0.57575756 0.12944609 0.5858586 0.094065905 0.5959596 0.061655104 0.6060606 0.0321092 0.61616164 0.0053236485 0.6262626 -0.018805861 0.6363636 -0.04038441 0.64646465 -0.05951619 0.65656567 -0.076305866 0.6666667 -0.09085786 0.67676765 -0.10327661 0.68686867 -0.11366701 0.6969697 -0.122133374 0.7070707 -0.12878036 0.7171717 -0.13371229 0.72727275 -0.13703394 0.7373737 -0.13884974 0.74747473 -0.13926435 0.75757575 -0.13838208 0.7676768 -0.13630772 0.7777778 -0.13314569 0.7878788 -0.12900054 0.7979798 -0.12397683 0.8080808 -0.11817908 0.8181818 -0.11171186 0.82828283 -0.1046797 0.83838385 -0.09718716 0.8484849 -0.08933878 0.85858583 -0.081239104 0.86868685 -0.07299256 0.8787879 -0.06470394 0.8888889 -0.056477547 0.8989899 -0.048418045 0.90909094 -0.040629864 0.9191919 -0.033217788 0.9292929 -0.026286125 0.93939394 -0.019939542 0.94949496 -0.014282465 0.959596 -0.00941956 0.969697 -0.0054552555 0.97979796 -0.0024943352 0.989899 -0.00064098835 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.13997176 1.08 1.1799718" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BackOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9531597 0.02020202 0.90760946 0.030303031 0.86333275 0.04040404 0.82031214 0.05050505 0.77853143 0.060606062 0.7379738 0.07070707 0.69862235 0.08080808 0.66046095 0.09090909 0.6234726 0.1010101 0.5876404 0.11111111 0.55294764 0.121212125 0.5193778 0.13131313 0.4869144 0.14141414 0.45554018 0.15151516 0.42523885 0.16161616 0.39599347 0.17171717 0.36778748 0.18181819 0.3406042 0.1919192 0.31442666 0.2020202 0.28923845 0.21212122 0.26502264 0.22222222 0.241763 0.23232323 0.21944213 0.24242425 0.19804376 0.25252524 0.17755127 0.26262626 0.15794736 0.27272728 0.13921613 0.28282827 0.121340334 0.2929293 0.10430336 0.3030303 0.08808863 0.3131313 0.07267934 0.32323232 0.058058858 0.33333334 0.044210315 0.34343433 0.03111726 0.35353535 0.018762767 0.36363637 0.007130146 0.37373737 -0.0037970543 0.3838384 -0.01403594 0.3939394 -0.023602843 0.4040404 -0.03251469 0.41414142 -0.040788174 0.42424244 -0.04843998 0.43434343 -0.05548668 0.44444445 -0.0619452 0.45454547 -0.06783199 0.46464646 -0.073163986 0.47474748 -0.07795787 0.4848485 -0.08223021 0.4949495 -0.08599782 0.5050505 -0.08927727 0.5151515 -0.09208548 0.5252525 -0.09443903 0.53535354 -0.096354604 0.54545456 -0.09784901 0.5555556 -0.09893882 0.56565654 -0.099640846 0.57575756 -0.09997177 0.5858586 -0.09994817 0.5959596 -0.09958696 0.6060606 -0.09890485 0.61616164 -0.09791827 0.6262626 -0.09664416 0.6363636 -0.09509909 0.64646465 -0.093299985 0.65656567 -0.091263294 0.6666667 -0.08900595 0.67676765 -0.08654451 0.68686867 -0.08389568 0.6969697 -0.081076145 0.7070707 -0.07810271 0.7171717 -0.07499218 0.72727275 -0.07176089 0.7373737 -0.068425894 0.74747473 -0.06500375 0.75757575 -0.06151128 0.7676768 -0.05796492 0.7777778 -0.05438161 0.7878788 -0.05077815 0.7979798 -0.047170997 0.8080808 -0.043576956 0.8181818 -0.040012717 0.82828283 -0.03649497 0.83838385 -0.033040524 0.8484849 -0.029665947 0.85858583 -0.02638805 0.86868685 -0.02322352 0.8787879 -0.020189047 0.8888889 -0.017301321 0.8989899 -0.014577031 0.90909094 -0.0120328665 0.9191919 -0.009685636 0.9292929 -0.0075520277 0.93939394 -0.005648613 0.94949496 -0.003992319 0.959596 -0.002599597 0.969697 -0.0014873743 0.97979796 -0.000672102 0.989899 -0.00017082691 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BothSteps(4, Both)</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 0.8 0.01010101 0.8 0.02020202 0.8 0.030303031 0.8 0.04040404 0.8 0.05050505 0.8 0.060606062 0.8 0.07070707 0.8 0.08080808 0.8 0.09090909 0.8 0.1010101 0.8 0.11111111 0.8 0.121212125 0.8 0.13131313 0.8 0.14141414 0.8 0.15151516 0.8 0.16161616 0.8 0.17171717 0.8 0.18181819 0.8 0.1919192 0.8 0.2020202 0.8 0.21212122 0.8 0.22222222 0.8 0.23232323 0.8 0.24242425 0.8 0.25252524 0.6 0.26262626 0.6 0.27272728 0.6 0.28282827 0.6 0.2929293 0.6 0.3030303 0.6 0.3131313 0.6 0.32323232 0.6 0.33333334 0.6 0.34343433 0.6 0.35353535 0.6 0.36363637 0.6 0.37373737 0.6 0.3838384 0.6 0.3939394 0.6 0.4040404 0.6 0.41414142 0.6 0.42424244 0.6 0.43434343 0.6 0.44444445 0.6 0.45454547 0.6 0.46464646 0.6 0.47474748 0.6 0.4848485 0.6 0.4949495 0.6 0.5050505 0.39999998 0.5151515 0.39999998 0.5252525 0.39999998 0.53535354 0.39999998 0.54545456 0.39999998 0.5555556 0.39999998 0.56565654 0.39999998 0.57575756 0.39999998 0.5858586 0.39999998 0.5959596 0.39999998 0.6060606 0.39999998 0.61616164 0.39999998 0.6262626 0.39999998 0.6363636 0.39999998 0.64646465 0.39999998 0.65656567 0.39999998 0.6666667 0.39999998 0.67676765 0.39999998 0.68686867 0.39999998 0.6969697 0.39999998 0.7070707 0.39999998 0.7171717 0.39999998 0.72727275 0.39999998 0.7373737 0.39999998 0.74747473 0.39999998 0.75757575 0.19999999 0.7676768 0.19999999 0.7777778 0.19999999 0.7878788 0.19999999 0.7979798 0.19999999 0.8080808 0.19999999 0.8181818 0.19999999 0.82828283 0.19999999 0.83838385 0.19999999 0.8484849 0.19999999 0.85858583 0.19999999 0.86868685 0.19999999 0.8787879 0.19999999 0.8888889 0.19999999 0.8989899 0.19999999 0.90909094 0.19999999 0.9191919 0.19999999 0.9292929 0.19999999 0.93939394 0.19999999 0.94949496 0.19999999 0.959596 0.19999999 0.969697 0.19999999 0.97979796 0.19999999 0.989899 0.19999999 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.0800003" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BounceIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99019337 0.02020202 0.98258877 0.030303031 0.977191 0.04040404 0.97399426 0.05050505 0.9730034 0.060606062 0.9742155 0.07070707 0.9776306 0.08080808 0.9832506 0.09090909 0.99107456 0.1010101 0.99790573 0.11111111 0.97832966 0.121212125 0.9612179 0.13131313 0.94656754 0.14141414 0.9343777 0.15151516 0.92465305 0.16161616 0.91738796 0.17171717 0.9125881 0.18181819 0.91024876 0.1919192 0.9103708 0.2020202 0.9129567 0.21212122 0.918005 0.22222222 0.9255142 0.23232323 0.9354873 0.24242425 0.9479213 0.25252524 0.9628186 0.26262626 0.98017836 0.27272728 0.99999905 0.28282827 0.9675927 0.2929293 0.93703747 0.3030303 0.9083333 0.3131313 0.88148165 0.32323232 0.856482 0.33333334 0.8333335 0.34343433 0.812037 0.35353535 0.792593 0.36363637 0.7750001 0.37373737 0.7592592 0.3838384 0.7453706 0.3939394 0.7333336 0.4040404 0.7231486 0.41414142 0.71481514 0.42424244 0.7083335 0.43434343 0.7037041 0.44444445 0.7009263 0.45454547 0.7000005 0.46464646 0.7009263 0.47474748 0.7037039 0.4848485 0.70833373 0.4949495 0.71481514 0.5050505 0.72314835 0.5151515 0.73333335 0.5252525 0.7453706 0.53535354 0.75925946 0.54545456 0.7750006 0.5555556 0.79259276 0.56565654 0.812037 0.57575756 0.83333373 0.5858586 0.85648155 0.5959596 0.88148165 0.6060606 0.9083338 0.61616164 0.93703747 0.6262626 0.9675927 0.6363636 1.0000002 0.64646465 0.94521606 0.65656567 0.8919752 0.6666667 0.8402777 0.67676765 0.79012364 0.68686867 0.7415124 0.6969697 0.6944445 0.7070707 0.64891976 0.7171717 0.6049382 0.72727275 0.5624999 0.7373737 0.521605 0.74747473 0.4822532 0.75757575 0.44444448 0.7676768 0.40817904 0.7777778 0.37345672 0.7878788 0.34027767 0.7979798 0.30864203 0.8080808 0.27854943 0.8181818 0.25 0.82828283 0.22299379 0.83838385 0.1975308 0.8484849 0.17361104 0.85858583 0.15123463 0.86868685 0.13040125 0.8787879 0.111111104 0.8888889 0.09336418 0.8989899 0.07716048 0.90909094 0.06249994 0.9191919 0.049382746 0.9292929 0.037808657 0.93939394 0.027777791 0.94949496 0.01929009 0.959596 0.012345672 0.969697 0.006944418 0.97979796 0.0030864477 0.989899 0.0007715821 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BounceInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9912944 0.02020202 0.9869971 0.030303031 0.98710775 0.04040404 0.9916253 0.05050505 0.99895287 0.060606062 0.98060894 0.07070707 0.96718884 0.08080808 0.958694 0.09090909 0.9551244 0.1010101 0.95647836 0.11111111 0.9627571 0.121212125 0.97396064 0.13131313 0.9900892 0.14141414 0.98379636 0.15151516 0.95416665 0.16161616 0.928241 0.17171717 0.9060185 0.18181819 0.88750005 0.1919192 0.8726853 0.2020202 0.8615743 0.21212122 0.85416675 0.22222222 0.85046315 0.23232323 0.85046315 0.24242425 0.85416687 0.25252524 0.8615742 0.26262626 0.8726853 0.27272728 0.8875003 0.28282827 0.9060185 0.2929293 0.9282408 0.3030303 0.9541669 0.3131313 0.98379636 0.32323232 0.97260803 0.33333334 0.92013884 0.34343433 0.8707562 0.35353535 0.8244599 0.36363637 0.78124994 0.37373737 0.7411266 0.3838384 0.7040895 0.3939394 0.67013884 0.4040404 0.6392747 0.41414142 0.6114969 0.42424244 0.5868055 0.43434343 0.5652006 0.44444445 0.5466821 0.45454547 0.53125 0.46464646 0.5189043 0.47474748 0.50964504 0.4848485 0.5034722 0.4949495 0.50038576 0.5050505 0.49961418 0.5151515 0.4965278 0.5252525 0.49035496 0.53535354 0.48109567 0.54545456 0.46875 0.5555556 0.45331788 0.56565654 0.43479943 0.57575756 0.41319448 0.5858586 0.38850307 0.5959596 0.36072528 0.6060606 0.32986104 0.61616164 0.29591036 0.6262626 0.25887352 0.6363636 0.21875006 0.64646465 0.17554009 0.65656567 0.12924379 0.6666667 0.079861045 0.67676765 0.027392149 0.68686867 0.016203642 0.6969697 0.04583311 0.7070707 0.071759224 0.7171717 0.093981504 0.72727275 0.11250007 0.7373737 0.12731469 0.74747473 0.13842583 0.75757575 0.14583313 0.7676768 0.14953685 0.7777778 0.14953685 0.7878788 0.14583325 0.7979798 0.13842583 0.8080808 0.12731469 0.8181818 0.11249995 0.82828283 0.093981504 0.83838385 0.071758986 0.8484849 0.04583311 0.85858583 0.016203642 0.86868685 0.009910822 0.8787879 0.026039362 0.8888889 0.03724289 0.8989899 0.043521643 0.90909094 0.0448761 0.9191919 0.041305542 0.9292929 0.032811165 0.93939394 0.01939106 0.94949496 0.0010471344 0.959596 0.008375168 0.969697 0.012892723 0.97979796 0.013002396 0.989899 0.008705616 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.040000238 1.08 1.0800003" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>BounceOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9992284 0.02020202 0.99691355 0.030303031 0.9930556 0.04040404 0.9876543 0.05050505 0.98070985 0.060606062 0.9722222 0.07070707 0.96219134 0.08080808 0.9506173 0.09090909 0.9375 0.1010101 0.9228395 0.11111111 0.9066358 0.121212125 0.8888889 0.13131313 0.86959875 0.14141414 0.84876543 0.15151516 0.8263889 0.16161616 0.80246913 0.17171717 0.7770062 0.18181819 0.75 0.1919192 0.72145057 0.2020202 0.69135803 0.21212122 0.6597222 0.22222222 0.62654316 0.23232323 0.59182096 0.24242425 0.5555555 0.25252524 0.5177469 0.26262626 0.4783951 0.27272728 0.4375 0.28282827 0.3950618 0.2929293 0.35108024 0.3030303 0.30555552 0.3131313 0.2584877 0.32323232 0.20987654 0.33333334 0.15972215 0.34343433 0.108024776 0.35353535 0.05478394 0.36363637 -0.00000023841858 0.37373737 0.032407284 0.3838384 0.06296277 0.3939394 0.09166622 0.4040404 0.11851835 0.41414142 0.14351845 0.42424244 0.16666627 0.43434343 0.18796253 0.44444445 0.20740747 0.45454547 0.2249999 0.46464646 0.24074054 0.47474748 0.25462937 0.4848485 0.26666665 0.4949495 0.27685142 0.5050505 0.28518486 0.5151515 0.29166627 0.5252525 0.29629612 0.53535354 0.2990737 0.54545456 0.29999995 0.5555556 0.2990737 0.56565654 0.29629588 0.57575756 0.2916665 0.5858586 0.28518486 0.5959596 0.27685142 0.6060606 0.2666664 0.61616164 0.25462914 0.6262626 0.2407403 0.6363636 0.2249999 0.64646465 0.207407 0.65656567 0.18796301 0.6666667 0.16666603 0.67676765 0.14351797 0.68686867 0.11851835 0.6969697 0.0916667 0.7070707 0.06296253 0.7171717 0.032407284 0.72727275 0.0000009536743 0.7373737 0.019821644 0.74747473 0.037181854 0.75757575 0.052078724 0.7676768 0.06451273 0.7777778 0.07448578 0.7878788 0.08199549 0.7979798 0.087043285 0.8080808 0.08962917 0.8181818 0.08975124 0.82828283 0.08741188 0.83838385 0.08261204 0.8484849 0.07534695 0.85858583 0.06562233 0.86868685 0.053432465 0.8787879 0.03878212 0.8888889 0.021670341 0.8989899 0.0020942688 0.90909094 0.008925438 0.9191919 0.016749382 0.9292929 0.022369385 0.93939394 0.025784492 0.94949496 0.026996613 0.959596 0.026005745 0.969697 0.022809029 0.97979796 0.017411232 0.989899 0.009806633 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>CircularIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.999949 0.02020202 0.9997959 0.030303031 0.99954075 0.04040404 0.9991834 0.05050505 0.9987238 0.060606062 0.9981618 0.07070707 0.9974971 0.08080808 0.9967297 0.09090909 0.9958592 0.1010101 0.9948854 0.11111111 0.993808 0.121212125 0.9926266 0.13131313 0.99134094 0.14141414 0.98995054 0.15151516 0.98845494 0.16161616 0.9868537 0.17171717 0.9851463 0.18181819 0.98333216 0.1919192 0.98141074 0.2020202 0.9793814 0.21212122 0.97724336 0.22222222 0.97499603 0.23232323 0.9726386 0.24242425 0.9701703 0.25252524 0.96759033 0.26262626 0.96489763 0.27272728 0.9620914 0.28282827 0.9591706 0.2929293 0.95613414 0.3030303 0.95298094 0.3131313 0.94970983 0.32323232 0.94631964 0.33333334 0.94280905 0.34343433 0.9391767 0.35353535 0.93542117 0.36363637 0.93154097 0.37373737 0.9275346 0.3838384 0.9234003 0.3939394 0.9191364 0.4040404 0.91474116 0.41414142 0.9102126 0.42424244 0.90554863 0.43434343 0.90074736 0.44444445 0.89580643 0.45454547 0.8907235 0.46464646 0.88549626 0.47474748 0.88012207 0.4848485 0.87459815 0.4949495 0.86892176 0.5050505 0.8630898 0.5151515 0.8570991 0.5252525 0.85094637 0.53535354 0.84462804 0.54545456 0.8381404 0.5555556 0.8314794 0.56565654 0.8246409 0.57575756 0.81762046 0.5858586 0.8104133 0.5959596 0.8030144 0.6060606 0.7954185 0.61616164 0.7876197 0.6262626 0.7796122 0.6363636 0.77138925 0.64646465 0.7629439 0.65656567 0.7542689 0.6666667 0.74535596 0.67676765 0.73619664 0.68686867 0.7267816 0.6969697 0.71710056 0.7070707 0.70714283 0.7171717 0.6968965 0.72727275 0.68634856 0.7373737 0.675485 0.74747473 0.66429025 0.75757575 0.6527473 0.7676768 0.64083725 0.7777778 0.6285394 0.7878788 0.6158303 0.7979798 0.6026842 0.8080808 0.5890717 0.8181818 0.5749596 0.82828283 0.56031024 0.83838385 0.5450803 0.8484849 0.5292196 0.85858583 0.51266986 0.86868685 0.49536163 0.8787879 0.4772126 0.8888889 0.45812285 0.8989899 0.43796933 0.90909094 0.41659772 0.9191919 0.3938099 0.9292929 0.36934352 0.93939394 0.34283972 0.94949496 0.31378222 0.959596 0.2813815 0.969697 0.24431074 0.97979796 0.19998991 0.989899 0.14177448 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>CircularInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99989796 0.02020202 0.9995917 0.030303031 0.9990809 0.04040404 0.9983648 0.05050505 0.9974427 0.060606062 0.99631333 0.07070707 0.99497527 0.08080808 0.99342686 0.09090909 0.9916661 0.1010101 0.98969066 0.11111111 0.98749804 0.121212125 0.9850851 0.13131313 0.9824488 0.14141414 0.9795853 0.15151516 0.9764905 0.16161616 0.9731598 0.17171717 0.96958834 0.18181819 0.9657705 0.1919192 0.96170014 0.2020202 0.9573706 0.21212122 0.9527743 0.22222222 0.9479032 0.23232323 0.9427481 0.24242425 0.9372991 0.25252524 0.9315449 0.26262626 0.9254732 0.27272728 0.91907024 0.28282827 0.9123205 0.2929293 0.9052067 0.3030303 0.89770925 0.3131313 0.8898061 0.32323232 0.881472 0.33333334 0.872678 0.34343433 0.8633908 0.35353535 0.8535714 0.36363637 0.8431743 0.37373737 0.8321451 0.3838384 0.8204186 0.3939394 0.80791515 0.4040404 0.7945359 0.41414142 0.7801551 0.42424244 0.7646098 0.43434343 0.7476808 0.44444445 0.7290614 0.45454547 0.70829886 0.46464646 0.68467176 0.47474748 0.6568911 0.4848485 0.62215537 0.4949495 0.5708872 0.5050505 0.42911297 0.5151515 0.37784463 0.5252525 0.3431089 0.53535354 0.31532824 0.54545456 0.29170108 0.5555556 0.27093852 0.56565654 0.25231922 0.57575756 0.23539019 0.5858586 0.21984488 0.5959596 0.20546412 0.6060606 0.19208479 0.61616164 0.17958134 0.6262626 0.1678549 0.6363636 0.15682572 0.64646465 0.14642859 0.65656567 0.1366092 0.6666667 0.12732196 0.67676765 0.11852807 0.68686867 0.11019391 0.6969697 0.10229075 0.7070707 0.09479332 0.7171717 0.087679505 0.72727275 0.080929756 0.7373737 0.07452679 0.74747473 0.0684551 0.75757575 0.06270093 0.7676768 0.05725187 0.7777778 0.052096784 0.7878788 0.047225654 0.7979798 0.04262942 0.8080808 0.03829986 0.8181818 0.034229517 0.82828283 0.03041166 0.83838385 0.02684021 0.8484849 0.023509502 0.85858583 0.02041471 0.86868685 0.017551184 0.8787879 0.01491487 0.8888889 0.012501955 0.8989899 0.010309339 0.90909094 0.008333921 0.9191919 0.0065732 0.9292929 0.005024731 0.93939394 0.0036866665 0.94949496 0.0025572777 0.959596 0.0016351938 0.969697 0.0009191036 0.97979796 0.00040829182 0.989899 0.00010204315 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>CircularOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.8582255 0.02020202 0.8000101 0.030303031 0.75568926 0.04040404 0.7186185 0.05050505 0.6862178 0.060606062 0.6571603 0.07070707 0.6306565 0.08080808 0.6061901 0.09090909 0.58340216 0.1010101 0.5620307 0.11111111 0.54187715 0.121212125 0.5227874 0.13131313 0.5046384 0.14141414 0.48733014 0.15151516 0.47078037 0.16161616 0.4549197 0.17171717 0.43968976 0.18181819 0.42504042 0.1919192 0.4109283 0.2020202 0.39731586 0.21212122 0.38416964 0.22222222 0.37146062 0.23232323 0.35916275 0.24242425 0.34725273 0.25252524 0.3357098 0.26262626 0.32451499 0.27272728 0.31365144 0.28282827 0.3031035 0.2929293 0.29285717 0.3030303 0.28289944 0.3131313 0.2732184 0.32323232 0.26380336 0.33333334 0.25464398 0.34343433 0.24573112 0.35353535 0.23705608 0.36363637 0.22861075 0.37373737 0.22038782 0.3838384 0.21238023 0.3939394 0.2045815 0.4040404 0.1969856 0.41414142 0.1895867 0.42424244 0.18237954 0.43434343 0.17535907 0.44444445 0.16852063 0.45454547 0.16185957 0.46464646 0.15537196 0.47474748 0.14905363 0.4848485 0.14290088 0.4949495 0.13691026 0.5050505 0.13107824 0.5151515 0.12540185 0.5252525 0.119877934 0.53535354 0.11450374 0.54545456 0.10927647 0.5555556 0.10419357 0.56565654 0.09925264 0.57575756 0.09445137 0.5858586 0.08978742 0.5959596 0.08525884 0.6060606 0.080863535 0.61616164 0.07659972 0.6262626 0.07246548 0.6363636 0.068459034 0.64646465 0.06457883 0.65656567 0.06082332 0.6666667 0.057190955 0.67676765 0.05368036 0.68686867 0.050290167 0.6969697 0.047019064 0.7070707 0.04386586 0.7171717 0.04082942 0.72727275 0.037908614 0.7373737 0.035102367 0.74747473 0.032409668 0.75757575 0.02982968 0.7676768 0.027361393 0.7777778 0.02500397 0.7878788 0.022756636 0.7979798 0.020618677 0.8080808 0.018589258 0.8181818 0.016667843 0.82828283 0.014853716 0.83838385 0.013146281 0.8484849 0.011545062 0.85858583 0.010049462 0.86868685 0.008659065 0.8787879 0.0073733926 0.8888889 0.0061920285 0.8989899 0.005114615 0.90909094 0.0041407943 0.9191919 0.003270328 0.9292929 0.0025029182 0.93939394 0.0018382072 0.94949496 0.001276195 0.959596 0.00081658363 0.969697 0.0004592538 0.97979796 0.0002040863 0.989899 0.000051021576 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>CubicIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.999999 0.02020202 0.9999918 0.030303031 0.99997216 0.04040404 0.999934 0.05050505 0.9998712 0.060606062 0.9997774 0.07070707 0.9996465 0.08080808 0.9994723 0.09090909 0.9992487 0.1010101 0.9989694 0.11111111 0.99862826 0.121212125 0.99821913 0.13131313 0.99773574 0.14141414 0.997172 0.15151516 0.9965217 0.16161616 0.9957786 0.17171717 0.9949366 0.18181819 0.99398947 0.1919192 0.99293107 0.2020202 0.9917551 0.21212122 0.9904555 0.22222222 0.98902607 0.23232323 0.98746055 0.24242425 0.9857528 0.25252524 0.98389673 0.26262626 0.98188597 0.27272728 0.9797145 0.28282827 0.97737604 0.2929293 0.9748644 0.3030303 0.9721735 0.3131313 0.9692971 0.32323232 0.96622896 0.33333334 0.962963 0.34343433 0.9594929 0.35353535 0.9558126 0.36363637 0.95191586 0.37373737 0.9477965 0.3838384 0.94344836 0.3939394 0.93886524 0.4040404 0.93404096 0.41414142 0.9289693 0.42424244 0.9236441 0.43434343 0.9180593 0.44444445 0.9122085 0.45454547 0.9060856 0.46464646 0.89968455 0.47474748 0.89299893 0.4848485 0.88602275 0.4949495 0.8787497 0.5050505 0.87117374 0.5151515 0.8632885 0.5252525 0.855088 0.53535354 0.84656584 0.54545456 0.837716 0.5555556 0.8285322 0.56565654 0.81900835 0.57575756 0.80913824 0.5858586 0.7989156 0.5959596 0.7883343 0.6060606 0.7773882 0.61616164 0.766071 0.6262626 0.75437677 0.6363636 0.7422991 0.64646465 0.7298317 0.65656567 0.71696866 0.6666667 0.70370364 0.67676765 0.69003063 0.68686867 0.67594326 0.6969697 0.6614353 0.7070707 0.6465007 0.7171717 0.6311333 0.72727275 0.61532676 0.7373737 0.5990752 0.74747473 0.58237207 0.75757575 0.56521136 0.7676768 0.54758686 0.7777778 0.52949244 0.7878788 0.51092184 0.7979798 0.49186903 0.8080808 0.47232765 0.8181818 0.4522915 0.82828283 0.43175453 0.83838385 0.4107105 0.8484849 0.38915318 0.85858583 0.36707658 0.86868685 0.34447426 0.8787879 0.32134014 0.8888889 0.29766804 0.8989899 0.27345175 0.90909094 0.24868512 0.9191919 0.22336215 0.9292929 0.19747627 0.93939394 0.17102152 0.94949496 0.14399165 0.959596 0.11638057 0.969697 0.08818203 0.97979796 0.05939001 0.989899 0.029998004 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>CubicInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9999959 0.02020202 0.99996704 0.030303031 0.9998887 0.04040404 0.9997362 0.05050505 0.9994847 0.060606062 0.99910957 0.07070707 0.998586 0.08080808 0.99788934 0.09090909 0.99699473 0.1010101 0.99587756 0.11111111 0.99451303 0.121212125 0.9928764 0.13131313 0.990943 0.14141414 0.98868805 0.15151516 0.9860868 0.16161616 0.9831145 0.17171717 0.97974646 0.18181819 0.97595793 0.1919192 0.97172415 0.2020202 0.96702045 0.21212122 0.9618221 0.22222222 0.9561043 0.23232323 0.9498423 0.24242425 0.9430114 0.25252524 0.93558687 0.26262626 0.927544 0.27272728 0.918858 0.28282827 0.9095042 0.2929293 0.8994578 0.3030303 0.8886941 0.3131313 0.8771884 0.32323232 0.86491585 0.33333334 0.8518518 0.34343433 0.8379716 0.35353535 0.82325035 0.36363637 0.8076634 0.37373737 0.79118603 0.3838384 0.77379346 0.3939394 0.7554609 0.4040404 0.73616385 0.41414142 0.7158773 0.42424244 0.6945766 0.43434343 0.67223716 0.44444445 0.648834 0.45454547 0.62434256 0.46464646 0.59873813 0.47474748 0.57199585 0.4848485 0.544091 0.4949495 0.51499903 0.5050505 0.4850011 0.5151515 0.455909 0.5252525 0.42800415 0.53535354 0.40126187 0.54545456 0.37565738 0.5555556 0.3511659 0.56565654 0.32776296 0.57575756 0.30542338 0.5858586 0.2841227 0.5959596 0.26383615 0.6060606 0.24453902 0.61616164 0.22620654 0.6262626 0.20881402 0.6363636 0.19233662 0.64646465 0.17674965 0.65656567 0.16202837 0.6666667 0.14814812 0.67676765 0.13508415 0.68686867 0.122811675 0.6969697 0.11130589 0.7070707 0.10054219 0.7171717 0.090495825 0.72727275 0.08114195 0.7373737 0.07245606 0.74747473 0.06441313 0.75757575 0.056988597 0.7676768 0.050157726 0.7777778 0.04389572 0.7878788 0.038177907 0.7979798 0.032979548 0.8080808 0.028275847 0.8181818 0.02404207 0.82828283 0.02025354 0.83838385 0.016885519 0.8484849 0.013913214 0.85858583 0.011312008 0.86868685 0.009056985 0.8787879 0.0071235895 0.8888889 0.005486965 0.8989899 0.004122436 0.90909094 0.0030052662 0.9191919 0.0021107197 0.9292929 0.001414001 0.93939394 0.0008904338 0.94949496 0.00051528215 0.959596 0.00026381016 0.969697 0.00011128187 0.97979796 0.00003296137 0.989899 0.0000041127205 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>CubicOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.970002 0.02020202 0.94061 0.030303031 0.91181797 0.04040404 0.8836194 0.05050505 0.85600835 0.060606062 0.8289785 0.07070707 0.80252373 0.08080808 0.77663785 0.09090909 0.75131476 0.1010101 0.72654825 0.11111111 0.70233196 0.121212125 0.67865986 0.13131313 0.65552574 0.14141414 0.6329234 0.15151516 0.6108468 0.16161616 0.5892895 0.17171717 0.5682455 0.18181819 0.5477085 0.1919192 0.52767235 0.2020202 0.508131 0.21212122 0.48907804 0.22222222 0.47050756 0.23232323 0.45241314 0.24242425 0.43478864 0.25252524 0.41762805 0.26262626 0.4009248 0.27272728 0.38467324 0.28282827 0.36886668 0.2929293 0.3534993 0.3030303 0.3385647 0.3131313 0.32405674 0.32323232 0.30996943 0.33333334 0.29629624 0.34343433 0.28303134 0.35353535 0.2701683 0.36363637 0.25770092 0.37373737 0.24562329 0.3838384 0.23392892 0.3939394 0.22261178 0.4040404 0.21166569 0.41414142 0.20108438 0.42424244 0.19086176 0.43434343 0.18099165 0.44444445 0.17146778 0.45454547 0.16228396 0.46464646 0.15343416 0.47474748 0.144912 0.4848485 0.13671148 0.4949495 0.12882626 0.5050505 0.12125027 0.5151515 0.11397725 0.5252525 0.107001066 0.53535354 0.10031545 0.54545456 0.09391433 0.5555556 0.0877915 0.56565654 0.08194071 0.57575756 0.076355875 0.5858586 0.07103068 0.5959596 0.06595904 0.6060606 0.061134756 0.61616164 0.056551635 0.6262626 0.052203536 0.6363636 0.04808414 0.64646465 0.044187427 0.65656567 0.04050708 0.6666667 0.037037015 0.67676765 0.033771038 0.68686867 0.030702889 0.6969697 0.027826488 0.7070707 0.025135577 0.7171717 0.022623956 0.72727275 0.020285487 0.7373737 0.01811403 0.74747473 0.016103268 0.75757575 0.014247179 0.7676768 0.012539446 0.7777778 0.01097393 0.7878788 0.009544492 0.7979798 0.008244872 0.8080808 0.007068932 0.8181818 0.0060105324 0.82828283 0.0050634146 0.83838385 0.0042213798 0.8484849 0.0034782887 0.85858583 0.002828002 0.86868685 0.0022642612 0.8787879 0.0017808676 0.8888889 0.0013717413 0.8989899 0.0010306239 0.90909094 0.00075131655 0.9191919 0.0005276799 0.9292929 0.00035351515 0.93939394 0.00022262335 0.94949496 0.00012880564 0.959596 0.00006598234 0.969697 0.00002783537 0.97979796 0.000008225441 0.989899 0.000001013279 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.9130816 1.08 1.9530816" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>Elastic(50.0)</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.87652457 0.02020202 0.5430269 0.030303031 0.08985078 0.04040404 -0.36685824 0.05050505 -0.7148113 0.060606062 -0.87308156 0.07070707 -0.8107506 0.08080808 -0.55244195 0.09090909 -0.1699208 0.1010101 0.23758578 0.11111111 0.5690067 0.121212125 0.74639827 0.13131313 0.7331244 0.14141414 0.5410442 0.15151516 0.22541612 0.16161616 -0.13067424 0.17171717 -0.43805146 0.18181819 -0.62368226 0.1919192 -0.64789987 0.2020202 -0.51258254 0.21212122 -0.2586938 0.22222222 0.045701385 0.23232323 0.323524 0.24242425 0.50810283 0.25252524 0.5590545 0.26262626 0.47088003 0.27272728 0.27251196 0.28282827 0.018388987 0.2929293 -0.22629714 0.3030303 -0.40221977 0.3131313 -0.470196 0.32323232 -0.4197166 0.33333334 -0.26991928 0.34343433 -0.063200235 0.35353535 0.14656717 0.36363637 0.30795324 0.37373737 0.384485 0.3838384 0.3627196 0.3939394 0.25414383 0.4040404 0.09079921 0.41414142 -0.08389914 0.42424244 -0.22657609 0.43434343 -0.30457044 0.44444445 -0.30326605 0.45454547 -0.22847569 0.46464646 -0.10361636 0.47474748 0.03728491 0.4848485 0.15871769 0.4949495 0.23254418 0.5050505 0.2443906 0.5151515 0.19616282 0.5252525 0.10433936 0.53535354 -0.0052199364 0.54545456 -0.10439193 0.5555556 -0.16991067 0.56565654 -0.18871152 0.57575756 -0.1603024 0.5858586 -0.095810294 0.5959596 -0.014217138 0.6060606 0.06303555 0.61616164 0.11757636 0.6262626 0.13836896 0.6363636 0.12374747 0.64646465 0.080914974 0.65656567 0.023267984 0.6666667 -0.03356588 0.67676765 -0.07585454 0.68686867 -0.094979525 0.6969697 -0.089021325 0.7070707 -0.062480092 0.7171717 -0.024399519 0.72727275 0.014448881 0.7373737 0.044489622 0.74747473 0.059607744 0.75757575 0.058243513 0.7676768 0.043173373 0.7777778 0.020197451 0.7878788 -0.0037806034 0.7979798 -0.022693872 0.8080808 -0.032753825 0.8181818 -0.033070922 0.82828283 -0.025409937 0.83838385 -0.013266563 0.8484849 -0.00062561035 0.85858583 0.009203136 0.86868685 0.014359295 0.8787879 0.014653623 0.8888889 0.0112707615 0.8989899 0.006127298 0.90909094 0.0011420846 0.9191919 -0.0023419857 0.9292929 -0.00382936 0.93939394 -0.0036069155 0.94949496 -0.002430439 0.959596 -0.0011194944 0.969697 -0.0002272129 0.97979796 0.000103116035 0.989899 0.000070393085 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.4520154" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>ElasticIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1.0004883 0.01010101 1.0003215 0.02020202 1.0001127 0.030303031 0.9998665 0.04040404 0.9995903 0.05050505 0.9992944 0.060606062 0.99899143 0.07070707 0.9986965 0.08080808 0.99842644 0.09090909 0.9981993 0.1010101 0.9980336 0.11111111 0.9979474 0.121212125 0.99795717 0.13131313 0.99807686 0.14141414 0.9983166 0.15151516 0.9986817 0.16161616 0.9991718 0.17171717 0.9997794 0.18181819 1.0004901 0.1919192 1.0012816 0.2020202 1.002124 0.21212122 1.0029804 0.22222222 1.0038071 0.23232323 1.0045561 0.24242425 1.0051758 0.25252524 1.0056139 0.26262626 1.0058199 0.27272728 1.0057479 0.28282827 1.00536 0.2929293 1.0046293 0.3030303 1.0035429 0.3131313 1.0021052 0.32323232 1.0003397 0.33333334 0.9982908 0.34343433 0.9960246 0.35353535 0.9936288 0.36363637 0.9912111 0.37373737 0.9888966 0.3838384 0.98682356 0.3939394 0.9851383 0.4040404 0.9839887 0.41414142 0.9835162 0.42424244 0.98384774 0.43434343 0.98508644 0.44444445 0.987303 0.45454547 0.99052674 0.46464646 0.9947378 0.47474748 0.99986124 0.4848485 1.0057621 0.4949495 1.0122441 0.5050505 1.0190504 0.5151515 1.0258684 0.5252525 1.0323383 0.53535354 1.0380639 0.54545456 1.0426296 0.5555556 1.0456187 0.56565654 1.0466355 0.57575756 1.0453296 0.5858586 1.0414213 0.5959596 1.0347267 0.6060606 1.0251826 0.61616164 1.0128671 0.6262626 0.99801755 0.6363636 0.9810412 0.64646465 0.9625194 0.65656567 0.9432033 0.6666667 0.9239987 0.67676765 0.905942 0.68686867 0.8901639 0.6969697 0.87784463 0.7070707 0.87015796 0.7171717 0.8682082 0.72727275 0.87296134 0.7373737 0.8851723 0.74747473 0.90531397 0.75757575 0.9335094 0.7676768 0.9694723 0.7777778 1.012461 0.7878788 1.06125 0.7979798 1.1141208 0.8080808 1.1688808 0.8181818 1.2229073 0.82828283 1.273222 0.83838385 1.316596 0.8484849 1.3496838 0.85858583 1.3691826 0.86868685 1.3720154 0.8787879 1.3555263 0.8888889 1.3176868 0.8989899 1.2572964 0.90909094 1.1741706 0.9191919 1.0693046 0.9292929 0.944999 0.93939394 0.80492735 0.94949496 0.6541569 0.959596 0.49908257 0.969697 0.34728938 0.97979796 0.2073291 0.989899 0.0884071 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.15834787 1.08 1.3166958" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>ElasticInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 0.9999152 0.01010101 0.99975234 0.02020202 0.99956495 0.030303031 0.9993664 0.04040404 0.9991756 0.05050505 0.9990167 0.060606062 0.998918 0.07070707 0.9989103 0.08080808 0.99902374 0.09090909 0.9992847 0.1010101 0.9997112 0.11111111 1.0003084 0.121212125 1.0010635 0.13131313 1.0019419 0.14141414 1.0028839 0.15151516 1.0038029 0.16161616 1.0045874 0.17171717 1.0051047 0.18181819 1.0052096 0.1919192 1.0047572 0.2020202 1.0036185 0.21212122 1.001701 0.22222222 0.9989708 0.23232323 0.99547493 0.24242425 0.9913628 0.25252524 0.98690206 0.26262626 0.9824863 0.27272728 0.9786314 0.28282827 0.9759561 0.2929293 0.9751454 0.3030303 0.976894 0.3131313 0.98183054 0.32323232 0.9904251 0.33333334 1.0028844 0.34343433 1.0190431 0.35353535 1.0382628 0.36363637 1.0593514 0.37373737 1.0805187 0.3838384 1.0993878 0.3939394 1.1130737 0.4040404 1.1183479 0.41414142 1.1118927 0.42424244 1.09065 0.43434343 1.0522518 0.44444445 0.9955114 0.45454547 0.92094195 0.46464646 0.83124506 0.47474748 0.7317127 0.4848485 0.6304644 0.4949495 0.538439 0.5050505 0.46156138 0.5151515 0.36953568 0.5252525 0.26828736 0.53535354 0.16875494 0.54545456 0.07905805 0.5555556 0.0044882894 0.56565654 -0.052251697 0.57575756 -0.09064996 0.5858586 -0.1118927 0.5959596 -0.11834788 0.6060606 -0.11307371 0.61616164 -0.099387765 0.6262626 -0.08051884 0.6363636 -0.059351444 0.64646465 -0.038262963 0.65656567 -0.019043088 0.6666667 -0.0028842688 0.67676765 0.00957489 0.68686867 0.018169403 0.6969697 0.023105979 0.7070707 0.0248546 0.7171717 0.024043918 0.72727275 0.021368623 0.7373737 0.017513692 0.74747473 0.013097942 0.75757575 0.00863719 0.7676768 0.0045250654 0.7777778 0.0010291934 0.7878788 -0.0017009974 0.7979798 -0.0036184788 0.8080808 -0.004757166 0.8181818 -0.005209565 0.82828283 -0.005104661 0.83838385 -0.004587412 0.8484849 -0.0038028955 0.85858583 -0.0028839111 0.86868685 -0.0019419193 0.8787879 -0.0010634661 0.8888889 -0.00030827522 0.8989899 0.0002887845 0.90909094 0.00071531534 0.9191919 0.0009762645 0.9292929 0.0010896921 0.93939394 0.0010820031 0.94949496 0.0009832978 0.959596 0.00082439184 0.969697 0.0006335974 0.97979796 0.0004350543 0.989899 0.0002476573 1 0.00008481741" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.41201535 1.08 1.4520154" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>ElasticOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9115931 0.02020202 0.7926715 0.030303031 0.65271026 0.04040404 0.5009174 0.05050505 0.34584343 0.060606062 0.19507319 0.07070707 0.055001915 0.08080808 -0.069304705 0.09090909 -0.1741705 0.1010101 -0.2572962 0.11111111 -0.31768692 0.121212125 -0.35552645 0.13131313 -0.37201536 0.14141414 -0.3691826 0.15151516 -0.34968352 0.16161616 -0.3165959 0.17171717 -0.27322197 0.18181819 -0.22290742 0.1919192 -0.16888106 0.2020202 -0.11412096 0.21212122 -0.06124997 0.22222222 -0.012461066 0.23232323 0.03052771 0.24242425 0.06649059 0.25252524 0.09468591 0.26262626 0.11482775 0.27272728 0.12703872 0.28282827 0.13179177 0.2929293 0.12984204 0.3030303 0.12215537 0.3131313 0.1098361 0.32323232 0.09405804 0.33333334 0.07600123 0.34343433 0.05679673 0.35353535 0.037480593 0.36363637 0.018958926 0.37373737 0.0019825697 0.3838384 -0.012867093 0.3939394 -0.025182605 0.4040404 -0.03472674 0.41414142 -0.041421294 0.42424244 -0.04532957 0.43434343 -0.04663551 0.44444445 -0.045618653 0.45454547 -0.0426296 0.46464646 -0.038063884 0.47474748 -0.03233826 0.4848485 -0.025868416 0.4949495 -0.01905036 0.5050505 -0.012244105 0.5151515 -0.0057621 0.5252525 0.00013875961 0.53535354 0.005262196 0.54545456 0.009473264 0.5555556 0.012696981 0.56565654 0.014913559 0.57575756 0.016152263 0.5858586 0.016483784 0.5959596 0.016011298 0.6060606 0.014861703 0.61616164 0.013176441 0.6262626 0.011103451 0.6363636 0.008788884 0.64646465 0.0063712 0.65656567 0.0039753914 0.6666667 0.0017092228 0.67676765 -0.00033974648 0.68686867 -0.002105236 0.6969697 -0.0035429 0.7070707 -0.0046292543 0.7171717 -0.0053600073 0.72727275 -0.0057479143 0.7373737 -0.0058199167 0.74747473 -0.005613923 0.75757575 -0.005175829 0.7676768 -0.00455606 0.7777778 -0.0038070679 0.7878788 -0.0029803514 0.7979798 -0.002123952 0.8080808 -0.0012816191 0.8181818 -0.0004900694 0.82828283 0.00022059679 0.83838385 0.00082820654 0.8484849 0.0013182759 0.85858583 0.001683414 0.86868685 0.0019231439 0.8787879 0.00204283 0.8888889 0.0020526052 0.8989899 0.0019664168 0.90909094 0.0018007159 0.9191919 0.0015735626 0.9292929 0.001303494 0.93939394 0.0010085702 0.94949496 0.0007055998 0.959596 0.00040972233 0.969697 0.0001335144 0.97979796 -0.00011265278 0.989899 -0.00032150745 1 -0.00048828125" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>EndSteps(4, End)</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1 0.02020202 1 0.030303031 1 0.04040404 1 0.05050505 1 0.060606062 1 0.07070707 1 0.08080808 1 0.09090909 1 0.1010101 1 0.11111111 1 0.121212125 1 0.13131313 1 0.14141414 1 0.15151516 1 0.16161616 1 0.17171717 1 0.18181819 1 0.1919192 1 0.2020202 1 0.21212122 1 0.22222222 1 0.23232323 1 0.24242425 1 0.25252524 0.75 0.26262626 0.75 0.27272728 0.75 0.28282827 0.75 0.2929293 0.75 0.3030303 0.75 0.3131313 0.75 0.32323232 0.75 0.33333334 0.75 0.34343433 0.75 0.35353535 0.75 0.36363637 0.75 0.37373737 0.75 0.3838384 0.75 0.3939394 0.75 0.4040404 0.75 0.41414142 0.75 0.42424244 0.75 0.43434343 0.75 0.44444445 0.75 0.45454547 0.75 0.46464646 0.75 0.47474748 0.75 0.4848485 0.75 0.4949495 0.75 0.5050505 0.5 0.5151515 0.5 0.5252525 0.5 0.53535354 0.5 0.54545456 0.5 0.5555556 0.5 0.56565654 0.5 0.57575756 0.5 0.5858586 0.5 0.5959596 0.5 0.6060606 0.5 0.61616164 0.5 0.6262626 0.5 0.6363636 0.5 0.64646465 0.5 0.65656567 0.5 0.6666667 0.5 0.67676765 0.5 0.68686867 0.5 0.6969697 0.5 0.7070707 0.5 0.7171717 0.5 0.72727275 0.5 0.7373737 0.5 0.74747473 0.5 0.75757575 0.25 0.7676768 0.25 0.7777778 0.25 0.7878788 0.25 0.7979798 0.25 0.8080808 0.25 0.8181818 0.25 0.82828283 0.25 0.83838385 0.25 0.8484849 0.25 0.85858583 0.25 0.86868685 0.25 0.8787879 0.25 0.8888889 0.25 0.8989899 0.25 0.90909094 0.25 0.9191919 0.25 0.9292929 0.25 0.93939394 0.25 0.94949496 0.25 0.959596 0.25 0.969697 0.25 0.97979796 0.25 0.989899 0.25 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>ExponentialIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99992913 0.02020202 0.9998531 0.030303031 0.99977154 0.04040404 0.99968404 0.05050505 0.9995903 0.060606062 0.99948967 0.07070707 0.9993817 0.08080808 0.999266 0.09090909 0.9991419 0.1010101 0.9990088 0.11111111 0.99886596 0.121212125 0.99871284 0.13131313 0.99854857 0.14141414 0.99837244 0.15151516 0.9981835 0.16161616 0.99798083 0.17171717 0.9977635 0.18181819 0.99753046 0.1919192 0.9972804 0.2020202 0.9970123 0.21212122 0.9967247 0.22222222 0.99641633 0.23232323 0.9960855 0.24242425 0.9957307 0.25252524 0.9953502 0.26262626 0.99494207 0.27272728 0.9945044 0.28282827 0.9940349 0.2929293 0.9935314 0.3030303 0.9929914 0.3131313 0.9924122 0.32323232 0.991791 0.33333334 0.99112475 0.34343433 0.9904102 0.35353535 0.9896438 0.36363637 0.98882186 0.37373737 0.98794025 0.3838384 0.98699474 0.3939394 0.9859807 0.4040404 0.984893 0.41414142 0.9837265 0.42424244 0.9824754 0.43434343 0.9811336 0.44444445 0.97969437 0.45454547 0.97815084 0.46464646 0.9764954 0.47474748 0.9747198 0.4848485 0.9728155 0.4949495 0.9707731 0.5050505 0.9685825 0.5151515 0.96623313 0.5252525 0.9637133 0.53535354 0.96101075 0.54545456 0.9581122 0.5555556 0.9550034 0.56565654 0.9516692 0.57575756 0.9480932 0.5858586 0.94425774 0.5959596 0.9401442 0.6060606 0.9357323 0.61616164 0.9310005 0.6262626 0.92592543 0.6363636 0.9204824 0.64646465 0.91464454 0.65656567 0.90838325 0.6666667 0.90166795 0.67676765 0.8944657 0.68686867 0.886741 0.6969697 0.87845606 0.7070707 0.86957026 0.7171717 0.86004007 0.72727275 0.8498187 0.7373737 0.8388561 0.74747473 0.82709837 0.75757575 0.81448793 0.7676768 0.8009629 0.7777778 0.78645706 0.7878788 0.7708991 0.7979798 0.7542129 0.8080808 0.73631656 0.8181818 0.7171222 0.82828283 0.6965358 0.83838385 0.6744564 0.8484849 0.6507757 0.85858583 0.6253778 0.86868685 0.59813774 0.8787879 0.56892204 0.8888889 0.5375875 0.8989899 0.5039808 0.90909094 0.46793646 0.9191919 0.42927808 0.9292929 0.38781637 0.93939394 0.34334725 0.94949496 0.2956531 0.959596 0.24449998 0.969697 0.18963695 0.97979796 0.13079566 0.989899 0.06768632 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>ExponentialInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9999265 0.02020202 0.99984205 0.030303031 0.99974483 0.04040404 0.999633 0.05050505 0.9995044 0.060606062 0.9993564 0.07070707 0.9991862 0.08080808 0.9989904 0.09090909 0.99876523 0.1010101 0.9985061 0.11111111 0.99820817 0.121212125 0.9978654 0.13131313 0.99747103 0.14141414 0.99701744 0.15151516 0.99649566 0.16161616 0.9958955 0.17171717 0.9952051 0.18181819 0.99441093 0.1919192 0.9934974 0.2020202 0.99244654 0.21212122 0.9912377 0.22222222 0.9898472 0.23232323 0.9882477 0.24242425 0.98640776 0.25252524 0.98429126 0.26262626 0.98185664 0.27272728 0.9790561 0.28282827 0.9758346 0.2929293 0.97212887 0.3030303 0.9678662 0.3131313 0.96296275 0.32323232 0.95732224 0.33333334 0.950834 0.34343433 0.94337046 0.35353535 0.9347851 0.36363637 0.92490935 0.37373737 0.9135492 0.3838384 0.90048146 0.3939394 0.8854495 0.4040404 0.8681583 0.41414142 0.8482679 0.42424244 0.82538784 0.43434343 0.79906887 0.44444445 0.76879376 0.45454547 0.73396826 0.46464646 0.6939082 0.47474748 0.64782655 0.4848485 0.5948185 0.4949495 0.53384316 0.5050505 0.4661572 0.5151515 0.40518153 0.5252525 0.35217345 0.53535354 0.3060918 0.54545456 0.26603174 0.5555556 0.23120606 0.56565654 0.20093113 0.57575756 0.17461216 0.5858586 0.15173209 0.5959596 0.13184172 0.6060606 0.11455047 0.61616164 0.09951854 0.6262626 0.086450815 0.6363636 0.07509065 0.64646465 0.06521487 0.65656567 0.05662954 0.6666667 0.049165964 0.67676765 0.04267776 0.68686867 0.037037313 0.6969697 0.032133818 0.7070707 0.027871132 0.7171717 0.024165392 0.72727275 0.02094388 0.7373737 0.018143356 0.74747473 0.015708745 0.75757575 0.013592243 0.7676768 0.011752307 0.7777778 0.010152817 0.7878788 0.0087623 0.7979798 0.007553458 0.8080808 0.0065026283 0.8181818 0.005589068 0.82828283 0.0047948956 0.83838385 0.004104495 0.8484849 0.0035042763 0.85858583 0.0029825568 0.86868685 0.0025289655 0.8787879 0.0021346211 0.8888889 0.0017918348 0.8989899 0.0014938712 0.90909094 0.0012347698 0.9191919 0.0010095835 0.9292929 0.0008137822 0.93939394 0.00064361095 0.94949496 0.0004956126 0.959596 0.0003669858 0.969697 0.00025516748 0.97979796 0.00015795231 0.989899 0.00007349253 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>ExponentialOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.93231386 0.02020202 0.86920464 0.030303031 0.8103629 0.04040404 0.7555 0.05050505 0.70434695 0.060606062 0.65665287 0.07070707 0.6121839 0.08080808 0.5707219 0.09090909 0.53206354 0.1010101 0.49601936 0.11111111 0.46241236 0.121212125 0.4310779 0.13131313 0.40186226 0.14141414 0.37462223 0.15151516 0.3492242 0.16161616 0.32554352 0.17171717 0.30346417 0.18181819 0.2828778 0.1919192 0.2636835 0.2020202 0.24578714 0.21212122 0.22910082 0.22222222 0.21354294 0.23232323 0.19903708 0.24242425 0.18551207 0.25252524 0.17290169 0.26262626 0.1611439 0.27272728 0.1501813 0.28282827 0.13995993 0.2929293 0.13042974 0.3030303 0.121543944 0.3131313 0.11325908 0.32323232 0.105534375 0.33333334 0.09833205 0.34343433 0.09161675 0.35353535 0.08535546 0.36363637 0.07951766 0.37373737 0.07407457 0.3838384 0.06899953 0.3939394 0.064267695 0.4040404 0.05985582 0.41414142 0.055742264 0.42424244 0.051906824 0.43434343 0.048330784 0.44444445 0.04499656 0.45454547 0.04188782 0.46464646 0.038989246 0.47474748 0.03628671 0.4848485 0.033766866 0.4949495 0.03141749 0.5050505 0.029226959 0.5151515 0.027184486 0.5252525 0.025280178 0.53535354 0.023504615 0.54545456 0.021849155 0.5555556 0.020305634 0.56565654 0.01886642 0.57575756 0.0175246 0.5858586 0.016273499 0.5959596 0.015106976 0.6060606 0.0140193105 0.61616164 0.013005257 0.6262626 0.012059748 0.6363636 0.011178136 0.64646465 0.010356188 0.65656567 0.009589791 0.6666667 0.008875251 0.67676765 0.00820899 0.68686867 0.0075877905 0.6969697 0.007008612 0.7070707 0.006468594 0.7171717 0.0059651136 0.72727275 0.005495608 0.7373737 0.005057931 0.74747473 0.004649818 0.75757575 0.004269302 0.7676768 0.0039144754 0.7777778 0.0035836697 0.7878788 0.0032752752 0.7979798 0.0029876828 0.8080808 0.0027195811 0.8181818 0.0024695396 0.82828283 0.0022364855 0.83838385 0.002019167 0.8484849 0.0018165112 0.85858583 0.0016275644 0.86868685 0.0014514327 0.8787879 0.0012871623 0.8888889 0.001134038 0.8989899 0.0009912252 0.90909094 0.00085812807 0.9191919 0.0007339716 0.9292929 0.000618279 0.93939394 0.00051033497 0.94949496 0.00040972233 0.959596 0.00031596422 0.969697 0.0002284646 0.97979796 0.00014692545 0.989899 0.00007086992 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>Linear</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.989899 0.02020202 0.97979796 0.030303031 0.969697 0.04040404 0.959596 0.05050505 0.94949496 0.060606062 0.93939394 0.07070707 0.9292929 0.08080808 0.9191919 0.09090909 0.9090909 0.1010101 0.8989899 0.11111111 0.8888889 0.121212125 0.8787879 0.13131313 0.86868685 0.14141414 0.85858583 0.15151516 0.8484849 0.16161616 0.83838385 0.17171717 0.82828283 0.18181819 0.8181818 0.1919192 0.8080808 0.2020202 0.79797983 0.21212122 0.78787875 0.22222222 0.7777778 0.23232323 0.7676768 0.24242425 0.75757575 0.25252524 0.7474748 0.26262626 0.7373737 0.27272728 0.72727275 0.28282827 0.7171717 0.2929293 0.7070707 0.3030303 0.6969697 0.3131313 0.68686867 0.32323232 0.6767677 0.33333334 0.6666666 0.34343433 0.65656567 0.35353535 0.64646465 0.36363637 0.6363636 0.37373737 0.62626266 0.3838384 0.6161616 0.3939394 0.6060606 0.4040404 0.5959596 0.41414142 0.5858586 0.42424244 0.57575756 0.43434343 0.56565654 0.44444445 0.5555556 0.45454547 0.5454545 0.46464646 0.53535354 0.47474748 0.5252525 0.4848485 0.5151515 0.4949495 0.50505054 0.5050505 0.49494952 0.5151515 0.4848485 0.5252525 0.47474748 0.53535354 0.46464646 0.54545456 0.45454544 0.5555556 0.44444442 0.56565654 0.43434346 0.57575756 0.42424244 0.5858586 0.41414142 0.5959596 0.4040404 0.6060606 0.39393938 0.61616164 0.38383836 0.6262626 0.3737374 0.6363636 0.36363637 0.64646465 0.35353535 0.65656567 0.34343433 0.6666667 0.3333333 0.67676765 0.32323235 0.68686867 0.31313133 0.6969697 0.3030303 0.7070707 0.2929293 0.7171717 0.28282827 0.72727275 0.27272725 0.7373737 0.2626263 0.74747473 0.25252527 0.75757575 0.24242425 0.7676768 0.23232323 0.7777778 0.22222221 0.7878788 0.21212119 0.7979798 0.20202023 0.8080808 0.19191921 0.8181818 0.18181819 0.82828283 0.17171717 0.83838385 0.16161615 0.8484849 0.15151513 0.85858583 0.14141417 0.86868685 0.13131315 0.8787879 0.121212125 0.8888889 0.111111104 0.8989899 0.101010084 0.90909094 0.090909064 0.9191919 0.0808081 0.9292929 0.07070708 0.93939394 0.060606062 0.94949496 0.050505042 0.959596 0.04040402 0.969697 0.030303001 0.97979796 0.02020204 0.989899 0.01010102 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>NoneSteps(4, None)</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1 0.02020202 1 0.030303031 1 0.04040404 1 0.05050505 1 0.060606062 1 0.07070707 1 0.08080808 1 0.09090909 1 0.1010101 1 0.11111111 1 0.121212125 1 0.13131313 1 0.14141414 1 0.15151516 1 0.16161616 1 0.17171717 1 0.18181819 1 0.1919192 1 0.2020202 1 0.21212122 1 0.22222222 1 0.23232323 1 0.24242425 1 0.25252524 0.6666666 0.26262626 0.6666666 0.27272728 0.6666666 0.28282827 0.6666666 0.2929293 0.6666666 0.3030303 0.6666666 0.3131313 0.6666666 0.32323232 0.6666666 0.33333334 0.6666666 0.34343433 0.6666666 0.35353535 0.6666666 0.36363637 0.6666666 0.37373737 0.6666666 0.3838384 0.6666666 0.3939394 0.6666666 0.4040404 0.6666666 0.41414142 0.6666666 0.42424244 0.6666666 0.43434343 0.6666666 0.44444445 0.6666666 0.45454547 0.6666666 0.46464646 0.6666666 0.47474748 0.6666666 0.4848485 0.6666666 0.4949495 0.6666666 0.5050505 0.3333333 0.5151515 0.3333333 0.5252525 0.3333333 0.53535354 0.3333333 0.54545456 0.3333333 0.5555556 0.3333333 0.56565654 0.3333333 0.57575756 0.3333333 0.5858586 0.3333333 0.5959596 0.3333333 0.6060606 0.3333333 0.61616164 0.3333333 0.6262626 0.3333333 0.6363636 0.3333333 0.64646465 0.3333333 0.65656567 0.3333333 0.6666667 0.3333333 0.67676765 0.3333333 0.68686867 0.3333333 0.6969697 0.3333333 0.7070707 0.3333333 0.7171717 0.3333333 0.72727275 0.3333333 0.7373737 0.3333333 0.74747473 0.3333333 0.75757575 0 0.7676768 0 0.7777778 0 0.7878788 0 0.7979798 0 0.8080808 0 0.8181818 0 0.82828283 0 0.83838385 0 0.8484849 0 0.85858583 0 0.86868685 0 0.8787879 0 0.8888889 0 0.8989899 0 0.90909094 0 0.9191919 0 0.9292929 0 0.93939394 0 0.94949496 0 0.959596 0 0.969697 0 0.97979796 0 0.989899 0 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuadraticIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99989796 0.02020202 0.9995919 0.030303031 0.99908173 0.04040404 0.9983675 0.05050505 0.9974492 0.060606062 0.9963269 0.07070707 0.9950005 0.08080808 0.9934701 0.09090909 0.9917355 0.1010101 0.98979694 0.11111111 0.9876543 0.121212125 0.98530763 0.13131313 0.98275685 0.14141414 0.98000205 0.15151516 0.97704315 0.16161616 0.97388023 0.17171717 0.9705132 0.18181819 0.96694213 0.1919192 0.963167 0.2020202 0.95918787 0.21212122 0.9550046 0.22222222 0.9506173 0.23232323 0.9460259 0.24242425 0.9412305 0.25252524 0.936231 0.26262626 0.9310275 0.27272728 0.92561984 0.28282827 0.9200082 0.2929293 0.91419244 0.3030303 0.9081726 0.3131313 0.9019488 0.32323232 0.89552087 0.33333334 0.8888889 0.34343433 0.88205284 0.35353535 0.87501276 0.36363637 0.8677686 0.37373737 0.8603204 0.3838384 0.8526681 0.3939394 0.84481174 0.4040404 0.83675134 0.41414142 0.8284869 0.42424244 0.82001835 0.43434343 0.8113458 0.44444445 0.80246913 0.45454547 0.7933884 0.46464646 0.78410363 0.47474748 0.7746148 0.4848485 0.7649219 0.4949495 0.755025 0.5050505 0.744924 0.5151515 0.7346189 0.5252525 0.72410977 0.53535354 0.71339655 0.54545456 0.70247936 0.5555556 0.691358 0.56565654 0.6800327 0.57575756 0.6685032 0.5858586 0.65676975 0.5959596 0.64483213 0.6060606 0.63269055 0.61616164 0.6203448 0.6262626 0.6077951 0.6363636 0.59504133 0.64646465 0.58208346 0.65656567 0.56892157 0.6666667 0.5555555 0.67676765 0.5419855 0.68686867 0.5282115 0.6969697 0.51423323 0.7070707 0.500051 0.7171717 0.48566473 0.72727275 0.47107434 0.7373737 0.45628 0.74747473 0.44128156 0.75757575 0.42607898 0.7676768 0.41067237 0.7777778 0.39506173 0.7878788 0.37924695 0.7979798 0.36322826 0.8080808 0.34700543 0.8181818 0.3305785 0.82828283 0.31394756 0.83838385 0.29711252 0.8484849 0.2800734 0.85858583 0.26283038 0.86868685 0.24538314 0.8787879 0.22773188 0.8888889 0.20987654 0.8989899 0.1918171 0.90909094 0.17355365 0.9191919 0.15508628 0.9292929 0.13641465 0.93939394 0.11753905 0.94949496 0.0984593 0.959596 0.07917553 0.969697 0.059687734 0.97979796 0.03999597 0.989899 0.020099998 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuadraticInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9997959 0.02020202 0.9991838 0.030303031 0.99816346 0.04040404 0.99673504 0.05050505 0.9948985 0.060606062 0.9926538 0.07070707 0.990001 0.08080808 0.9869401 0.09090909 0.9834711 0.1010101 0.97959393 0.11111111 0.97530866 0.121212125 0.97061527 0.13131313 0.9655137 0.14141414 0.9600041 0.15151516 0.9540863 0.16161616 0.94776046 0.17171717 0.94102645 0.18181819 0.93388426 0.1919192 0.926334 0.2020202 0.9183757 0.21212122 0.91000915 0.22222222 0.90123457 0.23232323 0.8920518 0.24242425 0.88246095 0.25252524 0.87246203 0.26262626 0.8620549 0.27272728 0.8512397 0.28282827 0.84001637 0.2929293 0.8283849 0.3030303 0.8163453 0.3131313 0.80389756 0.32323232 0.79104173 0.33333334 0.7777778 0.34343433 0.76410574 0.35353535 0.7500255 0.36363637 0.7355372 0.37373737 0.7206408 0.3838384 0.7053362 0.3939394 0.6896235 0.4040404 0.6735027 0.41414142 0.6569738 0.42424244 0.6400367 0.43434343 0.6226916 0.44444445 0.60493827 0.45454547 0.58677685 0.46464646 0.5682073 0.47474748 0.5492296 0.4848485 0.52984387 0.4949495 0.51005 0.5050505 0.48995006 0.5151515 0.47015613 0.5252525 0.45077038 0.53535354 0.43179268 0.54545456 0.41322315 0.5555556 0.39506167 0.56565654 0.3773085 0.57575756 0.3599633 0.5858586 0.34302622 0.5959596 0.32649732 0.6060606 0.31037647 0.61616164 0.2946638 0.6262626 0.27935928 0.6363636 0.26446283 0.64646465 0.24997449 0.65656567 0.23589426 0.6666667 0.22222221 0.67676765 0.20895833 0.68686867 0.19610244 0.6969697 0.18365473 0.7070707 0.17161512 0.7171717 0.15998363 0.72727275 0.14876032 0.7373737 0.13794512 0.74747473 0.12753803 0.75757575 0.11753905 0.7676768 0.107948184 0.7777778 0.09876543 0.7878788 0.089990795 0.7979798 0.08162433 0.8080808 0.07366598 0.8181818 0.06611574 0.82828283 0.05897355 0.83838385 0.052239537 0.8484849 0.045913696 0.85858583 0.03999591 0.86868685 0.034486294 0.8787879 0.029384732 0.8888889 0.024691343 0.8989899 0.020406067 0.90909094 0.016528904 0.9191919 0.013059914 0.9292929 0.009998977 0.93939394 0.007346213 0.94949496 0.005101502 0.959596 0.0032649636 0.969697 0.0018365383 0.97979796 0.000816226 0.989899 0.0002040863 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuadraticOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9799 0.02020202 0.96000403 0.030303031 0.94031227 0.04040404 0.92082447 0.05050505 0.9015407 0.060606062 0.88246095 0.07070707 0.86358535 0.08080808 0.8449137 0.09090909 0.82644624 0.1010101 0.8081829 0.11111111 0.79012346 0.121212125 0.7722681 0.13131313 0.75461686 0.14141414 0.7371696 0.15151516 0.7199266 0.16161616 0.7028875 0.17171717 0.68605244 0.18181819 0.6694215 0.1919192 0.6529946 0.2020202 0.6367718 0.21212122 0.62075293 0.22222222 0.60493827 0.23232323 0.58932763 0.24242425 0.573921 0.25252524 0.55871856 0.26262626 0.54372 0.27272728 0.52892566 0.28282827 0.5143353 0.2929293 0.49994898 0.3030303 0.48576677 0.3131313 0.47178853 0.32323232 0.4580145 0.33333334 0.44444442 0.34343433 0.43107843 0.35353535 0.41791654 0.36363637 0.40495867 0.37373737 0.39220488 0.3838384 0.37965512 0.3939394 0.36730945 0.4040404 0.35516787 0.41414142 0.34323025 0.42424244 0.33149678 0.43434343 0.31996733 0.44444445 0.30864203 0.45454547 0.29752064 0.46464646 0.28660345 0.47474748 0.27589023 0.4848485 0.2653811 0.4949495 0.25507605 0.5050505 0.24497503 0.5151515 0.2350781 0.5252525 0.22538519 0.53535354 0.21589637 0.54545456 0.20661157 0.5555556 0.19753087 0.56565654 0.18865424 0.57575756 0.17998165 0.5858586 0.17151308 0.5959596 0.16324866 0.6060606 0.1551882 0.61616164 0.1473319 0.6262626 0.13967967 0.6363636 0.13223141 0.64646465 0.124987245 0.65656567 0.11794716 0.6666667 0.111111104 0.67676765 0.104479134 0.68686867 0.09805125 0.6969697 0.09182739 0.7070707 0.08580756 0.7171717 0.07999182 0.72727275 0.07438016 0.7373737 0.06897259 0.74747473 0.06376898 0.75757575 0.058769524 0.7676768 0.053974092 0.7777778 0.049382687 0.7878788 0.044995427 0.7979798 0.040812194 0.8080808 0.03683299 0.8181818 0.03305787 0.82828283 0.029486775 0.83838385 0.026119769 0.8484849 0.022956848 0.85858583 0.019997954 0.86868685 0.017243147 0.8787879 0.014692366 0.8888889 0.012345672 0.8989899 0.0102030635 0.90909094 0.008264482 0.9191919 0.0065299273 0.9292929 0.0049995184 0.93939394 0.0036730766 0.94949496 0.0025507808 0.959596 0.0016325116 0.969697 0.00091826916 0.97979796 0.000408113 0.989899 0.00010204315 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuarticIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1 0.02020202 0.9999998 0.030303031 0.99999917 0.04040404 0.9999973 0.05050505 0.9999935 0.060606062 0.9999865 0.07070707 0.999975 0.08080808 0.9999574 0.09090909 0.9999317 0.1010101 0.9998959 0.11111111 0.9998476 0.121212125 0.9997841 0.13131313 0.9997027 0.14141414 0.99960005 0.15151516 0.999473 0.16161616 0.99931777 0.17171717 0.99913055 0.18181819 0.99890715 0.1919192 0.99864334 0.2020202 0.99833435 0.21212122 0.9979754 0.22222222 0.99756134 0.23232323 0.9970868 0.24242425 0.99654615 0.25252524 0.99593353 0.26262626 0.9952428 0.27272728 0.9944676 0.28282827 0.9936013 0.2929293 0.99263704 0.3030303 0.99156773 0.3131313 0.99038595 0.32323232 0.9890841 0.33333334 0.9876543 0.34343433 0.98608845 0.35353535 0.9843782 0.36363637 0.98251486 0.37373737 0.9804896 0.3838384 0.9782933 0.3939394 0.9759166 0.4040404 0.97334987 0.41414142 0.97058326 0.42424244 0.9676066 0.43434343 0.9644096 0.44444445 0.96098155 0.45454547 0.95731163 0.46464646 0.95338875 0.47474748 0.9492015 0.4848485 0.9447383 0.4949495 0.93998724 0.5050505 0.9349362 0.5151515 0.9295729 0.5252525 0.9238846 0.53535354 0.9178585 0.54545456 0.91148144 0.5555556 0.9047401 0.56565654 0.8976209 0.57575756 0.8901099 0.5858586 0.88219297 0.5959596 0.8738558 0.6060606 0.86508375 0.61616164 0.85586196 0.6262626 0.8461754 0.6363636 0.8360085 0.64646465 0.82534575 0.65656567 0.8141714 0.6666667 0.80246913 0.67676765 0.79022276 0.68686867 0.7774156 0.6969697 0.7640307 0.7070707 0.750051 0.7171717 0.7354592 0.72727275 0.7202377 0.7373737 0.7043686 0.74747473 0.68783367 0.75757575 0.67061466 0.7676768 0.6526929 0.7777778 0.63404965 0.7878788 0.6146657 0.7979798 0.59452176 0.8080808 0.57359815 0.8181818 0.5518749 0.82828283 0.52933204 0.83838385 0.5059492 0.8484849 0.48170573 0.85858583 0.45658094 0.86868685 0.43055338 0.8787879 0.40360194 0.8888889 0.37570494 0.8989899 0.34684044 0.90909094 0.31698644 0.9191919 0.28612077 0.9292929 0.25422037 0.93939394 0.22126263 0.94949496 0.18722439 0.959596 0.15208232 0.969697 0.11581284 0.97979796 0.07839227 0.989899 0.039795995 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuarticInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99999994 0.02020202 0.9999987 0.030303031 0.99999326 0.04040404 0.99997866 0.05050505 0.99994797 0.060606062 0.99989206 0.07070707 0.9998 0.08080808 0.9996589 0.09090909 0.9994536 0.1010101 0.9991672 0.11111111 0.99878067 0.121212125 0.9982731 0.13131313 0.9976214 0.14141414 0.99680066 0.15151516 0.99578387 0.16161616 0.99454206 0.17171717 0.99304426 0.18181819 0.9912574 0.1919192 0.98914665 0.2020202 0.98667496 0.21212122 0.98380333 0.22222222 0.9804908 0.23232323 0.9766944 0.24242425 0.97236913 0.25252524 0.96746814 0.26262626 0.9619423 0.27272728 0.95574075 0.28282827 0.94881046 0.2929293 0.9410965 0.3030303 0.93254185 0.3131313 0.92308766 0.32323232 0.9126729 0.33333334 0.90123457 0.34343433 0.88870776 0.35353535 0.8750255 0.36363637 0.86011887 0.37373737 0.84391683 0.3838384 0.82634646 0.3939394 0.8073329 0.4040404 0.7867991 0.41414142 0.764666 0.42424244 0.74085283 0.43434343 0.7152767 0.44444445 0.6878525 0.45454547 0.6584932 0.46464646 0.6271102 0.47474748 0.5936122 0.4848485 0.5579064 0.4949495 0.519898 0.5050505 0.48010212 0.5151515 0.4420936 0.5252525 0.4063878 0.53535354 0.37288982 0.54545456 0.34150672 0.5555556 0.3121475 0.56565654 0.2847234 0.57575756 0.25914717 0.5858586 0.23533398 0.5959596 0.21320093 0.6060606 0.19266713 0.61616164 0.17365348 0.6262626 0.15608323 0.6363636 0.13988113 0.64646465 0.12497449 0.65656567 0.11129224 0.6666667 0.09876543 0.67676765 0.08732712 0.68686867 0.07691234 0.6969697 0.06745815 0.7070707 0.058903515 0.7171717 0.05118954 0.72727275 0.04425925 0.7373737 0.038057745 0.74747473 0.032531917 0.75757575 0.027630866 0.7676768 0.023305595 0.7777778 0.019509196 0.7878788 0.016196668 0.7979798 0.013325095 0.8080808 0.01085335 0.8181818 0.008742571 0.82828283 0.006955743 0.83838385 0.0054579377 0.8484849 0.0042161345 0.85858583 0.003199339 0.86868685 0.002378583 0.8787879 0.0017269254 0.8888889 0.0012193322 0.8989899 0.0008327961 0.90909094 0.0005463958 0.9191919 0.00034111738 0.9292929 0.00019997358 0.93939394 0.00010794401 0.94949496 0.000052034855 0.959596 0.000021338463 0.969697 0.000006735325 0.97979796 0.0000013113022 0.989899 0.000000059604645 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuarticOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.960204 0.02020202 0.92160773 0.030303031 0.88418716 0.04040404 0.8479177 0.05050505 0.8127756 0.060606062 0.77873737 0.07070707 0.74577963 0.08080808 0.7138792 0.09090909 0.6830134 0.1010101 0.65315956 0.11111111 0.62429506 0.121212125 0.59639806 0.13131313 0.5694466 0.14141414 0.54341906 0.15151516 0.5182943 0.16161616 0.4940508 0.17171717 0.47066796 0.18181819 0.44812512 0.1919192 0.42640185 0.2020202 0.4054783 0.21212122 0.3853342 0.22222222 0.36595035 0.23232323 0.3473071 0.24242425 0.32938534 0.25252524 0.31216645 0.26262626 0.2956314 0.27272728 0.27976233 0.28282827 0.2645408 0.2929293 0.24994898 0.3030303 0.2359693 0.3131313 0.22258443 0.32323232 0.2097773 0.33333334 0.1975308 0.34343433 0.18582863 0.35353535 0.17465425 0.36363637 0.16399151 0.37373737 0.15382469 0.3838384 0.14413798 0.3939394 0.13491625 0.4040404 0.12614417 0.41414142 0.11780703 0.42424244 0.1098901 0.43434343 0.10237908 0.44444445 0.095259905 0.45454547 0.0885185 0.46464646 0.08214152 0.47474748 0.07611543 0.4848485 0.07042712 0.4949495 0.065063775 0.5050505 0.060012758 0.5151515 0.05526167 0.5252525 0.050798476 0.53535354 0.04661125 0.54545456 0.04268831 0.5555556 0.039018452 0.56565654 0.03559041 0.57575756 0.032393396 0.5858586 0.02941674 0.5959596 0.02665013 0.6060606 0.024083376 0.61616164 0.0217067 0.6262626 0.019510388 0.6363636 0.017485142 0.64646465 0.015621781 0.65656567 0.013911545 0.6666667 0.012345672 0.67676765 0.010915875 0.68686867 0.00961405 0.6969697 0.008432269 0.7070707 0.007362962 0.7171717 0.006398678 0.72727275 0.005532384 0.7373737 0.0047572255 0.74747473 0.0040664673 0.75757575 0.0034538507 0.7676768 0.002913177 0.7777778 0.0024386644 0.7878788 0.002024591 0.7979798 0.0016656518 0.8080808 0.0013566613 0.8181818 0.0010928512 0.82828283 0.00086945295 0.83838385 0.00068223476 0.8484849 0.00052702427 0.85858583 0.00039994717 0.86868685 0.00029730797 0.8787879 0.00021588802 0.8888889 0.00015240908 0.8989899 0.000104129314 0.90909094 0.00006830692 0.9191919 0.00004261732 0.9292929 0.000024974346 0.93939394 0.00001347065 0.94949496 0.0000064969063 0.959596 0.000002682209 0.969697 0.000000834465 0.97979796 0.00000017881393 0.989899 0 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuinticIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1 0.02020202 1 0.030303031 1 0.04040404 0.9999999 0.05050505 0.99999964 0.060606062 0.99999917 0.07070707 0.9999982 0.08080808 0.99999654 0.09090909 0.9999938 0.1010101 0.9999895 0.11111111 0.9999831 0.121212125 0.99997383 0.13131313 0.99996096 0.14141414 0.99994344 0.15151516 0.9999201 0.16161616 0.99988973 0.17171717 0.9998507 0.18181819 0.9998013 0.1919192 0.99973965 0.2020202 0.99966353 0.21212122 0.99957055 0.22222222 0.9994581 0.23232323 0.9993232 0.24242425 0.9991627 0.25252524 0.99897313 0.26262626 0.9987506 0.27272728 0.99849117 0.28282827 0.9981903 0.2929293 0.9978432 0.3030303 0.99744475 0.3131313 0.99698955 0.32323232 0.99647164 0.33333334 0.9958848 0.34343433 0.99522233 0.35353535 0.99447715 0.36363637 0.9936418 0.37373737 0.9927082 0.3838384 0.99166816 0.3939394 0.9905126 0.4040404 0.9892323 0.41414142 0.9878173 0.42424244 0.9862574 0.43434343 0.98454154 0.44444445 0.98265845 0.45454547 0.9805962 0.46464646 0.97834224 0.47474748 0.97588354 0.4848485 0.97320646 0.4949495 0.97029674 0.5050505 0.96713954 0.5151515 0.96371937 0.5252525 0.9600202 0.53535354 0.95602524 0.54545456 0.95171714 0.5555556 0.9470779 0.56565654 0.9420886 0.57575756 0.9367299 0.5858586 0.93098176 0.5959596 0.92482316 0.6060606 0.91823256 0.61616164 0.91118765 0.6262626 0.90366536 0.6363636 0.89564174 0.64646465 0.88709223 0.65656567 0.8779913 0.6666667 0.8683127 0.67676765 0.85802954 0.68686867 0.8471137 0.6969697 0.83553654 0.7070707 0.8232684 0.7171717 0.81027883 0.72727275 0.7965365 0.7373737 0.7820091 0.74747473 0.76666355 0.75757575 0.75046563 0.7676768 0.73338044 0.7777778 0.71537197 0.7878788 0.69640326 0.7979798 0.67643654 0.8080808 0.6554328 0.8181818 0.63335216 0.82828283 0.6101538 0.83838385 0.58579576 0.8484849 0.56023514 0.85858583 0.5334281 0.86868685 0.5053292 0.8787879 0.4758926 0.8888889 0.44507104 0.8989899 0.41281617 0.90909094 0.37907857 0.9191919 0.343808 0.9292929 0.3069523 0.93939394 0.26845884 0.94949496 0.22827363 0.959596 0.18634158 0.969697 0.14260638 0.97979796 0.09701061 0.989899 0.04949504 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuinticInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 1 0.02020202 0.99999994 0.030303031 0.9999996 0.04040404 0.9999983 0.05050505 0.99999475 0.060606062 0.99998695 0.07070707 0.99997175 0.08080808 0.99994487 0.09090909 0.99990064 0.1010101 0.99983174 0.11111111 0.99972904 0.121212125 0.99958134 0.13131313 0.99937534 0.14141414 0.99909514 0.15151516 0.9987224 0.16161616 0.9982358 0.17171717 0.99761117 0.18181819 0.99682087 0.1919192 0.99583405 0.2020202 0.99461615 0.21212122 0.99312866 0.22222222 0.99132925 0.23232323 0.98917115 0.24242425 0.9866032 0.25252524 0.98356974 0.26262626 0.9800101 0.27272728 0.97585857 0.28282827 0.9710443 0.2929293 0.9654909 0.3030303 0.9591163 0.3131313 0.9518327 0.32323232 0.9435461 0.33333334 0.93415636 0.34343433 0.92355686 0.35353535 0.9116342 0.36363637 0.8982682 0.37373737 0.8833318 0.3838384 0.8666902 0.3939394 0.84820163 0.4040404 0.8277164 0.41414142 0.8050769 0.42424244 0.7801176 0.43434343 0.75266457 0.44444445 0.7225355 0.45454547 0.6895393 0.46464646 0.6534761 0.47474748 0.6141368 0.4848485 0.5713032 0.4949495 0.5247475 0.5050505 0.47525263 0.5151515 0.4286968 0.5252525 0.38586318 0.53535354 0.34652388 0.54545456 0.31046063 0.5555556 0.2774644 0.56565654 0.24733543 0.57575756 0.21988243 0.5858586 0.1949231 0.5959596 0.17228359 0.6060606 0.15179831 0.61616164 0.13330972 0.6262626 0.116668284 0.6363636 0.10173178 0.64646465 0.08836579 0.65656567 0.076443136 0.6666667 0.06584358 0.67676765 0.056453943 0.68686867 0.048167348 0.6969697 0.04088372 0.7070707 0.034509122 0.7171717 0.028955698 0.72727275 0.02414143 0.7373737 0.019989908 0.74747473 0.016430259 0.75757575 0.0133968 0.7676768 0.010828853 0.7777778 0.008670747 0.7878788 0.0068713427 0.7979798 0.005383849 0.8080808 0.0041659474 0.8181818 0.003179133 0.82828283 0.002388835 0.83838385 0.0017641783 0.8484849 0.0012776256 0.85858583 0.0009048581 0.86868685 0.0006246567 0.8787879 0.00041866302 0.8888889 0.00027096272 0.8989899 0.00016826391 0.90909094 0.00009936094 0.9191919 0.000055134296 0.9292929 0.000028252602 0.93939394 0.000013053417 0.94949496 0.0000052452087 0.959596 0.0000017285347 0.969697 0.0000004172325 0.97979796 0.000000059604645 0.989899 0 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>QuinticOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.95050496 0.02020202 0.9029894 0.030303031 0.8573936 0.04040404 0.8136584 0.05050505 0.77172637 0.060606062 0.73154116 0.07070707 0.6930477 0.08080808 0.656192 0.09090909 0.62092125 0.1010101 0.58718383 0.11111111 0.55492896 0.121212125 0.5241074 0.13131313 0.4946708 0.14141414 0.46657193 0.15151516 0.43976486 0.16161616 0.41420424 0.17171717 0.3898462 0.18181819 0.36664784 0.1919192 0.34456718 0.2020202 0.32356352 0.21212122 0.30359662 0.22222222 0.28462803 0.23232323 0.26661956 0.24242425 0.24953437 0.25252524 0.23333657 0.26262626 0.21799088 0.27272728 0.2034635 0.28282827 0.18972117 0.2929293 0.17673159 0.3030303 0.16446346 0.3131313 0.15288627 0.32323232 0.14197052 0.33333334 0.13168722 0.34343433 0.12200868 0.35353535 0.11290777 0.36363637 0.104358256 0.37373737 0.096334696 0.3838384 0.08881229 0.3939394 0.08176744 0.4040404 0.075176835 0.41414142 0.069018245 0.42424244 0.06327009 0.43434343 0.057911396 0.44444445 0.05292213 0.45454547 0.048282802 0.46464646 0.043974757 0.47474748 0.039979815 0.4848485 0.036280632 0.4949495 0.032860518 0.5050505 0.02970326 0.5151515 0.02679354 0.5252525 0.024116457 0.53535354 0.021657765 0.54545456 0.019403815 0.5555556 0.017341495 0.56565654 0.015458465 0.57575756 0.013742626 0.5858586 0.012182713 0.5959596 0.010767698 0.6060606 0.0094873905 0.61616164 0.008331835 0.6262626 0.007291794 0.6363636 0.0063582063 0.64646465 0.005522847 0.65656567 0.00477767 0.6666667 0.004115224 0.67676765 0.0035283566 0.68686867 0.0030104518 0.6969697 0.0025552511 0.7070707 0.002156794 0.7171717 0.0018097162 0.72727275 0.001508832 0.7373737 0.001249373 0.74747473 0.0010268688 0.75757575 0.00083732605 0.7676768 0.00067681074 0.7777778 0.00054192543 0.7878788 0.00042945147 0.7979798 0.00033646822 0.8080808 0.0002603531 0.8181818 0.00019872189 0.82828283 0.00014930964 0.83838385 0.00011026859 0.8484849 0.000079870224 0.85858583 0.000056564808 0.86868685 0.000039041042 0.8787879 0.000026166439 0.8888889 0.00001692772 0.8989899 0.0000104904175 0.90909094 0.000006198883 0.9191919 0.0000034570694 0.9292929 0.0000017881393 0.93939394 0.000000834465 0.94949496 0.00000035762787 0.959596 0.00000011920929 0.969697 0 0.97979796 0 0.989899 0 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,3 @@
# EaseFunction
These graphs are auto-generated via `tools/build-easefunction-graphs`.

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SineIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9998741 0.02020202 0.9994965 0.030303031 0.99886733 0.04040404 0.9979867 0.05050505 0.9968548 0.060606062 0.9954719 0.07070707 0.9938385 0.08080808 0.9919548 0.09090909 0.98982143 0.1010101 0.9874389 0.11111111 0.9848077 0.121212125 0.9819287 0.13131313 0.97880244 0.14141414 0.9754298 0.15151516 0.9718116 0.16161616 0.9679487 0.17171717 0.96384215 0.18181819 0.959493 0.1919192 0.95490223 0.2020202 0.9500711 0.21212122 0.9450008 0.22222222 0.9396926 0.23232323 0.93414783 0.24242425 0.9283679 0.25252524 0.9223543 0.26262626 0.9161084 0.27272728 0.90963197 0.28282827 0.90292656 0.2929293 0.89599377 0.3030303 0.88883543 0.3131313 0.88145334 0.32323232 0.8738494 0.33333334 0.8660254 0.34343433 0.8579834 0.35353535 0.8497254 0.36363637 0.8412535 0.37373737 0.83256984 0.3838384 0.8236766 0.3939394 0.8145759 0.4040404 0.80527025 0.41414142 0.7957618 0.42424244 0.78605306 0.43434343 0.7761465 0.44444445 0.76604444 0.45454547 0.7557495 0.46464646 0.7452645 0.47474748 0.7345917 0.4848485 0.72373396 0.4949495 0.71269417 0.5050505 0.7014749 0.5151515 0.69007903 0.5252525 0.67850935 0.53535354 0.66676897 0.54545456 0.6548607 0.5555556 0.6427876 0.56565654 0.63055265 0.57575756 0.618159 0.5858586 0.60560966 0.5959596 0.5929079 0.6060606 0.58005685 0.61616164 0.5670598 0.6262626 0.5539201 0.6363636 0.54064083 0.64646465 0.52722543 0.65656567 0.51367736 0.6666667 0.5 0.67676765 0.4861967 0.68686867 0.47227103 0.6969697 0.45822644 0.7070707 0.44406652 0.7171717 0.42979485 0.72727275 0.41541493 0.7373737 0.40093058 0.74747473 0.38634515 0.75757575 0.3716625 0.7676768 0.35688627 0.7777778 0.34202003 0.7878788 0.32706785 0.7979798 0.3120334 0.8080808 0.29692036 0.8181818 0.28173256 0.82828283 0.26647377 0.83838385 0.251148 0.8484849 0.2357589 0.85858583 0.22031045 0.86868685 0.20480663 0.8787879 0.18925118 0.8888889 0.17364812 0.8989899 0.1580013 0.90909094 0.14231473 0.9191919 0.12659246 0.9292929 0.110838234 0.93939394 0.09505606 0.94949496 0.07924998 0.959596 0.06342393 0.969697 0.047581792 0.97979796 0.03172791 0.989899 0.015865922 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SineInOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99974823 0.02020202 0.99899334 0.030303031 0.997736 0.04040404 0.9959774 0.05050505 0.99371946 0.060606062 0.99096435 0.07070707 0.9877149 0.08080808 0.98397434 0.09090909 0.97974646 0.1010101 0.97503555 0.11111111 0.9698463 0.121212125 0.9641839 0.13131313 0.9580542 0.14141414 0.9514633 0.15151516 0.9444177 0.16161616 0.9369247 0.17171717 0.9289917 0.18181819 0.92062676 0.1919192 0.9118383 0.2020202 0.9026351 0.21212122 0.89302653 0.22222222 0.8830222 0.23232323 0.87263227 0.24242425 0.86186695 0.25252524 0.85073745 0.26262626 0.8392547 0.27272728 0.82743037 0.28282827 0.8152763 0.2929293 0.8028048 0.3030303 0.79002845 0.3131313 0.77696 0.32323232 0.76361275 0.33333334 0.75 0.34343433 0.7361355 0.35353535 0.72203326 0.36363637 0.70770746 0.37373737 0.6931726 0.3838384 0.67844313 0.3939394 0.6635339 0.4040404 0.64846015 0.41414142 0.6332369 0.42424244 0.61787945 0.43434343 0.6024033 0.44444445 0.58682406 0.45454547 0.57115734 0.46464646 0.5554191 0.47474748 0.539625 0.4848485 0.5237909 0.4949495 0.50793296 0.5050505 0.49206704 0.5151515 0.47620904 0.5252525 0.46037495 0.53535354 0.44458085 0.54545456 0.42884254 0.5555556 0.41317582 0.56565654 0.39759666 0.57575756 0.3821205 0.5858586 0.36676306 0.5959596 0.3515398 0.6060606 0.33646595 0.61616164 0.3215568 0.6262626 0.30682743 0.6363636 0.29229248 0.64646465 0.27796668 0.65656567 0.26386446 0.6666667 0.25 0.67676765 0.23638725 0.68686867 0.22303993 0.6969697 0.20997155 0.7070707 0.19719511 0.7171717 0.18472362 0.72727275 0.17256957 0.7373737 0.16074532 0.74747473 0.14926255 0.75757575 0.13813299 0.7676768 0.1273678 0.7777778 0.11697769 0.7878788 0.10697341 0.7979798 0.0973649 0.8080808 0.08816171 0.8181818 0.07937324 0.82828283 0.071008265 0.83838385 0.063075304 0.8484849 0.055582285 0.85858583 0.048536718 0.86868685 0.041945755 0.8787879 0.035816014 0.8888889 0.030153632 0.8989899 0.024964452 0.90909094 0.02025348 0.9191919 0.016025662 0.9292929 0.012285113 0.93939394 0.009035647 0.94949496 0.0062805414 0.959596 0.0040225983 0.969697 0.0022640228 0.97979796 0.0010066628 0.989899 0.00025177002 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SineOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.984134 0.02020202 0.9682721 0.030303031 0.9524181 0.04040404 0.93657607 0.05050505 0.92075 0.060606062 0.90494394 0.07070707 0.8891618 0.08080808 0.87340754 0.09090909 0.85768515 0.1010101 0.8419986 0.11111111 0.8263518 0.121212125 0.81074876 0.13131313 0.7951933 0.14141414 0.77968943 0.15151516 0.76424104 0.16161616 0.748852 0.17171717 0.7335262 0.18181819 0.71826744 0.1919192 0.70307964 0.2020202 0.6879666 0.21212122 0.672932 0.22222222 0.65797985 0.23232323 0.6431138 0.24242425 0.6283375 0.25252524 0.61365485 0.26262626 0.5990695 0.27272728 0.58458495 0.28282827 0.5702051 0.2929293 0.55593336 0.3030303 0.54177344 0.3131313 0.5277289 0.32323232 0.51380324 0.33333334 0.5 0.34343433 0.48632258 0.35353535 0.4727745 0.36363637 0.45935917 0.37373737 0.44607997 0.3838384 0.43294013 0.3939394 0.41994303 0.4040404 0.40709203 0.41414142 0.39439029 0.42424244 0.381841 0.43434343 0.3694473 0.44444445 0.35721236 0.45454547 0.3451392 0.46464646 0.33323097 0.47474748 0.3214906 0.4848485 0.30992097 0.4949495 0.2985251 0.5050505 0.28730583 0.5151515 0.27626598 0.5252525 0.26540828 0.53535354 0.25473553 0.54545456 0.24425042 0.5555556 0.2339555 0.56565654 0.22385353 0.57575756 0.21394688 0.5858586 0.20423812 0.5959596 0.19472975 0.6060606 0.18542403 0.61616164 0.17632341 0.6262626 0.16743016 0.6363636 0.15874648 0.64646465 0.15027457 0.65656567 0.14201659 0.6666667 0.13397455 0.67676765 0.12615061 0.68686867 0.118546605 0.6969697 0.11116451 0.7070707 0.10400617 0.7171717 0.097073436 0.72727275 0.09036797 0.7373737 0.08389157 0.74747473 0.07764572 0.75757575 0.07163209 0.7676768 0.065852165 0.7777778 0.060307324 0.7878788 0.054999113 0.7979798 0.049928904 0.8080808 0.04509777 0.8181818 0.04050702 0.82828283 0.036157846 0.83838385 0.032051265 0.8484849 0.028188407 0.85858583 0.024570227 0.86868685 0.021197557 0.8787879 0.018071294 0.8888889 0.015192211 0.8989899 0.012561083 0.90909094 0.010178566 0.9191919 0.0080451965 0.9292929 0.006161511 0.93939394 0.0045281053 0.94949496 0.003145218 0.959596 0.0020133257 0.969697 0.0011326671 0.97979796 0.00050348043 0.989899 0.00012588501 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SmoothStep</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99969596 0.02020202 0.9987921 0.030303031 0.9973008 0.04040404 0.99523443 0.05050505 0.9926054 0.060606062 0.98942596 0.07070707 0.98570853 0.08080808 0.9814655 0.09090909 0.97670925 0.1010101 0.9714521 0.11111111 0.96570647 0.121212125 0.95948464 0.13131313 0.9527991 0.14141414 0.94566214 0.15151516 0.9380861 0.16161616 0.9300834 0.17171717 0.92166644 0.18181819 0.91284746 0.1919192 0.90363896 0.2020202 0.8940533 0.21212122 0.8841027 0.22222222 0.87379974 0.23232323 0.8631566 0.24242425 0.8521858 0.25252524 0.8408996 0.26262626 0.82931036 0.27272728 0.8174305 0.28282827 0.8052724 0.2929293 0.7928484 0.3030303 0.78017086 0.3131313 0.7672522 0.32323232 0.7541047 0.33333334 0.7407407 0.34343433 0.72717273 0.35353535 0.7134131 0.36363637 0.6994741 0.37373737 0.6853681 0.3838384 0.67110753 0.3939394 0.6567048 0.4040404 0.64217216 0.41414142 0.627522 0.42424244 0.6127668 0.43434343 0.5979188 0.44444445 0.5829904 0.45454547 0.567994 0.46464646 0.5529419 0.47474748 0.53784657 0.4848485 0.52272034 0.4949495 0.5075755 0.5050505 0.4924245 0.5151515 0.47727972 0.5252525 0.46215343 0.53535354 0.44705802 0.54545456 0.432006 0.5555556 0.4170096 0.56565654 0.40208125 0.57575756 0.3872332 0.5858586 0.372478 0.5959596 0.35782784 0.6060606 0.34329516 0.61616164 0.32889235 0.6262626 0.31463194 0.6363636 0.30052596 0.64646465 0.28658694 0.65656567 0.27282727 0.6666667 0.25925922 0.67676765 0.24589539 0.68686867 0.23274791 0.6969697 0.21982914 0.7070707 0.20715159 0.7171717 0.1947276 0.72727275 0.1825695 0.7373737 0.1706897 0.74747473 0.15910047 0.75757575 0.14781427 0.7676768 0.13684332 0.7777778 0.1262002 0.7878788 0.11589724 0.7979798 0.10594672 0.8080808 0.09636104 0.8181818 0.08715248 0.82828283 0.07833356 0.83838385 0.069916606 0.8484849 0.061913908 0.85858583 0.05433786 0.86868685 0.04720086 0.8787879 0.040515304 0.8888889 0.034293592 0.8989899 0.028547943 0.90909094 0.023290753 0.9191919 0.018534541 0.9292929 0.014291525 0.93939394 0.010574102 0.94949496 0.0073946714 0.959596 0.0047655106 0.969697 0.0026991367 0.97979796 0.0012078881 0.989899 0.0003039837 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SmoothStepIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9998475 0.02020202 0.9993919 0.030303031 0.9986365 0.04040404 0.9975842 0.05050505 0.9962383 0.060606062 0.99460167 0.07070707 0.9926775 0.08080808 0.9904689 0.09090909 0.98797894 0.1010101 0.9852107 0.11111111 0.98216736 0.121212125 0.97885185 0.13131313 0.9752674 0.14141414 0.97141707 0.15151516 0.9673039 0.16161616 0.96293104 0.17171717 0.95830154 0.18181819 0.9534185 0.1919192 0.948285 0.2020202 0.9429042 0.21212122 0.9372791 0.22222222 0.9314129 0.23232323 0.9253086 0.24242425 0.9189693 0.25252524 0.91239816 0.26262626 0.90559816 0.27272728 0.8985725 0.28282827 0.8913242 0.2929293 0.8838564 0.3030303 0.8761722 0.3131313 0.8682746 0.32323232 0.8601668 0.33333334 0.8518518 0.34343433 0.8433328 0.35353535 0.83461285 0.36363637 0.825695 0.37373737 0.8165823 0.3838384 0.8072779 0.3939394 0.79778504 0.4040404 0.78810656 0.41414142 0.7782457 0.42424244 0.76820546 0.43434343 0.75798905 0.44444445 0.7475995 0.45454547 0.7370398 0.46464646 0.72631323 0.47474748 0.71542275 0.4848485 0.7043716 0.4949495 0.6931627 0.5050505 0.6817992 0.5151515 0.67028415 0.5252525 0.6586207 0.53535354 0.64681196 0.54545456 0.634861 0.5555556 0.6227709 0.56565654 0.6105448 0.57575756 0.5981857 0.5858586 0.5856968 0.5959596 0.57308114 0.6060606 0.5603417 0.61616164 0.5474817 0.6262626 0.53450435 0.6363636 0.5214125 0.64646465 0.50820935 0.65656567 0.49489796 0.6666667 0.48148143 0.67676765 0.46796298 0.68686867 0.45434552 0.6969697 0.44063228 0.7070707 0.42682618 0.7171717 0.41293043 0.72727275 0.39894813 0.7373737 0.3848825 0.74747473 0.37073624 0.75757575 0.35651278 0.7676768 0.34221512 0.7777778 0.3278463 0.7878788 0.3134095 0.7979798 0.29890794 0.8080808 0.28434432 0.8181818 0.26972198 0.82828283 0.25504404 0.83838385 0.24031359 0.8484849 0.2255336 0.85858583 0.21070725 0.86868685 0.19583762 0.8787879 0.18092775 0.8888889 0.16598076 0.8989899 0.15099978 0.90909094 0.13598794 0.9191919 0.120948255 0.9292929 0.1058839 0.93939394 0.09079778 0.94949496 0.07569307 0.959596 0.06057298 0.969697 0.045440614 0.97979796 0.030298889 0.989899 0.015150964 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SmoothStepOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.984849 0.02020202 0.9697011 0.030303031 0.9545594 0.04040404 0.9394269 0.05050505 0.92430687 0.060606062 0.9092022 0.07070707 0.89411616 0.08080808 0.87905174 0.09090909 0.864012 0.1010101 0.84900016 0.11111111 0.8340192 0.121212125 0.81907225 0.13131313 0.80416244 0.14141414 0.7892928 0.15151516 0.7744664 0.16161616 0.75968647 0.17171717 0.74495596 0.18181819 0.730278 0.1919192 0.7156557 0.2020202 0.7010921 0.21212122 0.68659043 0.22222222 0.6721536 0.23232323 0.6577849 0.24242425 0.6434872 0.25252524 0.62926376 0.26262626 0.6151176 0.27272728 0.6010518 0.28282827 0.5870696 0.2929293 0.5731739 0.3030303 0.5593678 0.3131313 0.54565454 0.32323232 0.532037 0.33333334 0.5185185 0.34343433 0.50510204 0.35353535 0.49179065 0.36363637 0.4785875 0.37373737 0.4654957 0.3838384 0.45251822 0.3939394 0.43965828 0.4040404 0.42691892 0.41414142 0.41430318 0.42424244 0.40181428 0.43434343 0.3894552 0.44444445 0.37722903 0.45454547 0.365139 0.46464646 0.35318804 0.47474748 0.34137928 0.4848485 0.3297159 0.4949495 0.3182009 0.5050505 0.30683738 0.5151515 0.2956285 0.5252525 0.2845772 0.53535354 0.27368677 0.54545456 0.26296014 0.5555556 0.25240052 0.56565654 0.24201095 0.57575756 0.23179454 0.5858586 0.22175431 0.5959596 0.21189344 0.6060606 0.20221502 0.61616164 0.19272202 0.6262626 0.18341768 0.6363636 0.17430508 0.64646465 0.16538715 0.65656567 0.15666717 0.6666667 0.14814812 0.67676765 0.13983321 0.68686867 0.13172543 0.6969697 0.123827755 0.7070707 0.116143584 0.7171717 0.10867572 0.72727275 0.101427495 0.7373737 0.09440184 0.74747473 0.08760184 0.75757575 0.08103067 0.7676768 0.074691415 0.7777778 0.068587065 0.7878788 0.062720895 0.7979798 0.057095826 0.8080808 0.051715016 0.8181818 0.046581507 0.82828283 0.041698456 0.83838385 0.037069023 0.8484849 0.032696128 0.85858583 0.02858299 0.86868685 0.02473253 0.8787879 0.021148086 0.8888889 0.017832637 0.8989899 0.014789283 0.90909094 0.012021005 0.9191919 0.009531081 0.9292929 0.00732255 0.93939394 0.0053983927 0.94949496 0.0037617683 0.959596 0.0024157763 0.969697 0.0013635755 0.97979796 0.0006080866 0.989899 0.00015246868 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SmootherStep</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99998987 0.02020202 0.99992 0.030303031 0.9997342 0.04040404 0.99937975 0.05050505 0.9988074 0.060606062 0.99797136 0.07070707 0.99682933 0.08080808 0.9953422 0.09090909 0.9934741 0.1010101 0.99119234 0.11111111 0.9884672 0.121212125 0.98527205 0.13131313 0.9815831 0.14141414 0.9773795 0.15151516 0.972643 0.16161616 0.9673583 0.17171717 0.9615124 0.18181819 0.955095 0.1919192 0.94809824 0.2020202 0.9405167 0.21212122 0.93234724 0.22222222 0.9235889 0.23232323 0.91424286 0.24242425 0.9043125 0.25252524 0.8938031 0.26262626 0.88272196 0.27272728 0.8710781 0.28282827 0.8588825 0.2929293 0.84614766 0.3030303 0.8328878 0.3131313 0.81911886 0.32323232 0.8048578 0.33333334 0.79012346 0.34343433 0.7749357 0.35353535 0.7593159 0.36363637 0.74328625 0.37373737 0.7268704 0.3838384 0.7100928 0.3939394 0.69297886 0.4040404 0.675555 0.41414142 0.65784824 0.42424244 0.6398865 0.43434343 0.62169826 0.44444445 0.6033125 0.45454547 0.5847589 0.46464646 0.5660672 0.47474748 0.54726803 0.4848485 0.5283916 0.4949495 0.5094691 0.5050505 0.49053103 0.5151515 0.47160834 0.5252525 0.45273203 0.53535354 0.43393272 0.54545456 0.41524112 0.5555556 0.39668745 0.56565654 0.37830186 0.57575756 0.3601135 0.5858586 0.34215176 0.5959596 0.324445 0.6060606 0.30702114 0.61616164 0.28990716 0.6262626 0.27312964 0.6363636 0.25671363 0.64646465 0.24068403 0.65656567 0.22506416 0.6666667 0.20987654 0.67676765 0.19514233 0.68686867 0.1808812 0.6969697 0.16711229 0.7070707 0.15385246 0.7171717 0.14111769 0.72727275 0.12892199 0.7373737 0.11727822 0.74747473 0.106197 0.75757575 0.09568775 0.7676768 0.085757315 0.7777778 0.07641095 0.7878788 0.06765282 0.7979798 0.059483588 0.8080808 0.05190164 0.8181818 0.044904888 0.82828283 0.038487732 0.83838385 0.03264159 0.8484849 0.027357042 0.85858583 0.02262044 0.86868685 0.018416762 0.8787879 0.014727771 0.8888889 0.011532545 0.8989899 0.008807182 0.90909094 0.0065256953 0.9191919 0.004657209 0.9292929 0.0031701922 0.93939394 0.0020281076 0.94949496 0.0011927485 0.959596 0.0006200671 0.969697 0.00026607513 0.97979796 0.00007981062 0.989899 0.000010192394 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SmootherStepIn</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.99999744 0.02020202 0.9999797 0.030303031 0.999932 0.04040404 0.9998401 0.05050505 0.99969 0.060606062 0.99946845 0.07070707 0.99916244 0.08080808 0.99875945 0.09090909 0.99824744 0.1010101 0.99761474 0.11111111 0.9968501 0.121212125 0.9959427 0.13131313 0.9948822 0.14141414 0.99365866 0.15151516 0.9922624 0.16161616 0.9906844 0.17171717 0.9889158 0.18181819 0.98694825 0.1919192 0.98477376 0.2020202 0.9823847 0.21212122 0.9797739 0.22222222 0.97693443 0.23232323 0.97385985 0.24242425 0.9705441 0.25252524 0.96698135 0.26262626 0.96316624 0.27272728 0.9590937 0.28282827 0.954759 0.2929293 0.9501578 0.3030303 0.9452861 0.3131313 0.9401401 0.32323232 0.9347166 0.33333334 0.92901236 0.34343433 0.9230247 0.35353535 0.91675127 0.36363637 0.9101899 0.37373737 0.90333885 0.3838384 0.8961965 0.3939394 0.88876164 0.4040404 0.8810335 0.41414142 0.8730112 0.42424244 0.8646945 0.43434343 0.8560833 0.44444445 0.84717774 0.45454547 0.83797836 0.46464646 0.8284857 0.47474748 0.8187009 0.4848485 0.808625 0.4949495 0.79825956 0.5050505 0.78760624 0.5151515 0.776667 0.5252525 0.76544386 0.53535354 0.7539394 0.54545456 0.74215615 0.5555556 0.730097 0.56565654 0.717765 0.57575756 0.70516324 0.5858586 0.6922953 0.5959596 0.6791648 0.6060606 0.66577566 0.61616164 0.6521318 0.6262626 0.63823766 0.6363636 0.62409735 0.64646465 0.6097156 0.65656567 0.5950971 0.6666667 0.5802469 0.67676765 0.56517005 0.68686867 0.54987144 0.6969697 0.53435695 0.7070707 0.5186318 0.7171717 0.50270176 0.72727275 0.48657256 0.7373737 0.4702503 0.74747473 0.4537409 0.75757575 0.43705052 0.7676768 0.42018557 0.7777778 0.4031524 0.7878788 0.38595766 0.7979798 0.368608 0.8080808 0.35110998 0.8181818 0.3334704 0.82828283 0.31569654 0.83838385 0.29779494 0.8484849 0.27977294 0.85858583 0.26163775 0.86868685 0.24339652 0.8787879 0.22505635 0.8888889 0.20662498 0.8989899 0.18810958 0.90909094 0.1695177 0.9191919 0.15085685 0.9292929 0.1321345 0.93939394 0.11335838 0.94949496 0.094536066 0.959596 0.07567507 0.969697 0.05678326 0.97979796 0.0378685 0.989899 0.018938184 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>SmootherStepOut</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 1 0.01010101 0.9810619 0.02020202 0.9621315 0.030303031 0.94321656 0.04040404 0.9243248 0.05050505 0.90546393 0.060606062 0.8866416 0.07070707 0.86786544 0.08080808 0.84914315 0.09090909 0.83048224 0.1010101 0.81189036 0.11111111 0.793375 0.121212125 0.7749436 0.13131313 0.75660354 0.14141414 0.7383623 0.15151516 0.720227 0.16161616 0.70220506 0.17171717 0.6843035 0.18181819 0.66652954 0.1919192 0.6488901 0.2020202 0.63139206 0.21212122 0.6140423 0.22222222 0.59684753 0.23232323 0.57981443 0.24242425 0.5629495 0.25252524 0.54625916 0.26262626 0.52974975 0.27272728 0.5134274 0.28282827 0.4972983 0.2929293 0.48136824 0.3030303 0.46564305 0.3131313 0.4501285 0.32323232 0.43483007 0.33333334 0.41975307 0.34343433 0.40490288 0.35353535 0.39028436 0.36363637 0.37590265 0.37373737 0.36176234 0.3838384 0.34786814 0.3939394 0.33422428 0.4040404 0.32083517 0.41414142 0.3077047 0.42424244 0.29483676 0.43434343 0.28223503 0.44444445 0.26990294 0.45454547 0.2578438 0.46464646 0.24606055 0.47474748 0.23455614 0.4848485 0.22333306 0.4949495 0.21239376 0.5050505 0.20174044 0.5151515 0.19137502 0.5252525 0.18129909 0.53535354 0.17151427 0.54545456 0.16202164 0.5555556 0.1528222 0.56565654 0.14391673 0.57575756 0.13530552 0.5858586 0.12698883 0.5959596 0.11896658 0.6060606 0.1112383 0.61616164 0.103803515 0.6262626 0.09666121 0.6363636 0.089810014 0.64646465 0.083248734 0.65656567 0.076975286 0.6666667 0.0709877 0.67676765 0.06528342 0.68686867 0.05985993 0.6969697 0.054713905 0.7070707 0.04984224 0.7171717 0.045241 0.72727275 0.04090637 0.7373737 0.036833823 0.74747473 0.03301871 0.75757575 0.02945596 0.7676768 0.026140094 0.7777778 0.023065627 0.7878788 0.020226121 0.7979798 0.017615318 0.8080808 0.0152263045 0.8181818 0.013051748 0.82828283 0.011084199 0.83838385 0.00931561 0.8484849 0.0077375174 0.85858583 0.0063413978 0.86868685 0.005117893 0.8787879 0.004057288 0.8888889 0.003149867 0.8989899 0.002385378 0.90909094 0.0017526746 0.9191919 0.0012404323 0.9292929 0.00083744526 0.93939394 0.0005315542 0.94949496 0.00030994415 0.959596 0.00015997887 0.969697 0.000067949295 0.97979796 0.000020205975 0.989899 0.0000026226044 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,5 @@
<svg viewBox="-0.04 -0.04 1.08 1.08" width="6em" xmlns="http://www.w3.org/2000/svg">
<title>StartSteps(4, Start)</title>
<path d="M0,0 L0,1 M1,0 L1,1 M0,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 M0,1 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0 m0.1,0 l0.1,0" fill="none" stroke="var(--main-color)" stroke-width="0.02"/>
<polyline fill="none" points="0 0.75 0.01010101 0.75 0.02020202 0.75 0.030303031 0.75 0.04040404 0.75 0.05050505 0.75 0.060606062 0.75 0.07070707 0.75 0.08080808 0.75 0.09090909 0.75 0.1010101 0.75 0.11111111 0.75 0.121212125 0.75 0.13131313 0.75 0.14141414 0.75 0.15151516 0.75 0.16161616 0.75 0.17171717 0.75 0.18181819 0.75 0.1919192 0.75 0.2020202 0.75 0.21212122 0.75 0.22222222 0.75 0.23232323 0.75 0.24242425 0.75 0.25252524 0.5 0.26262626 0.5 0.27272728 0.5 0.28282827 0.5 0.2929293 0.5 0.3030303 0.5 0.3131313 0.5 0.32323232 0.5 0.33333334 0.5 0.34343433 0.5 0.35353535 0.5 0.36363637 0.5 0.37373737 0.5 0.3838384 0.5 0.3939394 0.5 0.4040404 0.5 0.41414142 0.5 0.42424244 0.5 0.43434343 0.5 0.44444445 0.5 0.45454547 0.5 0.46464646 0.5 0.47474748 0.5 0.4848485 0.5 0.4949495 0.5 0.5050505 0.25 0.5151515 0.25 0.5252525 0.25 0.53535354 0.25 0.54545456 0.25 0.5555556 0.25 0.56565654 0.25 0.57575756 0.25 0.5858586 0.25 0.5959596 0.25 0.6060606 0.25 0.61616164 0.25 0.6262626 0.25 0.6363636 0.25 0.64646465 0.25 0.65656567 0.25 0.6666667 0.25 0.67676765 0.25 0.68686867 0.25 0.6969697 0.25 0.7070707 0.25 0.7171717 0.25 0.72727275 0.25 0.7373737 0.25 0.74747473 0.25 0.75757575 0 0.7676768 0 0.7777778 0 0.7878788 0 0.7979798 0 0.8080808 0 0.8181818 0 0.82828283 0 0.83838385 0 0.8484849 0 0.85858583 0 0.86868685 0 0.8787879 0 0.8888889 0 0.8989899 0 0.90909094 0 0.9191919 0 0.9292929 0 0.93939394 0 0.94949496 0 0.959596 0 0.969697 0 0.97979796 0 0.989899 0 1 0" stroke="red" stroke-width="0.04"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

63
vendor/bevy_math/src/affine3.rs vendored Normal file
View File

@@ -0,0 +1,63 @@
use glam::{Affine3A, Mat3, Vec3, Vec3Swizzles, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// Reduced-size version of `glam::Affine3A` for use when storage has
/// significant performance impact. Convert to `glam::Affine3A` to do
/// non-trivial calculations.
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct Affine3 {
/// Scaling, rotation, shears, and other non-translation affine transforms
pub matrix3: Mat3,
/// Translation
pub translation: Vec3,
}
impl Affine3 {
/// Calculates the transpose of the affine 4x3 matrix to a 3x4 and formats it for packing into GPU buffers
#[inline]
pub fn to_transpose(&self) -> [Vec4; 3] {
let transpose_3x3 = self.matrix3.transpose();
[
transpose_3x3.x_axis.extend(self.translation.x),
transpose_3x3.y_axis.extend(self.translation.y),
transpose_3x3.z_axis.extend(self.translation.z),
]
}
/// Calculates the inverse transpose of the 3x3 matrix and formats it for packing into GPU buffers
#[inline]
pub fn inverse_transpose_3x3(&self) -> ([Vec4; 2], f32) {
let inverse_transpose_3x3 = Affine3A::from(self).inverse().matrix3.transpose();
(
[
(inverse_transpose_3x3.x_axis, inverse_transpose_3x3.y_axis.x).into(),
(
inverse_transpose_3x3.y_axis.yz(),
inverse_transpose_3x3.z_axis.xy(),
)
.into(),
],
inverse_transpose_3x3.z_axis.z,
)
}
}
impl From<&Affine3A> for Affine3 {
fn from(affine: &Affine3A) -> Self {
Self {
matrix3: affine.matrix3.into(),
translation: affine.translation.into(),
}
}
}
impl From<&Affine3> for Affine3A {
fn from(affine3: &Affine3) -> Self {
Self {
matrix3: affine3.matrix3.into(),
translation: affine3.translation.into(),
}
}
}

103
vendor/bevy_math/src/aspect_ratio.rs vendored Normal file
View File

@@ -0,0 +1,103 @@
//! Provides a simple aspect ratio struct to help with calculations.
use crate::Vec2;
use derive_more::derive::Into;
use thiserror::Error;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// An `AspectRatio` is the ratio of width to height.
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Into)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
pub struct AspectRatio(f32);
impl AspectRatio {
/// Standard 16:9 aspect ratio
pub const SIXTEEN_NINE: Self = Self(16.0 / 9.0);
/// Standard 4:3 aspect ratio
pub const FOUR_THREE: Self = Self(4.0 / 3.0);
/// Standard 21:9 ultrawide aspect ratio
pub const ULTRAWIDE: Self = Self(21.0 / 9.0);
/// Attempts to create a new [`AspectRatio`] from a given width and height.
///
/// # Errors
///
/// Returns an `Err` with `AspectRatioError` if:
/// - Either width or height is zero (`AspectRatioError::Zero`)
/// - Either width or height is infinite (`AspectRatioError::Infinite`)
/// - Either width or height is NaN (`AspectRatioError::NaN`)
#[inline]
pub fn try_new(width: f32, height: f32) -> Result<Self, AspectRatioError> {
match (width, height) {
(w, h) if w == 0.0 || h == 0.0 => Err(AspectRatioError::Zero),
(w, h) if w.is_infinite() || h.is_infinite() => Err(AspectRatioError::Infinite),
(w, h) if w.is_nan() || h.is_nan() => Err(AspectRatioError::NaN),
_ => Ok(Self(width / height)),
}
}
/// Attempts to create a new [`AspectRatio`] from a given amount of x pixels and y pixels.
#[inline]
pub fn try_from_pixels(x: u32, y: u32) -> Result<Self, AspectRatioError> {
Self::try_new(x as f32, y as f32)
}
/// Returns the aspect ratio as a f32 value.
#[inline]
pub const fn ratio(&self) -> f32 {
self.0
}
/// Returns the inverse of this aspect ratio (height/width).
#[inline]
pub const fn inverse(&self) -> Self {
Self(1.0 / self.0)
}
/// Returns true if the aspect ratio represents a landscape orientation.
#[inline]
pub const fn is_landscape(&self) -> bool {
self.0 > 1.0
}
/// Returns true if the aspect ratio represents a portrait orientation.
#[inline]
pub const fn is_portrait(&self) -> bool {
self.0 < 1.0
}
/// Returns true if the aspect ratio is exactly square.
#[inline]
pub const fn is_square(&self) -> bool {
self.0 == 1.0
}
}
impl TryFrom<Vec2> for AspectRatio {
type Error = AspectRatioError;
#[inline]
fn try_from(value: Vec2) -> Result<Self, Self::Error> {
Self::try_new(value.x, value.y)
}
}
/// An Error type for when [`super::AspectRatio`] is provided invalid width or height values
#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)]
pub enum AspectRatioError {
/// Error due to width or height having zero as a value.
#[error("AspectRatio error: width or height is zero")]
Zero,
/// Error due towidth or height being infinite.
#[error("AspectRatio error: width or height is infinite")]
Infinite,
/// Error due to width or height being Not a Number (NaN).
#[error("AspectRatio error: width or height is NaN")]
NaN,
}

View File

@@ -0,0 +1,770 @@
mod primitive_impls;
use super::{BoundingVolume, IntersectsVolume};
use crate::{
ops,
prelude::{Mat2, Rot2, Vec2},
FloatPow, Isometry2d,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
/// Computes the geometric center of the given set of points.
#[inline(always)]
fn point_cloud_2d_center(points: &[Vec2]) -> Vec2 {
assert!(
!points.is_empty(),
"cannot compute the center of an empty set of points"
);
let denom = 1.0 / points.len() as f32;
points.iter().fold(Vec2::ZERO, |acc, point| acc + *point) * denom
}
/// A trait with methods that return 2D bounding volumes for a shape.
pub trait Bounded2d {
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_2d(&self, isometry: impl Into<Isometry2d>) -> Aabb2d;
/// Get a bounding circle for the shape translated and rotated by the given isometry.
fn bounding_circle(&self, isometry: impl Into<Isometry2d>) -> BoundingCircle;
}
/// A 2D axis-aligned bounding box, or bounding rectangle
#[doc(alias = "BoundingRectangle")]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Aabb2d {
/// The minimum, conventionally bottom-left, point of the box
pub min: Vec2,
/// The maximum, conventionally top-right, point of the box
pub max: Vec2,
}
impl Aabb2d {
/// Constructs an AABB from its center and half-size.
#[inline(always)]
pub fn new(center: Vec2, half_size: Vec2) -> Self {
debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0);
Self {
min: center - half_size,
max: center + half_size,
}
}
/// Computes the smallest [`Aabb2d`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> Aabb2d {
let isometry = isometry.into();
// Transform all points by rotation
let mut iter = points.iter().map(|point| isometry.rotation * *point);
let first = iter
.next()
.expect("point cloud must contain at least one point for Aabb2d construction");
let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
(point.min(prev_min), point.max(prev_max))
});
Aabb2d {
min: min + isometry.translation,
max: max + isometry.translation,
}
}
/// Computes the smallest [`BoundingCircle`] containing this [`Aabb2d`].
#[inline(always)]
pub fn bounding_circle(&self) -> BoundingCircle {
let radius = self.min.distance(self.max) / 2.0;
BoundingCircle::new(self.center(), radius)
}
/// Finds the point on the AABB that is closest to the given `point`.
///
/// If the point is outside the AABB, the returned point will be on the perimeter of the AABB.
/// Otherwise, it will be inside the AABB and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
// Clamp point coordinates to the AABB
point.clamp(self.min, self.max)
}
}
impl BoundingVolume for Aabb2d {
type Translation = Vec2;
type Rotation = Rot2;
type HalfSize = Vec2;
#[inline(always)]
fn center(&self) -> Self::Translation {
(self.min + self.max) / 2.
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
(self.max - self.min) / 2.
}
#[inline(always)]
fn visible_area(&self) -> f32 {
let b = self.max - self.min;
b.x * b.y
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
other.min.x >= self.min.x
&& other.min.y >= self.min.y
&& other.max.x <= self.max.x
&& other.max.y <= self.max.y
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min - amount,
max: self.max + amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min + amount,
max: self.max - amount,
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
let b = Self {
min: self.center() - (self.half_size() * scale),
max: self.center() + (self.half_size() * scale),
};
debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y);
b
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transformed_by(
mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) -> Self {
self.transform_by(translation, rotation);
self
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transform_by(
&mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) {
self.rotate_by(rotation);
self.translate_by(translation);
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
let translation = translation.into();
self.min += translation;
self.max += translation;
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
self.rotate_by(rotation);
self
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rot_mat = Mat2::from(rotation.into());
let half_size = rot_mat.abs() * self.half_size();
*self = Self::new(rot_mat * self.center(), half_size);
}
}
impl IntersectsVolume<Self> for Aabb2d {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
let x_overlaps = self.min.x <= other.max.x && self.max.x >= other.min.x;
let y_overlaps = self.min.y <= other.max.y && self.max.y >= other.min.y;
x_overlaps && y_overlaps
}
}
impl IntersectsVolume<BoundingCircle> for Aabb2d {
#[inline(always)]
fn intersects(&self, circle: &BoundingCircle) -> bool {
let closest_point = self.closest_point(circle.center);
let distance_squared = circle.center.distance_squared(closest_point);
let radius_squared = circle.radius().squared();
distance_squared <= radius_squared
}
}
#[cfg(test)]
mod aabb2d_tests {
use approx::assert_relative_eq;
use super::Aabb2d;
use crate::{
bounding::{BoundingCircle, BoundingVolume, IntersectsVolume},
ops, Vec2,
};
#[test]
fn center() {
let aabb = Aabb2d {
min: Vec2::new(-0.5, -1.),
max: Vec2::new(1., 1.),
};
assert!((aabb.center() - Vec2::new(0.25, 0.)).length() < f32::EPSILON);
let aabb = Aabb2d {
min: Vec2::new(5., -10.),
max: Vec2::new(10., -5.),
};
assert!((aabb.center() - Vec2::new(7.5, -7.5)).length() < f32::EPSILON);
}
#[test]
fn half_size() {
let aabb = Aabb2d {
min: Vec2::new(-0.5, -1.),
max: Vec2::new(1., 1.),
};
let half_size = aabb.half_size();
assert!((half_size - Vec2::new(0.75, 1.)).length() < f32::EPSILON);
}
#[test]
fn area() {
let aabb = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
assert!(ops::abs(aabb.visible_area() - 4.) < f32::EPSILON);
let aabb = Aabb2d {
min: Vec2::new(0., 0.),
max: Vec2::new(1., 0.5),
};
assert!(ops::abs(aabb.visible_area() - 0.5) < f32::EPSILON);
}
#[test]
fn contains() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
let b = Aabb2d {
min: Vec2::new(-2., -1.),
max: Vec2::new(1., 1.),
};
assert!(!a.contains(&b));
let b = Aabb2d {
min: Vec2::new(-0.25, -0.8),
max: Vec2::new(1., 1.),
};
assert!(a.contains(&b));
}
#[test]
fn merge() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 0.5),
};
let b = Aabb2d {
min: Vec2::new(-2., -0.5),
max: Vec2::new(0.75, 1.),
};
let merged = a.merge(&b);
assert!((merged.min - Vec2::new(-2., -1.)).length() < f32::EPSILON);
assert!((merged.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
}
#[test]
fn grow() {
let a = Aabb2d {
min: Vec2::new(-1., -1.),
max: Vec2::new(1., 1.),
};
let padded = a.grow(Vec2::ONE);
assert!((padded.min - Vec2::new(-2., -2.)).length() < f32::EPSILON);
assert!((padded.max - Vec2::new(2., 2.)).length() < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = Aabb2d {
min: Vec2::new(-2., -2.),
max: Vec2::new(2., 2.),
};
let shrunk = a.shrink(Vec2::ONE);
assert!((shrunk.min - Vec2::new(-1., -1.)).length() < f32::EPSILON);
assert!((shrunk.max - Vec2::new(1., 1.)).length() < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
let scaled = a.scale_around_center(Vec2::splat(2.));
assert!((scaled.min - Vec2::splat(-2.)).length() < f32::EPSILON);
assert!((scaled.max - Vec2::splat(2.)).length() < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn rotate() {
let a = Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(2.0, 2.0),
};
let rotated = a.rotated_by(core::f32::consts::PI);
assert_relative_eq!(rotated.min, a.min);
assert_relative_eq!(rotated.max, a.max);
}
#[test]
fn transform() {
let a = Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(2.0, 2.0),
};
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
let half_length = ops::hypot(2.0, 2.0);
assert_eq!(
transformed.min,
Vec2::new(2.0 - half_length, -half_length - 2.0)
);
assert_eq!(
transformed.max,
Vec2::new(2.0 + half_length, half_length - 2.0)
);
}
#[test]
fn closest_point() {
let aabb = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
assert_eq!(aabb.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(aabb.closest_point(Vec2::NEG_ONE * 10.0), Vec2::NEG_ONE);
assert_eq!(
aabb.closest_point(Vec2::new(0.25, 0.1)),
Vec2::new(0.25, 0.1)
);
}
#[test]
fn intersect_aabb() {
let aabb = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
assert!(aabb.intersects(&aabb));
assert!(aabb.intersects(&Aabb2d {
min: Vec2::new(0.5, 0.5),
max: Vec2::new(2.0, 2.0),
}));
assert!(aabb.intersects(&Aabb2d {
min: Vec2::new(-2.0, -2.0),
max: Vec2::new(-0.5, -0.5),
}));
assert!(!aabb.intersects(&Aabb2d {
min: Vec2::new(1.1, 0.0),
max: Vec2::new(2.0, 0.5),
}));
}
#[test]
fn intersect_bounding_circle() {
let aabb = Aabb2d {
min: Vec2::NEG_ONE,
max: Vec2::ONE,
};
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
assert!(aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
assert!(aabb.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.5, 1.0)));
assert!(!aabb.intersects(&BoundingCircle::new(Vec2::ONE * 1.75, 1.0)));
}
}
use crate::primitives::Circle;
/// A bounding circle
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct BoundingCircle {
/// The center of the bounding circle
pub center: Vec2,
/// The circle
pub circle: Circle,
}
impl BoundingCircle {
/// Constructs a bounding circle from its center and radius.
#[inline(always)]
pub fn new(center: Vec2, radius: f32) -> Self {
debug_assert!(radius >= 0.);
Self {
center,
circle: Circle { radius },
}
}
/// Computes a [`BoundingCircle`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// The bounding circle is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(isometry: impl Into<Isometry2d>, points: &[Vec2]) -> BoundingCircle {
let isometry = isometry.into();
let center = point_cloud_2d_center(points);
let mut radius_squared = 0.0;
for point in points {
// Get squared version to avoid unnecessary sqrt calls
let distance_squared = point.distance_squared(center);
if distance_squared > radius_squared {
radius_squared = distance_squared;
}
}
BoundingCircle::new(isometry * center, ops::sqrt(radius_squared))
}
/// Get the radius of the bounding circle
#[inline(always)]
pub fn radius(&self) -> f32 {
self.circle.radius
}
/// Computes the smallest [`Aabb2d`] containing this [`BoundingCircle`].
#[inline(always)]
pub fn aabb_2d(&self) -> Aabb2d {
Aabb2d {
min: self.center - Vec2::splat(self.radius()),
max: self.center + Vec2::splat(self.radius()),
}
}
/// Finds the point on the bounding circle that is closest to the given `point`.
///
/// If the point is outside the circle, the returned point will be on the perimeter of the circle.
/// Otherwise, it will be inside the circle and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: Vec2) -> Vec2 {
self.circle.closest_point(point - self.center) + self.center
}
}
impl BoundingVolume for BoundingCircle {
type Translation = Vec2;
type Rotation = Rot2;
type HalfSize = f32;
#[inline(always)]
fn center(&self) -> Self::Translation {
self.center
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
self.radius()
}
#[inline(always)]
fn visible_area(&self) -> f32 {
core::f32::consts::PI * self.radius() * self.radius()
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
let diff = self.radius() - other.radius();
self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
let diff = other.center - self.center;
let length = diff.length();
if self.radius() >= length + other.radius() {
return *self;
}
if other.radius() >= length + self.radius() {
return *other;
}
let dir = diff / length;
Self::new(
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
(length + self.radius() + other.radius()) / 2.,
)
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
Self::new(self.center, self.radius() + amount)
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
debug_assert!(self.radius() >= amount);
Self::new(self.center, self.radius() - amount)
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
debug_assert!(scale >= 0.);
Self::new(self.center, self.radius() * scale)
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
self.center += translation.into();
}
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rotation: Rot2 = rotation.into();
self.center = rotation * self.center;
}
}
impl IntersectsVolume<Self> for BoundingCircle {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
let center_distance_squared = self.center.distance_squared(other.center);
let radius_sum_squared = (self.radius() + other.radius()).squared();
center_distance_squared <= radius_sum_squared
}
}
impl IntersectsVolume<Aabb2d> for BoundingCircle {
#[inline(always)]
fn intersects(&self, aabb: &Aabb2d) -> bool {
aabb.intersects(self)
}
}
#[cfg(test)]
mod bounding_circle_tests {
use super::BoundingCircle;
use crate::{
bounding::{BoundingVolume, IntersectsVolume},
ops, Vec2,
};
#[test]
fn area() {
let circle = BoundingCircle::new(Vec2::ONE, 5.);
// Since this number is messy we check it with a higher threshold
assert!(ops::abs(circle.visible_area() - 78.5398) < 0.001);
}
#[test]
fn contains() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let b = BoundingCircle::new(Vec2::new(5.5, 1.), 1.);
assert!(!a.contains(&b));
let b = BoundingCircle::new(Vec2::new(1., -3.5), 0.5);
assert!(a.contains(&b));
}
#[test]
fn contains_identical() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
assert!(a.contains(&a));
}
#[test]
fn merge() {
// When merging two circles that don't contain each other, we find a center position that
// contains both
let a = BoundingCircle::new(Vec2::ONE, 5.);
let b = BoundingCircle::new(Vec2::new(1., -4.), 1.);
let merged = a.merge(&b);
assert!((merged.center - Vec2::new(1., 0.5)).length() < f32::EPSILON);
assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
// When one circle contains the other circle, we use the bigger circle
let b = BoundingCircle::new(Vec2::ZERO, 3.);
assert!(a.contains(&b));
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
// When two circles are at the same point, we use the bigger radius
let b = BoundingCircle::new(Vec2::ONE, 6.);
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), b.radius());
}
#[test]
fn merge_identical() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let merged = a.merge(&a);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
}
#[test]
fn grow() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let padded = a.grow(1.25);
assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let shrunk = a.shrink(0.5);
assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = BoundingCircle::new(Vec2::ONE, 5.);
let scaled = a.scale_around_center(2.);
assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn transform() {
let a = BoundingCircle::new(Vec2::ONE, 5.0);
let transformed = a.transformed_by(Vec2::new(2.0, -2.0), core::f32::consts::FRAC_PI_4);
assert_eq!(
transformed.center,
Vec2::new(2.0, core::f32::consts::SQRT_2 - 2.0)
);
assert_eq!(transformed.radius(), 5.0);
}
#[test]
fn closest_point() {
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
assert_eq!(circle.closest_point(Vec2::X * 10.0), Vec2::X);
assert_eq!(
circle.closest_point(Vec2::NEG_ONE * 10.0),
Vec2::NEG_ONE.normalize()
);
assert_eq!(
circle.closest_point(Vec2::new(0.25, 0.1)),
Vec2::new(0.25, 0.1)
);
}
#[test]
fn intersect_bounding_circle() {
let circle = BoundingCircle::new(Vec2::ZERO, 1.0);
assert!(circle.intersects(&BoundingCircle::new(Vec2::ZERO, 1.0)));
assert!(circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.25, 1.0)));
assert!(circle.intersects(&BoundingCircle::new(Vec2::NEG_ONE * 1.25, 1.0)));
assert!(!circle.intersects(&BoundingCircle::new(Vec2::ONE * 1.5, 1.0)));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,473 @@
use core::f32::consts::FRAC_PI_2;
use glam::{Vec2, Vec3A, Vec3Swizzles};
use crate::{
bounding::{BoundingCircle, BoundingVolume},
ops,
primitives::{
Capsule2d, Cuboid, Cylinder, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Primitive2d,
Rectangle, RegularPolygon, Segment2d, Triangle2d,
},
Isometry2d, Isometry3d, Quat, Rot2,
};
#[cfg(feature = "alloc")]
use crate::primitives::{BoxedPolygon, BoxedPolyline2d};
use crate::{bounding::Bounded2d, primitives::Circle};
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl BoundedExtrusion for Circle {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Z;
let top = (segment_dir * half_depth).abs();
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation - half_size - top,
max: isometry.translation + half_size + top,
}
}
}
impl BoundedExtrusion for Ellipse {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let Vec2 { x: a, y: b } = self.half_size;
let normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
let [max_x, max_y, max_z] = Vec3A::AXES.map(|axis| {
let Some(axis) = (conjugate_rot * axis.reject_from(normal))
.xy()
.try_normalize()
else {
return Vec3A::ZERO;
};
if axis.element_product() == 0. {
return isometry.rotation * Vec3A::new(a * axis.y, b * axis.x, 0.);
}
let m = -axis.x / axis.y;
let signum = axis.signum();
let y = signum.y * b * b / ops::sqrt(b * b + m * m * a * a);
let x = signum.x * a * ops::sqrt(1. - y * y / b / b);
isometry.rotation * Vec3A::new(x, y, 0.)
});
let half_size = Vec3A::new(max_x.x, max_y.y, max_z.z).abs() + (normal * half_depth).abs();
Aabb3d::new(isometry.translation, half_size)
}
}
impl BoundedExtrusion for Line2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let dir = isometry.rotation * Vec3A::from(self.direction.extend(0.));
let half_depth = (isometry.rotation * Vec3A::new(0., 0., half_depth)).abs();
let max = f32::MAX / 2.;
let half_size = Vec3A::new(
if dir.x == 0. { half_depth.x } else { max },
if dir.y == 0. { half_depth.y } else { max },
if dir.z == 0. { half_depth.z } else { max },
);
Aabb3d::new(isometry.translation, half_size)
}
}
impl BoundedExtrusion for Segment2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let half_size = isometry.rotation * Vec3A::from(self.point1().extend(0.));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(isometry.translation, half_size.abs() + depth.abs())
}
}
impl<const N: usize> BoundedExtrusion for Polyline2d<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
#[cfg(feature = "alloc")]
impl BoundedExtrusion for BoxedPolyline2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Triangle2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Rectangle {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
Cuboid {
half_size: self.half_size.extend(half_depth),
}
.aabb_3d(isometry)
}
}
impl<const N: usize> BoundedExtrusion for Polygon<N> {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb =
Aabb3d::from_point_cloud(isometry, self.vertices.map(|v| v.extend(0.)).into_iter());
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
#[cfg(feature = "alloc")]
impl BoundedExtrusion for BoxedPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(isometry, self.vertices.iter().map(|v| v.extend(0.)));
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for RegularPolygon {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Aabb3d::from_point_cloud(
isometry,
self.vertices(0.).into_iter().map(|v| v.extend(0.)),
);
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
aabb.grow(depth.abs())
}
}
impl BoundedExtrusion for Capsule2d {
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let aabb = Cylinder {
half_height: half_depth,
radius: self.radius,
}
.aabb_3d(isometry.rotation * Quat::from_rotation_x(FRAC_PI_2));
let up = isometry.rotation * Vec3A::new(0., self.half_length, 0.);
let half_size = aabb.max + up.abs();
Aabb3d::new(isometry.translation, half_size)
}
}
impl<T: BoundedExtrusion> Bounded3d for Extrusion<T> {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
self.base_shape.extrusion_aabb_3d(self.half_depth, isometry)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
self.base_shape
.extrusion_bounding_sphere(self.half_depth, isometry)
}
}
/// A trait implemented on 2D shapes which determines the 3D bounding volumes of their extrusions.
///
/// Since default implementations can be inferred from 2D bounding volumes, this allows a `Bounded2d`
/// implementation on some shape `MyShape` to be extrapolated to a `Bounded3d` implementation on
/// `Extrusion<MyShape>` without supplying any additional data; e.g.:
/// `impl BoundedExtrusion for MyShape {}`
pub trait BoundedExtrusion: Primitive2d + Bounded2d {
/// Get an axis-aligned bounding box for an extrusion with this shape as a base and the given `half_depth`, transformed by the given `translation` and `rotation`.
fn extrusion_aabb_3d(&self, half_depth: f32, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let cap_normal = isometry.rotation * Vec3A::Z;
let conjugate_rot = isometry.rotation.conjugate();
// The `(halfsize, offset)` for each axis
let axis_values = Vec3A::AXES.map(|ax| {
// This is the direction of the line of intersection of a plane with the `ax` normal and the plane containing the cap of the extrusion.
let intersect_line = ax.cross(cap_normal);
if intersect_line.length_squared() <= f32::EPSILON {
return (0., 0.);
};
// This is the normal vector of the intersection line rotated to be in the XY-plane
let line_normal = (conjugate_rot * intersect_line).yx();
let angle = line_normal.to_angle();
// Since the plane containing the caps of the extrusion is not guaranteed to be orthogonal to the `ax` plane, only a certain "scale" factor
// of the `Aabb2d` will actually go towards the dimensions of the `Aabb3d`
let scale = cap_normal.reject_from(ax).length();
// Calculate the `Aabb2d` of the base shape. The shape is rotated so that the line of intersection is parallel to the Y axis in the `Aabb2d` calculations.
// This guarantees that the X value of the `Aabb2d` is closest to the `ax` plane
let aabb2d = self.aabb_2d(Rot2::radians(angle));
(aabb2d.half_size().x * scale, aabb2d.center().x * scale)
});
let offset = Vec3A::from_array(axis_values.map(|(_, offset)| offset));
let cap_size = Vec3A::from_array(axis_values.map(|(max_val, _)| max_val)).abs();
let depth = isometry.rotation * Vec3A::new(0., 0., half_depth);
Aabb3d::new(isometry.translation - offset, cap_size + depth.abs())
}
/// Get a bounding sphere for an extrusion of the `base_shape` with the given `half_depth` with the given translation and rotation
fn extrusion_bounding_sphere(
&self,
half_depth: f32,
isometry: impl Into<Isometry3d>,
) -> BoundingSphere {
let isometry = isometry.into();
// We calculate the bounding circle of the base shape.
// Since each of the extrusions bases will have the same distance from its center,
// and they are just shifted along the Z-axis, the minimum bounding sphere will be the bounding sphere
// of the cylinder defined by the two bounding circles of the bases for any base shape
let BoundingCircle {
center,
circle: Circle { radius },
} = self.bounding_circle(Isometry2d::IDENTITY);
let radius = ops::hypot(radius, half_depth);
let center = isometry * Vec3A::from(center.extend(0.));
BoundingSphere::new(center, radius)
}
}
#[cfg(test)]
mod tests {
use core::f32::consts::FRAC_PI_4;
use glam::{EulerRot, Quat, Vec2, Vec3, Vec3A};
use crate::{
bounding::{Bounded3d, BoundingVolume},
ops,
primitives::{
Capsule2d, Circle, Ellipse, Extrusion, Line2d, Polygon, Polyline2d, Rectangle,
RegularPolygon, Segment2d, Triangle2d,
},
Dir2, Isometry3d,
};
#[test]
fn circle() {
let cylinder = Extrusion::new(Circle::new(0.5), 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cylinder.aabb_3d(translation);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 0.5, 1.0));
let bounding_sphere = cylinder.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
#[test]
fn ellipse() {
let extrusion = Extrusion::new(Ellipse::new(2.0, 0.5), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_euler(EulerRot::ZYX, FRAC_PI_4, FRAC_PI_4, FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), Vec3A::from(translation));
assert_eq!(aabb.half_size(), Vec3A::new(2.709784, 1.3801551, 2.436141));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));
}
#[test]
fn line() {
let extrusion = Extrusion::new(
Line2d {
direction: Dir2::new_unchecked(Vec2::Y),
},
4.,
);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_y(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.min, Vec3A::new(1.5857864, f32::MIN / 2., 3.5857865));
assert_eq!(aabb.max, Vec3A::new(4.4142136, f32::MAX / 2., 6.414213));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center(), translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.);
}
#[test]
fn rectangle() {
let extrusion = Extrusion::new(Rectangle::new(2.0, 1.0), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_z(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1.0606602, 1.0606602, 2.));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.291288);
}
#[test]
fn segment() {
let extrusion = Extrusion::new(
Segment2d::new(Vec2::new(0.0, -1.5), Vec2::new(0.0, 1.5)),
4.0,
);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0., 2.4748735, 2.4748735));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5);
}
#[test]
fn polyline() {
let polyline = Polyline2d::<4>::new([
Vec2::ONE,
Vec2::new(-1.0, 1.0),
Vec2::NEG_ONE,
Vec2::new(1.0, -1.0),
]);
let extrusion = Extrusion::new(polyline, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528);
}
#[test]
fn triangle() {
let triangle = Triangle2d::new(
Vec2::new(0.0, 1.0),
Vec2::new(-10.0, -1.0),
Vec2::new(10.0, -1.0),
);
let extrusion = Extrusion::new(triangle, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(10., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(
bounding_sphere.center,
Vec3A::new(3.0, 3.2928934, 4.2928934)
);
assert_eq!(bounding_sphere.radius(), 10.111875);
}
#[test]
fn polygon() {
let polygon = Polygon::<4>::new([
Vec2::ONE,
Vec2::new(-1.0, 1.0),
Vec2::NEG_ONE,
Vec2::new(1.0, -1.0),
]);
let extrusion = Extrusion::new(polygon, 3.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(1., 1.7677668, 1.7677668));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.0615528);
}
#[test]
fn regular_polygon() {
let extrusion = Extrusion::new(RegularPolygon::new(2.0, 7), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(
aabb.center(),
Vec3A::from(translation) + Vec3A::new(0., 0.0700254, 0.0700254)
);
assert_eq!(
aabb.half_size(),
Vec3A::new(1.9498558, 2.7584014, 2.7584019)
);
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::sqrt(8f32));
}
#[test]
fn capsule() {
let extrusion = Extrusion::new(Capsule2d::new(0.5, 2.0), 4.0);
let translation = Vec3::new(3., 4., 5.);
let rotation = Quat::from_rotation_x(FRAC_PI_4);
let isometry = Isometry3d::new(translation, rotation);
let aabb = extrusion.aabb_3d(isometry);
assert_eq!(aabb.center(), translation.into());
assert_eq!(aabb.half_size(), Vec3A::new(0.5, 2.4748735, 2.4748735));
let bounding_sphere = extrusion.bounding_sphere(isometry);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 2.5);
}
}

View File

@@ -0,0 +1,807 @@
mod extrusion;
mod primitive_impls;
use glam::Mat3;
use super::{BoundingVolume, IntersectsVolume};
use crate::{
ops::{self, FloatPow},
Isometry3d, Quat, Vec3A,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
pub use extrusion::BoundedExtrusion;
/// Computes the geometric center of the given set of points.
#[inline(always)]
fn point_cloud_3d_center(points: impl Iterator<Item = impl Into<Vec3A>>) -> Vec3A {
let (acc, len) = points.fold((Vec3A::ZERO, 0), |(acc, len), point| {
(acc + point.into(), len + 1)
});
assert!(
len > 0,
"cannot compute the center of an empty set of points"
);
acc / len as f32
}
/// A trait with methods that return 3D bounding volumes for a shape.
pub trait Bounded3d {
/// Get an axis-aligned bounding box for the shape translated and rotated by the given isometry.
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d;
/// Get a bounding sphere for the shape translated and rotated by the given isometry.
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere;
}
/// A 3D axis-aligned bounding box
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Aabb3d {
/// The minimum point of the box
pub min: Vec3A,
/// The maximum point of the box
pub max: Vec3A,
}
impl Aabb3d {
/// Constructs an AABB from its center and half-size.
#[inline(always)]
pub fn new(center: impl Into<Vec3A>, half_size: impl Into<Vec3A>) -> Self {
let (center, half_size) = (center.into(), half_size.into());
debug_assert!(half_size.x >= 0.0 && half_size.y >= 0.0 && half_size.z >= 0.0);
Self {
min: center - half_size,
max: center + half_size,
}
}
/// Computes the smallest [`Aabb3d`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// # Panics
///
/// Panics if the given set of points is empty.
#[inline(always)]
pub fn from_point_cloud(
isometry: impl Into<Isometry3d>,
points: impl Iterator<Item = impl Into<Vec3A>>,
) -> Aabb3d {
let isometry = isometry.into();
// Transform all points by rotation
let mut iter = points.map(|point| isometry.rotation * point.into());
let first = iter
.next()
.expect("point cloud must contain at least one point for Aabb3d construction");
let (min, max) = iter.fold((first, first), |(prev_min, prev_max), point| {
(point.min(prev_min), point.max(prev_max))
});
Aabb3d {
min: min + isometry.translation,
max: max + isometry.translation,
}
}
/// Computes the smallest [`BoundingSphere`] containing this [`Aabb3d`].
#[inline(always)]
pub fn bounding_sphere(&self) -> BoundingSphere {
let radius = self.min.distance(self.max) / 2.0;
BoundingSphere::new(self.center(), radius)
}
/// Finds the point on the AABB that is closest to the given `point`.
///
/// If the point is outside the AABB, the returned point will be on the surface of the AABB.
/// Otherwise, it will be inside the AABB and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
// Clamp point coordinates to the AABB
point.into().clamp(self.min, self.max)
}
}
impl BoundingVolume for Aabb3d {
type Translation = Vec3A;
type Rotation = Quat;
type HalfSize = Vec3A;
#[inline(always)]
fn center(&self) -> Self::Translation {
(self.min + self.max) / 2.
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
(self.max - self.min) / 2.
}
#[inline(always)]
fn visible_area(&self) -> f32 {
let b = self.max - self.min;
b.x * (b.y + b.z) + b.y * b.z
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
other.min.cmpge(self.min).all() && other.max.cmple(self.max).all()
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min - amount,
max: self.max + amount,
};
debug_assert!(b.min.cmple(b.max).all());
b
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
let b = Self {
min: self.min + amount,
max: self.max - amount,
};
debug_assert!(b.min.cmple(b.max).all());
b
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
let b = Self {
min: self.center() - (self.half_size() * scale),
max: self.center() + (self.half_size() * scale),
};
debug_assert!(b.min.cmple(b.max).all());
b
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transformed_by(
mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) -> Self {
self.transform_by(translation, rotation);
self
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn transform_by(
&mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) {
self.rotate_by(rotation);
self.translate_by(translation);
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
let translation = translation.into();
self.min += translation;
self.max += translation;
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
self.rotate_by(rotation);
self
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape.
///
/// Note that the result may not be as tightly fitting as the original, and repeated rotations
/// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB,
/// and consider storing the original AABB and rotating that every time instead.
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rot_mat = Mat3::from_quat(rotation.into());
let half_size = rot_mat.abs() * self.half_size();
*self = Self::new(rot_mat * self.center(), half_size);
}
}
impl IntersectsVolume<Self> for Aabb3d {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
self.min.cmple(other.max).all() && self.max.cmpge(other.min).all()
}
}
impl IntersectsVolume<BoundingSphere> for Aabb3d {
#[inline(always)]
fn intersects(&self, sphere: &BoundingSphere) -> bool {
let closest_point = self.closest_point(sphere.center);
let distance_squared = sphere.center.distance_squared(closest_point);
let radius_squared = sphere.radius().squared();
distance_squared <= radius_squared
}
}
#[cfg(test)]
mod aabb3d_tests {
use approx::assert_relative_eq;
use super::Aabb3d;
use crate::{
bounding::{BoundingSphere, BoundingVolume, IntersectsVolume},
ops, Quat, Vec3, Vec3A,
};
#[test]
fn center() {
let aabb = Aabb3d {
min: Vec3A::new(-0.5, -1., -0.5),
max: Vec3A::new(1., 1., 2.),
};
assert!((aabb.center() - Vec3A::new(0.25, 0., 0.75)).length() < f32::EPSILON);
let aabb = Aabb3d {
min: Vec3A::new(5., 5., -10.),
max: Vec3A::new(10., 10., -5.),
};
assert!((aabb.center() - Vec3A::new(7.5, 7.5, -7.5)).length() < f32::EPSILON);
}
#[test]
fn half_size() {
let aabb = Aabb3d {
min: Vec3A::new(-0.5, -1., -0.5),
max: Vec3A::new(1., 1., 2.),
};
assert!((aabb.half_size() - Vec3A::new(0.75, 1., 1.25)).length() < f32::EPSILON);
}
#[test]
fn area() {
let aabb = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
assert!(ops::abs(aabb.visible_area() - 12.) < f32::EPSILON);
let aabb = Aabb3d {
min: Vec3A::new(0., 0., 0.),
max: Vec3A::new(1., 0.5, 0.25),
};
assert!(ops::abs(aabb.visible_area() - 0.875) < f32::EPSILON);
}
#[test]
fn contains() {
let a = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
let b = Aabb3d {
min: Vec3A::new(-2., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
assert!(!a.contains(&b));
let b = Aabb3d {
min: Vec3A::new(-0.25, -0.8, -0.9),
max: Vec3A::new(1., 1., 0.9),
};
assert!(a.contains(&b));
}
#[test]
fn merge() {
let a = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 0.5, 1.),
};
let b = Aabb3d {
min: Vec3A::new(-2., -0.5, -0.),
max: Vec3A::new(0.75, 1., 2.),
};
let merged = a.merge(&b);
assert!((merged.min - Vec3A::new(-2., -1., -1.)).length() < f32::EPSILON);
assert!((merged.max - Vec3A::new(1., 1., 2.)).length() < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
}
#[test]
fn grow() {
let a = Aabb3d {
min: Vec3A::new(-1., -1., -1.),
max: Vec3A::new(1., 1., 1.),
};
let padded = a.grow(Vec3A::ONE);
assert!((padded.min - Vec3A::new(-2., -2., -2.)).length() < f32::EPSILON);
assert!((padded.max - Vec3A::new(2., 2., 2.)).length() < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = Aabb3d {
min: Vec3A::new(-2., -2., -2.),
max: Vec3A::new(2., 2., 2.),
};
let shrunk = a.shrink(Vec3A::ONE);
assert!((shrunk.min - Vec3A::new(-1., -1., -1.)).length() < f32::EPSILON);
assert!((shrunk.max - Vec3A::new(1., 1., 1.)).length() < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
let scaled = a.scale_around_center(Vec3A::splat(2.));
assert!((scaled.min - Vec3A::splat(-2.)).length() < f32::EPSILON);
assert!((scaled.max - Vec3A::splat(2.)).length() < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn rotate() {
use core::f32::consts::PI;
let a = Aabb3d {
min: Vec3A::new(-2.0, -2.0, -2.0),
max: Vec3A::new(2.0, 2.0, 2.0),
};
let rotation = Quat::from_euler(glam::EulerRot::XYZ, PI, PI, 0.0);
let rotated = a.rotated_by(rotation);
assert_relative_eq!(rotated.min, a.min);
assert_relative_eq!(rotated.max, a.max);
}
#[test]
fn transform() {
let a = Aabb3d {
min: Vec3A::new(-2.0, -2.0, -2.0),
max: Vec3A::new(2.0, 2.0, 2.0),
};
let transformed = a.transformed_by(
Vec3A::new(2.0, -2.0, 4.0),
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
);
let half_length = ops::hypot(2.0, 2.0);
assert_eq!(
transformed.min,
Vec3A::new(2.0 - half_length, -half_length - 2.0, 2.0)
);
assert_eq!(
transformed.max,
Vec3A::new(2.0 + half_length, half_length - 2.0, 6.0)
);
}
#[test]
fn closest_point() {
let aabb = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
assert_eq!(aabb.closest_point(Vec3A::X * 10.0), Vec3A::X);
assert_eq!(aabb.closest_point(Vec3A::NEG_ONE * 10.0), Vec3A::NEG_ONE);
assert_eq!(
aabb.closest_point(Vec3A::new(0.25, 0.1, 0.3)),
Vec3A::new(0.25, 0.1, 0.3)
);
}
#[test]
fn intersect_aabb() {
let aabb = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
assert!(aabb.intersects(&aabb));
assert!(aabb.intersects(&Aabb3d {
min: Vec3A::splat(0.5),
max: Vec3A::splat(2.0),
}));
assert!(aabb.intersects(&Aabb3d {
min: Vec3A::splat(-2.0),
max: Vec3A::splat(-0.5),
}));
assert!(!aabb.intersects(&Aabb3d {
min: Vec3A::new(1.1, 0.0, 0.0),
max: Vec3A::new(2.0, 0.5, 0.25),
}));
}
#[test]
fn intersect_bounding_sphere() {
let aabb = Aabb3d {
min: Vec3A::NEG_ONE,
max: Vec3A::ONE,
};
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
assert!(aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.5, 1.0)));
assert!(aabb.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.5, 1.0)));
assert!(!aabb.intersects(&BoundingSphere::new(Vec3::ONE * 1.75, 1.0)));
}
}
use crate::primitives::Sphere;
/// A bounding sphere
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(Serialize), derive(Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct BoundingSphere {
/// The center of the bounding sphere
pub center: Vec3A,
/// The sphere
pub sphere: Sphere,
}
impl BoundingSphere {
/// Constructs a bounding sphere from its center and radius.
pub fn new(center: impl Into<Vec3A>, radius: f32) -> Self {
debug_assert!(radius >= 0.);
Self {
center: center.into(),
sphere: Sphere { radius },
}
}
/// Computes a [`BoundingSphere`] containing the given set of points,
/// transformed by the rotation and translation of the given isometry.
///
/// The bounding sphere is not guaranteed to be the smallest possible.
#[inline(always)]
pub fn from_point_cloud(
isometry: impl Into<Isometry3d>,
points: &[impl Copy + Into<Vec3A>],
) -> BoundingSphere {
let isometry = isometry.into();
let center = point_cloud_3d_center(points.iter().map(|v| Into::<Vec3A>::into(*v)));
let mut radius_squared: f32 = 0.0;
for point in points {
// Get squared version to avoid unnecessary sqrt calls
let distance_squared = Into::<Vec3A>::into(*point).distance_squared(center);
if distance_squared > radius_squared {
radius_squared = distance_squared;
}
}
BoundingSphere::new(isometry * center, ops::sqrt(radius_squared))
}
/// Get the radius of the bounding sphere
#[inline(always)]
pub fn radius(&self) -> f32 {
self.sphere.radius
}
/// Computes the smallest [`Aabb3d`] containing this [`BoundingSphere`].
#[inline(always)]
pub fn aabb_3d(&self) -> Aabb3d {
Aabb3d {
min: self.center - self.radius(),
max: self.center + self.radius(),
}
}
/// Finds the point on the bounding sphere that is closest to the given `point`.
///
/// If the point is outside the sphere, the returned point will be on the surface of the sphere.
/// Otherwise, it will be inside the sphere and returned as is.
#[inline(always)]
pub fn closest_point(&self, point: impl Into<Vec3A>) -> Vec3A {
let point = point.into();
let radius = self.radius();
let distance_squared = (point - self.center).length_squared();
if distance_squared <= radius.squared() {
// The point is inside the sphere.
point
} else {
// The point is outside the sphere.
// Find the closest point on the surface of the sphere.
let dir_to_point = point / ops::sqrt(distance_squared);
self.center + radius * dir_to_point
}
}
}
impl BoundingVolume for BoundingSphere {
type Translation = Vec3A;
type Rotation = Quat;
type HalfSize = f32;
#[inline(always)]
fn center(&self) -> Self::Translation {
self.center
}
#[inline(always)]
fn half_size(&self) -> Self::HalfSize {
self.radius()
}
#[inline(always)]
fn visible_area(&self) -> f32 {
2. * core::f32::consts::PI * self.radius() * self.radius()
}
#[inline(always)]
fn contains(&self, other: &Self) -> bool {
let diff = self.radius() - other.radius();
self.center.distance_squared(other.center) <= ops::copysign(diff.squared(), diff)
}
#[inline(always)]
fn merge(&self, other: &Self) -> Self {
let diff = other.center - self.center;
let length = diff.length();
if self.radius() >= length + other.radius() {
return *self;
}
if other.radius() >= length + self.radius() {
return *other;
}
let dir = diff / length;
Self::new(
(self.center + other.center) / 2. + dir * ((other.radius() - self.radius()) / 2.),
(length + self.radius() + other.radius()) / 2.,
)
}
#[inline(always)]
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
Self {
center: self.center,
sphere: Sphere {
radius: self.radius() + amount,
},
}
}
#[inline(always)]
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self {
let amount = amount.into();
debug_assert!(amount >= 0.);
debug_assert!(self.radius() >= amount);
Self {
center: self.center,
sphere: Sphere {
radius: self.radius() - amount,
},
}
}
#[inline(always)]
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self {
let scale = scale.into();
debug_assert!(scale >= 0.);
Self::new(self.center, self.radius() * scale)
}
#[inline(always)]
fn translate_by(&mut self, translation: impl Into<Self::Translation>) {
self.center += translation.into();
}
#[inline(always)]
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>) {
let rotation: Quat = rotation.into();
self.center = rotation * self.center;
}
}
impl IntersectsVolume<Self> for BoundingSphere {
#[inline(always)]
fn intersects(&self, other: &Self) -> bool {
let center_distance_squared = self.center.distance_squared(other.center);
let radius_sum_squared = (self.radius() + other.radius()).squared();
center_distance_squared <= radius_sum_squared
}
}
impl IntersectsVolume<Aabb3d> for BoundingSphere {
#[inline(always)]
fn intersects(&self, aabb: &Aabb3d) -> bool {
aabb.intersects(self)
}
}
#[cfg(test)]
mod bounding_sphere_tests {
use approx::assert_relative_eq;
use super::BoundingSphere;
use crate::{
bounding::{BoundingVolume, IntersectsVolume},
ops, Quat, Vec3, Vec3A,
};
#[test]
fn area() {
let sphere = BoundingSphere::new(Vec3::ONE, 5.);
// Since this number is messy we check it with a higher threshold
assert!(ops::abs(sphere.visible_area() - 157.0796) < 0.001);
}
#[test]
fn contains() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let b = BoundingSphere::new(Vec3::new(5.5, 1., 1.), 1.);
assert!(!a.contains(&b));
let b = BoundingSphere::new(Vec3::new(1., -3.5, 1.), 0.5);
assert!(a.contains(&b));
}
#[test]
fn contains_identical() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
assert!(a.contains(&a));
}
#[test]
fn merge() {
// When merging two circles that don't contain each other, we find a center position that
// contains both
let a = BoundingSphere::new(Vec3::ONE, 5.);
let b = BoundingSphere::new(Vec3::new(1., 1., -4.), 1.);
let merged = a.merge(&b);
assert!((merged.center - Vec3A::new(1., 1., 0.5)).length() < f32::EPSILON);
assert!(ops::abs(merged.radius() - 5.5) < f32::EPSILON);
assert!(merged.contains(&a));
assert!(merged.contains(&b));
assert!(!a.contains(&merged));
assert!(!b.contains(&merged));
// When one circle contains the other circle, we use the bigger circle
let b = BoundingSphere::new(Vec3::ZERO, 3.);
assert!(a.contains(&b));
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
// When two circles are at the same point, we use the bigger radius
let b = BoundingSphere::new(Vec3::ONE, 6.);
let merged = a.merge(&b);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), b.radius());
}
#[test]
fn merge_identical() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let merged = a.merge(&a);
assert_eq!(merged.center, a.center);
assert_eq!(merged.radius(), a.radius());
}
#[test]
fn grow() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let padded = a.grow(1.25);
assert!(ops::abs(padded.radius() - 6.25) < f32::EPSILON);
assert!(padded.contains(&a));
assert!(!a.contains(&padded));
}
#[test]
fn shrink() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let shrunk = a.shrink(0.5);
assert!(ops::abs(shrunk.radius() - 4.5) < f32::EPSILON);
assert!(a.contains(&shrunk));
assert!(!shrunk.contains(&a));
}
#[test]
fn scale_around_center() {
let a = BoundingSphere::new(Vec3::ONE, 5.);
let scaled = a.scale_around_center(2.);
assert!(ops::abs(scaled.radius() - 10.) < f32::EPSILON);
assert!(!a.contains(&scaled));
assert!(scaled.contains(&a));
}
#[test]
fn transform() {
let a = BoundingSphere::new(Vec3::ONE, 5.0);
let transformed = a.transformed_by(
Vec3::new(2.0, -2.0, 4.0),
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
);
assert_relative_eq!(
transformed.center,
Vec3A::new(2.0, core::f32::consts::SQRT_2 - 2.0, 5.0)
);
assert_eq!(transformed.radius(), 5.0);
}
#[test]
fn closest_point() {
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3A::X);
assert_eq!(
sphere.closest_point(Vec3::NEG_ONE * 10.0),
Vec3A::NEG_ONE.normalize()
);
assert_eq!(
sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
Vec3A::new(0.25, 0.1, 0.3)
);
}
#[test]
fn intersect_bounding_sphere() {
let sphere = BoundingSphere::new(Vec3::ZERO, 1.0);
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ZERO, 1.0)));
assert!(sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.1, 1.0)));
assert!(sphere.intersects(&BoundingSphere::new(Vec3::NEG_ONE * 1.1, 1.0)));
assert!(!sphere.intersects(&BoundingSphere::new(Vec3::ONE * 1.2, 1.0)));
}
}

View File

@@ -0,0 +1,700 @@
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).
use crate::{
bounding::{Bounded2d, BoundingCircle, BoundingVolume},
ops,
primitives::{
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
},
Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A,
};
#[cfg(feature = "alloc")]
use crate::primitives::BoxedPolyline3d;
use super::{Aabb3d, Bounded3d, BoundingSphere};
impl Bounded3d for Sphere {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
Aabb3d::new(isometry.translation, Vec3::splat(self.radius))
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.radius)
}
}
impl Bounded3d for InfinitePlane3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let normal = isometry.rotation * *self.normal;
let facing_x = normal == Vec3::X || normal == Vec3::NEG_X;
let facing_y = normal == Vec3::Y || normal == Vec3::NEG_Y;
let facing_z = normal == Vec3::Z || normal == Vec3::NEG_Z;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
// like growing or shrinking the AABB without breaking things.
let half_width = if facing_x { 0.0 } else { f32::MAX / 2.0 };
let half_height = if facing_y { 0.0 } else { f32::MAX / 2.0 };
let half_depth = if facing_z { 0.0 } else { f32::MAX / 2.0 };
let half_size = Vec3A::new(half_width, half_height, half_depth);
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Line3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let direction = isometry.rotation * *self.direction;
// Dividing `f32::MAX` by 2.0 is helpful so that we can do operations
// like growing or shrinking the AABB without breaking things.
let max = f32::MAX / 2.0;
let half_width = if direction.x == 0.0 { 0.0 } else { max };
let half_height = if direction.y == 0.0 { 0.0 } else { max };
let half_depth = if direction.z == 0.0 { 0.0 } else { max };
let half_size = Vec3A::new(half_width, half_height, half_depth);
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, f32::MAX / 2.0)
}
}
impl Bounded3d for Segment3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, [self.point1(), self.point2()].iter().copied())
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let local_sphere = BoundingSphere::new(self.center(), self.length() / 2.);
local_sphere.transformed_by(isometry.translation, isometry.rotation)
}
}
impl<const N: usize> Bounded3d for Polyline3d<N> {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
#[cfg(feature = "alloc")]
impl Bounded3d for BoxedPolyline3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
Aabb3d::from_point_cloud(isometry, self.vertices.iter().copied())
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
BoundingSphere::from_point_cloud(isometry, &self.vertices)
}
}
impl Bounded3d for Cuboid {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Compute the AABB of the rotated cuboid by transforming the half-size
// by an absolute rotation matrix.
let rot_mat = Mat3::from_quat(isometry.rotation);
let abs_rot_mat = Mat3::from_cols(
rot_mat.x_axis.abs(),
rot_mat.y_axis.abs(),
rot_mat.z_axis.abs(),
);
let half_size = abs_rot_mat * self.half_size;
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.half_size.length())
}
}
impl Bounded3d for Cylinder {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_height;
let bottom = -top;
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_size = self.radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation + (top - half_size).min(bottom - half_size),
max: isometry.translation + (top + half_size).max(bottom + half_size),
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let radius = ops::hypot(self.radius, self.half_height);
BoundingSphere::new(isometry.translation, radius)
}
}
impl Bounded3d for Capsule3d {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Get the line segment between the hemispheres of the rotated capsule
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * self.half_length;
let bottom = -top;
// Expand the line segment by the capsule radius to get the capsule half-extents
let min = bottom.min(top) - Vec3A::splat(self.radius);
let max = bottom.max(top) + Vec3A::splat(self.radius);
Aabb3d {
min: min + isometry.translation,
max: max + isometry.translation,
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.radius + self.half_length)
}
}
impl Bounded3d for Cone {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation + top.min(bottom - self.radius * half_extents),
max: isometry.translation + top.max(bottom + self.radius * half_extents),
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
// Get the triangular cross-section of the cone.
let half_height = 0.5 * self.height;
let triangle = Triangle2d::new(
half_height * Vec2::Y,
Vec2::new(-self.radius, -half_height),
Vec2::new(self.radius, -half_height),
);
// Because of circular symmetry, we can use the bounding circle of the triangle
// for the bounding sphere of the cone.
let BoundingCircle { circle, center } = triangle.bounding_circle(Isometry2d::IDENTITY);
BoundingSphere::new(
isometry.rotation * Vec3A::from(center.extend(0.0)) + isometry.translation,
circle.radius,
)
}
}
impl Bounded3d for ConicalFrustum {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
// Reference: http://iquilezles.org/articles/diskbbox/
let isometry = isometry.into();
let segment_dir = isometry.rotation * Vec3A::Y;
let top = segment_dir * 0.5 * self.height;
let bottom = -top;
let e = (Vec3A::ONE - segment_dir * segment_dir).max(Vec3A::ZERO);
let half_extents = Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
Aabb3d {
min: isometry.translation
+ (top - self.radius_top * half_extents)
.min(bottom - self.radius_bottom * half_extents),
max: isometry.translation
+ (top + self.radius_top * half_extents)
.max(bottom + self.radius_bottom * half_extents),
}
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
let half_height = 0.5 * self.height;
// To compute the bounding sphere, we'll get the center and radius of the circumcircle
// passing through all four vertices of the trapezoidal cross-section of the conical frustum.
//
// If the circumcenter is inside the trapezoid, we can use that for the bounding sphere.
// Otherwise, we clamp it to the longer parallel side to get a more tightly fitting bounding sphere.
//
// The circumcenter is at the intersection of the bisectors perpendicular to the sides.
// For the isosceles trapezoid, the X coordinate is zero at the center, so a single bisector is enough.
//
// A
// *-------*
// / | \
// / | \
// AB / \ | / \
// / \ | / \
// / C \
// *-------------------*
// B
let a = Vec2::new(-self.radius_top, half_height);
let b = Vec2::new(-self.radius_bottom, -half_height);
let ab = a - b;
let ab_midpoint = b + 0.5 * ab;
let bisector = ab.perp();
// Compute intersection between bisector and vertical line at x = 0.
//
// x = ab_midpoint.x + t * bisector.x = 0
// y = ab_midpoint.y + t * bisector.y = ?
//
// Because ab_midpoint.y = 0 for our conical frustum, we get:
// y = t * bisector.y
//
// Solve x for t:
// t = -ab_midpoint.x / bisector.x
//
// Substitute t to solve for y:
// y = -ab_midpoint.x / bisector.x * bisector.y
let circumcenter_y = -ab_midpoint.x / bisector.x * bisector.y;
// If the circumcenter is outside the trapezoid, the bounding circle is too large.
// In those cases, we clamp it to the longer parallel side.
let (center, radius) = if circumcenter_y <= -half_height {
(Vec2::new(0.0, -half_height), self.radius_bottom)
} else if circumcenter_y >= half_height {
(Vec2::new(0.0, half_height), self.radius_top)
} else {
let circumcenter = Vec2::new(0.0, circumcenter_y);
// We can use the distance from an arbitrary vertex because they all lie on the circumcircle.
(circumcenter, a.distance(circumcenter))
};
BoundingSphere::new(
isometry.translation + isometry.rotation * Vec3A::from(center.extend(0.0)),
radius,
)
}
}
impl Bounded3d for Torus {
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
// Compute the AABB of a flat disc with the major radius of the torus.
// Reference: http://iquilezles.org/articles/diskbbox/
let normal = isometry.rotation * Vec3A::Y;
let e = (Vec3A::ONE - normal * normal).max(Vec3A::ZERO);
let disc_half_size =
self.major_radius * Vec3A::new(ops::sqrt(e.x), ops::sqrt(e.y), ops::sqrt(e.z));
// Expand the disc by the minor radius to get the torus half-size
let half_size = disc_half_size + Vec3A::splat(self.minor_radius);
Aabb3d::new(isometry.translation, half_size)
}
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
BoundingSphere::new(isometry.translation, self.outer_radius())
}
}
impl Bounded3d for Triangle3d {
/// Get the bounding box of the triangle.
fn aabb_3d(&self, isometry: impl Into<Isometry3d>) -> Aabb3d {
let isometry = isometry.into();
let [a, b, c] = self.vertices;
let a = isometry.rotation * a;
let b = isometry.rotation * b;
let c = isometry.rotation * c;
let min = Vec3A::from(a.min(b).min(c));
let max = Vec3A::from(a.max(b).max(c));
let bounding_center = (max + min) / 2.0 + isometry.translation;
let half_extents = (max - min) / 2.0;
Aabb3d::new(bounding_center, half_extents)
}
/// Get the bounding sphere of the triangle.
///
/// The [`Triangle3d`] implements the minimal bounding sphere calculation. For acute triangles, the circumcenter is used as
/// the center of the sphere. For the others, the bounding sphere is the minimal sphere
/// that contains the largest side of the triangle.
fn bounding_sphere(&self, isometry: impl Into<Isometry3d>) -> BoundingSphere {
let isometry = isometry.into();
if self.is_degenerate() || self.is_obtuse() {
let (p1, p2) = self.largest_side();
let (p1, p2) = (Vec3A::from(p1), Vec3A::from(p2));
let mid_point = (p1 + p2) / 2.0;
let radius = mid_point.distance(p1);
BoundingSphere::new(mid_point + isometry.translation, radius)
} else {
let [a, _, _] = self.vertices;
let circumcenter = self.circumcenter();
let radius = circumcenter.distance(a);
BoundingSphere::new(Vec3A::from(circumcenter) + isometry.translation, radius)
}
}
}
#[cfg(test)]
mod tests {
use crate::{bounding::BoundingVolume, ops, Isometry3d};
use glam::{Quat, Vec3, Vec3A};
use crate::{
bounding::Bounded3d,
primitives::{
Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d,
Segment3d, Sphere, Torus, Triangle3d,
},
Dir3,
};
#[test]
fn sphere() {
let sphere = Sphere { radius: 1.0 };
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = sphere.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = sphere.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.0);
}
#[test]
fn plane() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb1 = InfinitePlane3d::new(Vec3::X).aabb_3d(translation);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, -f32::MAX / 2.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, f32::MAX / 2.0));
let aabb2 = InfinitePlane3d::new(Vec3::Y).aabb_3d(translation);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, f32::MAX / 2.0));
let aabb3 = InfinitePlane3d::new(Vec3::Z).aabb_3d(translation);
assert_eq!(aabb3.min, Vec3A::new(-f32::MAX / 2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb3.max, Vec3A::new(f32::MAX / 2.0, f32::MAX / 2.0, 0.0));
let aabb4 = InfinitePlane3d::new(Vec3::ONE).aabb_3d(translation);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere = InfinitePlane3d::new(Vec3::Y).bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
#[test]
fn line() {
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb1 = Line3d { direction: Dir3::Y }.aabb_3d(translation);
assert_eq!(aabb1.min, Vec3A::new(2.0, -f32::MAX / 2.0, 0.0));
assert_eq!(aabb1.max, Vec3A::new(2.0, f32::MAX / 2.0, 0.0));
let aabb2 = Line3d { direction: Dir3::X }.aabb_3d(translation);
assert_eq!(aabb2.min, Vec3A::new(-f32::MAX / 2.0, 1.0, 0.0));
assert_eq!(aabb2.max, Vec3A::new(f32::MAX / 2.0, 1.0, 0.0));
let aabb3 = Line3d { direction: Dir3::Z }.aabb_3d(translation);
assert_eq!(aabb3.min, Vec3A::new(2.0, 1.0, -f32::MAX / 2.0));
assert_eq!(aabb3.max, Vec3A::new(2.0, 1.0, f32::MAX / 2.0));
let aabb4 = Line3d {
direction: Dir3::from_xyz(1.0, 1.0, 1.0).unwrap(),
}
.aabb_3d(translation);
assert_eq!(aabb4.min, Vec3A::splat(-f32::MAX / 2.0));
assert_eq!(aabb4.max, Vec3A::splat(f32::MAX / 2.0));
let bounding_sphere = Line3d { direction: Dir3::Y }.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), f32::MAX / 2.0);
}
#[test]
fn segment() {
let segment = Segment3d::new(Vec3::new(-1.0, -0.5, 0.0), Vec3::new(1.0, 0.5, 0.0));
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = segment.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.5, 0.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 1.5, 0.0));
let bounding_sphere = segment.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
#[test]
fn polyline() {
let polyline = Polyline3d::<4>::new([
Vec3::ONE,
Vec3::new(-1.0, 1.0, 1.0),
Vec3::NEG_ONE,
Vec3::new(1.0, -1.0, -1.0),
]);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = polyline.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = polyline.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(
bounding_sphere.radius(),
ops::hypot(ops::hypot(1.0, 1.0), 1.0)
);
}
#[test]
fn cuboid() {
let cuboid = Cuboid::new(2.0, 1.0, 1.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cuboid.aabb_3d(Isometry3d::new(
translation,
Quat::from_rotation_z(core::f32::consts::FRAC_PI_4),
));
let expected_half_size = Vec3A::new(1.0606601, 1.0606601, 0.5);
assert_eq!(aabb.min, Vec3A::from(translation) - expected_half_size);
assert_eq!(aabb.max, Vec3A::from(translation) + expected_half_size);
let bounding_sphere = cuboid.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(
bounding_sphere.radius(),
ops::hypot(ops::hypot(1.0, 0.5), 0.5)
);
}
#[test]
fn cylinder() {
let cylinder = Cylinder::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cylinder.aabb_3d(translation);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.0, 0.5)
);
assert_eq!(
aabb.max,
Vec3A::from(translation) + Vec3A::new(0.5, 1.0, 0.5)
);
let bounding_sphere = cylinder.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), ops::hypot(1.0, 0.5));
}
#[test]
fn capsule() {
let capsule = Capsule3d::new(0.5, 2.0);
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = capsule.aabb_3d(translation);
assert_eq!(
aabb.min,
Vec3A::from(translation) - Vec3A::new(0.5, 1.5, 0.5)
);
assert_eq!(
aabb.max,
Vec3A::from(translation) + Vec3A::new(0.5, 1.5, 0.5)
);
let bounding_sphere = capsule.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
#[test]
fn cone() {
let cone = Cone {
radius: 1.0,
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = cone.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = cone.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.25
);
assert_eq!(bounding_sphere.radius(), 1.25);
}
#[test]
fn conical_frustum() {
let conical_frustum = ConicalFrustum {
radius_top: 0.5,
radius_bottom: 1.0,
height: 2.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = conical_frustum.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(1.0, 0.0, -1.0));
assert_eq!(aabb.max, Vec3A::new(3.0, 2.0, 1.0));
let bounding_sphere = conical_frustum.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.1875
);
assert_eq!(bounding_sphere.radius(), 1.2884705);
}
#[test]
fn wide_conical_frustum() {
let conical_frustum = ConicalFrustum {
radius_top: 0.5,
radius_bottom: 5.0,
height: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = conical_frustum.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(-3.0, 0.5, -5.0));
assert_eq!(aabb.max, Vec3A::new(7.0, 1.5, 5.0));
// For wide conical frusta like this, the circumcenter can be outside the frustum,
// so the center and radius should be clamped to the longest side.
let bounding_sphere = conical_frustum.bounding_sphere(translation);
assert_eq!(
bounding_sphere.center,
Vec3A::from(translation) + Vec3A::NEG_Y * 0.5
);
assert_eq!(bounding_sphere.radius(), 5.0);
}
#[test]
fn torus() {
let torus = Torus {
minor_radius: 0.5,
major_radius: 1.0,
};
let translation = Vec3::new(2.0, 1.0, 0.0);
let aabb = torus.aabb_3d(translation);
assert_eq!(aabb.min, Vec3A::new(0.5, 0.5, -1.5));
assert_eq!(aabb.max, Vec3A::new(3.5, 1.5, 1.5));
let bounding_sphere = torus.bounding_sphere(translation);
assert_eq!(bounding_sphere.center, translation.into());
assert_eq!(bounding_sphere.radius(), 1.5);
}
#[test]
fn triangle3d() {
let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
let br = zero_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::ZERO.into(),
"incorrect bounding box half extents"
);
let bs = zero_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.0, "incorrect bounding sphere radius");
let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
let bs = dup_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 0.5, "incorrect bounding sphere radius");
let br = dup_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::new(0.5, 0.0, 0.0).into(),
"incorrect bounding box half extents"
);
let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
let bs = collinear_degenerate_triangle.bounding_sphere(Isometry3d::IDENTITY);
assert_eq!(
bs.center,
Vec3::ZERO.into(),
"incorrect bounding sphere center"
);
assert_eq!(bs.sphere.radius, 1.0, "incorrect bounding sphere radius");
let br = collinear_degenerate_triangle.aabb_3d(Isometry3d::IDENTITY);
assert_eq!(
br.center(),
Vec3::ZERO.into(),
"incorrect bounding box center"
);
assert_eq!(
br.half_size(),
Vec3::new(1.0, 0.0, 0.0).into(),
"incorrect bounding box half extents"
);
}
}

117
vendor/bevy_math/src/bounding/mod.rs vendored Normal file
View File

@@ -0,0 +1,117 @@
//! This module contains traits and implements for working with bounding shapes
//!
//! There are four traits used:
//! - [`BoundingVolume`] is a generic abstraction for any bounding volume
//! - [`IntersectsVolume`] abstracts intersection tests against a [`BoundingVolume`]
//! - [`Bounded2d`]/[`Bounded3d`] are abstractions for shapes to generate [`BoundingVolume`]s
/// A trait that generalizes different bounding volumes.
/// Bounding volumes are simplified shapes that are used to get simpler ways to check for
/// overlapping elements or finding intersections.
///
/// This trait supports both 2D and 3D bounding shapes.
pub trait BoundingVolume: Sized {
/// The position type used for the volume. This should be `Vec2` for 2D and `Vec3` for 3D.
type Translation: Clone + Copy + PartialEq;
/// The rotation type used for the volume. This should be `Rot2` for 2D and `Quat` for 3D.
type Rotation: Clone + Copy + PartialEq;
/// The type used for the size of the bounding volume. Usually a half size. For example an
/// `f32` radius for a circle, or a `Vec3` with half sizes for x, y and z for a 3D axis-aligned
/// bounding box
type HalfSize;
/// Returns the center of the bounding volume.
fn center(&self) -> Self::Translation;
/// Returns the half size of the bounding volume.
fn half_size(&self) -> Self::HalfSize;
/// Computes the visible surface area of the bounding volume.
/// This method can be useful to make decisions about merging bounding volumes,
/// using a Surface Area Heuristic.
///
/// For 2D shapes this would simply be the area of the shape.
/// For 3D shapes this would usually be half the area of the shape.
fn visible_area(&self) -> f32;
/// Checks if this bounding volume contains another one.
fn contains(&self, other: &Self) -> bool;
/// Computes the smallest bounding volume that contains both `self` and `other`.
fn merge(&self, other: &Self) -> Self;
/// Increases the size of the bounding volume in each direction by the given amount.
fn grow(&self, amount: impl Into<Self::HalfSize>) -> Self;
/// Decreases the size of the bounding volume in each direction by the given amount.
fn shrink(&self, amount: impl Into<Self::HalfSize>) -> Self;
/// Scale the size of the bounding volume around its center by the given amount
fn scale_around_center(&self, scale: impl Into<Self::HalfSize>) -> Self;
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
fn transformed_by(
mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) -> Self {
self.transform_by(translation, rotation);
self
}
/// Transforms the bounding volume by first rotating it around the origin and then applying a translation.
fn transform_by(
&mut self,
translation: impl Into<Self::Translation>,
rotation: impl Into<Self::Rotation>,
) {
self.rotate_by(rotation);
self.translate_by(translation);
}
/// Translates the bounding volume by the given translation.
fn translated_by(mut self, translation: impl Into<Self::Translation>) -> Self {
self.translate_by(translation);
self
}
/// Translates the bounding volume by the given translation.
fn translate_by(&mut self, translation: impl Into<Self::Translation>);
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is a combination of the original volume and the rotated volume,
/// so it is guaranteed to be either the same size or larger than the original.
fn rotated_by(mut self, rotation: impl Into<Self::Rotation>) -> Self {
self.rotate_by(rotation);
self
}
/// Rotates the bounding volume around the origin by the given rotation.
///
/// The result is a combination of the original volume and the rotated volume,
/// so it is guaranteed to be either the same size or larger than the original.
fn rotate_by(&mut self, rotation: impl Into<Self::Rotation>);
}
/// A trait that generalizes intersection tests against a volume.
/// Intersection tests can be used for a variety of tasks, for example:
/// - Raycasting
/// - Testing for overlap
/// - Checking if an object is within the view frustum of a camera
pub trait IntersectsVolume<Volume: BoundingVolume> {
/// Check if a volume intersects with this intersection test
fn intersects(&self, volume: &Volume) -> bool;
}
mod bounded2d;
pub use bounded2d::*;
mod bounded3d;
pub use bounded3d::*;
mod raycast2d;
pub use raycast2d::*;
mod raycast3d;
pub use raycast3d::*;

View File

@@ -0,0 +1,536 @@
use super::{Aabb2d, BoundingCircle, IntersectsVolume};
use crate::{
ops::{self, FloatPow},
Dir2, Ray2d, Vec2,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A raycast intersection test for 2D bounding volumes
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct RayCast2d {
/// The ray for the test
pub ray: Ray2d,
/// The maximum distance for the ray
pub max: f32,
/// The multiplicative inverse direction of the ray
direction_recip: Vec2,
}
impl RayCast2d {
/// Construct a [`RayCast2d`] from an origin, [`Dir2`], and max distance.
pub fn new(origin: Vec2, direction: Dir2, max: f32) -> Self {
Self::from_ray(Ray2d { origin, direction }, max)
}
/// Construct a [`RayCast2d`] from a [`Ray2d`] and max distance.
pub fn from_ray(ray: Ray2d, max: f32) -> Self {
Self {
ray,
direction_recip: ray.direction.recip(),
max,
}
}
/// Get the cached multiplicative inverse of the direction of the ray.
pub fn direction_recip(&self) -> Vec2 {
self.direction_recip
}
/// Get the distance of an intersection with an [`Aabb2d`], if any.
pub fn aabb_intersection_at(&self, aabb: &Aabb2d) -> Option<f32> {
let (min_x, max_x) = if self.ray.direction.x.is_sign_positive() {
(aabb.min.x, aabb.max.x)
} else {
(aabb.max.x, aabb.min.x)
};
let (min_y, max_y) = if self.ray.direction.y.is_sign_positive() {
(aabb.min.y, aabb.max.y)
} else {
(aabb.max.y, aabb.min.y)
};
// Calculate the minimum/maximum time for each axis based on how much the direction goes that
// way. These values can get arbitrarily large, or even become NaN, which is handled by the
// min/max operations below
let tmin_x = (min_x - self.ray.origin.x) * self.direction_recip.x;
let tmin_y = (min_y - self.ray.origin.y) * self.direction_recip.y;
let tmax_x = (max_x - self.ray.origin.x) * self.direction_recip.x;
let tmax_y = (max_y - self.ray.origin.y) * self.direction_recip.y;
// An axis that is not relevant to the ray direction will be NaN. When one of the arguments
// to min/max is NaN, the other argument is used.
// An axis for which the direction is the wrong way will return an arbitrarily large
// negative value.
let tmin = tmin_x.max(tmin_y).max(0.);
let tmax = tmax_y.min(tmax_x).min(self.max);
if tmin <= tmax {
Some(tmin)
} else {
None
}
}
/// Get the distance of an intersection with a [`BoundingCircle`], if any.
pub fn circle_intersection_at(&self, circle: &BoundingCircle) -> Option<f32> {
let offset = self.ray.origin - circle.center;
let projected = offset.dot(*self.ray.direction);
let closest_point = offset - projected * *self.ray.direction;
let distance_squared = circle.radius().squared() - closest_point.length_squared();
if distance_squared < 0.
|| ops::copysign(projected.squared(), -projected) < -distance_squared
{
None
} else {
let toi = -projected - ops::sqrt(distance_squared);
if toi > self.max {
None
} else {
Some(toi.max(0.))
}
}
}
}
impl IntersectsVolume<Aabb2d> for RayCast2d {
fn intersects(&self, volume: &Aabb2d) -> bool {
self.aabb_intersection_at(volume).is_some()
}
}
impl IntersectsVolume<BoundingCircle> for RayCast2d {
fn intersects(&self, volume: &BoundingCircle) -> bool {
self.circle_intersection_at(volume).is_some()
}
}
/// An intersection test that casts an [`Aabb2d`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct AabbCast2d {
/// The ray along which to cast the bounding volume
pub ray: RayCast2d,
/// The aabb that is being cast
pub aabb: Aabb2d,
}
impl AabbCast2d {
/// Construct an [`AabbCast2d`] from an [`Aabb2d`], origin, [`Dir2`], and max distance.
pub fn new(aabb: Aabb2d, origin: Vec2, direction: Dir2, max: f32) -> Self {
Self::from_ray(aabb, Ray2d { origin, direction }, max)
}
/// Construct an [`AabbCast2d`] from an [`Aabb2d`], [`Ray2d`], and max distance.
pub fn from_ray(aabb: Aabb2d, ray: Ray2d, max: f32) -> Self {
Self {
ray: RayCast2d::from_ray(ray, max),
aabb,
}
}
/// Get the distance at which the [`Aabb2d`]s collide, if at all.
pub fn aabb_collision_at(&self, mut aabb: Aabb2d) -> Option<f32> {
aabb.min -= self.aabb.max;
aabb.max -= self.aabb.min;
self.ray.aabb_intersection_at(&aabb)
}
}
impl IntersectsVolume<Aabb2d> for AabbCast2d {
fn intersects(&self, volume: &Aabb2d) -> bool {
self.aabb_collision_at(*volume).is_some()
}
}
/// An intersection test that casts a [`BoundingCircle`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct BoundingCircleCast {
/// The ray along which to cast the bounding volume
pub ray: RayCast2d,
/// The circle that is being cast
pub circle: BoundingCircle,
}
impl BoundingCircleCast {
/// Construct a [`BoundingCircleCast`] from a [`BoundingCircle`], origin, [`Dir2`], and max distance.
pub fn new(circle: BoundingCircle, origin: Vec2, direction: Dir2, max: f32) -> Self {
Self::from_ray(circle, Ray2d { origin, direction }, max)
}
/// Construct a [`BoundingCircleCast`] from a [`BoundingCircle`], [`Ray2d`], and max distance.
pub fn from_ray(circle: BoundingCircle, ray: Ray2d, max: f32) -> Self {
Self {
ray: RayCast2d::from_ray(ray, max),
circle,
}
}
/// Get the distance at which the [`BoundingCircle`]s collide, if at all.
pub fn circle_collision_at(&self, mut circle: BoundingCircle) -> Option<f32> {
circle.center -= self.circle.center;
circle.circle.radius += self.circle.radius();
self.ray.circle_intersection_at(&circle)
}
}
impl IntersectsVolume<BoundingCircle> for BoundingCircleCast {
fn intersects(&self, volume: &BoundingCircle) -> bool {
self.circle_collision_at(*volume).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 0.001;
#[test]
fn test_ray_intersection_circle_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered bounding circle
RayCast2d::new(Vec2::Y * -5., Dir2::Y, 90.),
BoundingCircle::new(Vec2::ZERO, 1.),
4.,
),
(
// Hit the center of a centered bounding circle, but from the other side
RayCast2d::new(Vec2::Y * 5., -Dir2::Y, 90.),
BoundingCircle::new(Vec2::ZERO, 1.),
4.,
),
(
// Hit the center of an offset circle
RayCast2d::new(Vec2::ZERO, Dir2::Y, 90.),
BoundingCircle::new(Vec2::Y * 3., 2.),
1.,
),
(
// Just barely hit the circle before the max distance
RayCast2d::new(Vec2::X, Dir2::Y, 1.),
BoundingCircle::new(Vec2::ONE, 0.01),
0.99,
),
(
// Hit a circle off-center
RayCast2d::new(Vec2::X, Dir2::Y, 90.),
BoundingCircle::new(Vec2::Y * 5., 2.),
3.268,
),
(
// Barely hit a circle on the side
RayCast2d::new(Vec2::X * 0.99999, Dir2::Y, 90.),
BoundingCircle::new(Vec2::Y * 5., 1.),
4.996,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.circle_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast2d::new(test.ray.origin, -test.ray.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_circle_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast2d::new(Vec2::ZERO, Dir2::X, 90.),
BoundingCircle::new(Vec2::Y * 2., 1.),
),
(
// Ray's alignment isn't enough to hit the circle
RayCast2d::new(Vec2::ZERO, Dir2::from_xy(1., 1.).unwrap(), 90.),
BoundingCircle::new(Vec2::Y * 2., 1.),
),
(
// The ray's maximum distance isn't high enough
RayCast2d::new(Vec2::ZERO, Dir2::Y, 0.5),
BoundingCircle::new(Vec2::Y * 2., 1.),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_circle_inside() {
let volume = BoundingCircle::new(Vec2::splat(0.5), 1.);
for origin in &[Vec2::X, Vec2::Y, Vec2::ONE, Vec2::ZERO] {
for direction in &[Dir2::X, Dir2::Y, -Dir2::X, -Dir2::Y] {
for max in &[0., 1., 900.] {
let test = RayCast2d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.circle_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_ray_intersection_aabb_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered aabb
RayCast2d::new(Vec2::Y * -5., Dir2::Y, 90.),
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
4.,
),
(
// Hit the center of a centered aabb, but from the other side
RayCast2d::new(Vec2::Y * 5., -Dir2::Y, 90.),
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
4.,
),
(
// Hit the center of an offset aabb
RayCast2d::new(Vec2::ZERO, Dir2::Y, 90.),
Aabb2d::new(Vec2::Y * 3., Vec2::splat(2.)),
1.,
),
(
// Just barely hit the aabb before the max distance
RayCast2d::new(Vec2::X, Dir2::Y, 1.),
Aabb2d::new(Vec2::ONE, Vec2::splat(0.01)),
0.99,
),
(
// Hit an aabb off-center
RayCast2d::new(Vec2::X, Dir2::Y, 90.),
Aabb2d::new(Vec2::Y * 5., Vec2::splat(2.)),
3.,
),
(
// Barely hit an aabb on corner
RayCast2d::new(Vec2::X * -0.001, Dir2::from_xy(1., 1.).unwrap(), 90.),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
1.414,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast2d::new(test.ray.origin, -test.ray.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast2d::new(Vec2::ZERO, Dir2::X, 90.),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
),
(
// Ray's alignment isn't enough to hit the aabb
RayCast2d::new(Vec2::ZERO, Dir2::from_xy(1., 0.99).unwrap(), 90.),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
),
(
// The ray's maximum distance isn't high enough
RayCast2d::new(Vec2::ZERO, Dir2::Y, 0.5),
Aabb2d::new(Vec2::Y * 2., Vec2::ONE),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_inside() {
let volume = Aabb2d::new(Vec2::splat(0.5), Vec2::ONE);
for origin in &[Vec2::X, Vec2::Y, Vec2::ONE, Vec2::ZERO] {
for direction in &[Dir2::X, Dir2::Y, -Dir2::X, -Dir2::Y] {
for max in &[0., 1., 900.] {
let test = RayCast2d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.aabb_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_aabb_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the aabb, that a ray would've also hit
AabbCast2d::new(Aabb2d::new(Vec2::ZERO, Vec2::ONE), Vec2::ZERO, Dir2::Y, 90.),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
(
// Hit the center of the aabb, but from the other side
AabbCast2d::new(
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
Vec2::Y * 10.,
-Dir2::Y,
90.,
),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
(
// Hit the edge of the aabb, that a ray would've missed
AabbCast2d::new(
Aabb2d::new(Vec2::ZERO, Vec2::ONE),
Vec2::X * 1.5,
Dir2::Y,
90.,
),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
(
// Hit the edge of the aabb, by casting an off-center AABB
AabbCast2d::new(
Aabb2d::new(Vec2::X * -2., Vec2::ONE),
Vec2::X * 3.,
Dir2::Y,
90.,
),
Aabb2d::new(Vec2::Y * 5., Vec2::ONE),
3.,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray =
RayCast2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_circle_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the bounding circle, that a ray would've also hit
BoundingCircleCast::new(
BoundingCircle::new(Vec2::ZERO, 1.),
Vec2::ZERO,
Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.,
),
(
// Hit the center of the bounding circle, but from the other side
BoundingCircleCast::new(
BoundingCircle::new(Vec2::ZERO, 1.),
Vec2::Y * 10.,
-Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.,
),
(
// Hit the bounding circle off-center, that a ray would've missed
BoundingCircleCast::new(
BoundingCircle::new(Vec2::ZERO, 1.),
Vec2::X * 1.5,
Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.677,
),
(
// Hit the bounding circle off-center, by casting a circle that is off-center
BoundingCircleCast::new(
BoundingCircle::new(Vec2::X * -1.5, 1.),
Vec2::X * 3.,
Dir2::Y,
90.,
),
BoundingCircle::new(Vec2::Y * 5., 1.),
3.677,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.circle_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray =
RayCast2d::new(test.ray.ray.origin, -test.ray.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
}

View File

@@ -0,0 +1,546 @@
use super::{Aabb3d, BoundingSphere, IntersectsVolume};
use crate::{
ops::{self, FloatPow},
Dir3A, Ray3d, Vec3A,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A raycast intersection test for 3D bounding volumes
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct RayCast3d {
/// The origin of the ray.
pub origin: Vec3A,
/// The direction of the ray.
pub direction: Dir3A,
/// The maximum distance for the ray
pub max: f32,
/// The multiplicative inverse direction of the ray
direction_recip: Vec3A,
}
impl RayCast3d {
/// Construct a [`RayCast3d`] from an origin, [direction], and max distance.
///
/// [direction]: crate::direction::Dir3
pub fn new(origin: impl Into<Vec3A>, direction: impl Into<Dir3A>, max: f32) -> Self {
let direction = direction.into();
Self {
origin: origin.into(),
direction,
direction_recip: direction.recip(),
max,
}
}
/// Construct a [`RayCast3d`] from a [`Ray3d`] and max distance.
pub fn from_ray(ray: Ray3d, max: f32) -> Self {
Self::new(ray.origin, ray.direction, max)
}
/// Get the cached multiplicative inverse of the direction of the ray.
pub fn direction_recip(&self) -> Vec3A {
self.direction_recip
}
/// Get the distance of an intersection with an [`Aabb3d`], if any.
pub fn aabb_intersection_at(&self, aabb: &Aabb3d) -> Option<f32> {
let positive = self.direction.signum().cmpgt(Vec3A::ZERO);
let min = Vec3A::select(positive, aabb.min, aabb.max);
let max = Vec3A::select(positive, aabb.max, aabb.min);
// Calculate the minimum/maximum time for each axis based on how much the direction goes that
// way. These values can get arbitrarily large, or even become NaN, which is handled by the
// min/max operations below
let tmin = (min - self.origin) * self.direction_recip;
let tmax = (max - self.origin) * self.direction_recip;
// An axis that is not relevant to the ray direction will be NaN. When one of the arguments
// to min/max is NaN, the other argument is used.
// An axis for which the direction is the wrong way will return an arbitrarily large
// negative value.
let tmin = tmin.max_element().max(0.);
let tmax = tmax.min_element().min(self.max);
if tmin <= tmax {
Some(tmin)
} else {
None
}
}
/// Get the distance of an intersection with a [`BoundingSphere`], if any.
pub fn sphere_intersection_at(&self, sphere: &BoundingSphere) -> Option<f32> {
let offset = self.origin - sphere.center;
let projected = offset.dot(*self.direction);
let closest_point = offset - projected * *self.direction;
let distance_squared = sphere.radius().squared() - closest_point.length_squared();
if distance_squared < 0.
|| ops::copysign(projected.squared(), -projected) < -distance_squared
{
None
} else {
let toi = -projected - ops::sqrt(distance_squared);
if toi > self.max {
None
} else {
Some(toi.max(0.))
}
}
}
}
impl IntersectsVolume<Aabb3d> for RayCast3d {
fn intersects(&self, volume: &Aabb3d) -> bool {
self.aabb_intersection_at(volume).is_some()
}
}
impl IntersectsVolume<BoundingSphere> for RayCast3d {
fn intersects(&self, volume: &BoundingSphere) -> bool {
self.sphere_intersection_at(volume).is_some()
}
}
/// An intersection test that casts an [`Aabb3d`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct AabbCast3d {
/// The ray along which to cast the bounding volume
pub ray: RayCast3d,
/// The aabb that is being cast
pub aabb: Aabb3d,
}
impl AabbCast3d {
/// Construct an [`AabbCast3d`] from an [`Aabb3d`], origin, [direction], and max distance.
///
/// [direction]: crate::direction::Dir3
pub fn new(
aabb: Aabb3d,
origin: impl Into<Vec3A>,
direction: impl Into<Dir3A>,
max: f32,
) -> Self {
Self {
ray: RayCast3d::new(origin, direction, max),
aabb,
}
}
/// Construct an [`AabbCast3d`] from an [`Aabb3d`], [`Ray3d`], and max distance.
pub fn from_ray(aabb: Aabb3d, ray: Ray3d, max: f32) -> Self {
Self::new(aabb, ray.origin, ray.direction, max)
}
/// Get the distance at which the [`Aabb3d`]s collide, if at all.
pub fn aabb_collision_at(&self, mut aabb: Aabb3d) -> Option<f32> {
aabb.min -= self.aabb.max;
aabb.max -= self.aabb.min;
self.ray.aabb_intersection_at(&aabb)
}
}
impl IntersectsVolume<Aabb3d> for AabbCast3d {
fn intersects(&self, volume: &Aabb3d) -> bool {
self.aabb_collision_at(*volume).is_some()
}
}
/// An intersection test that casts a [`BoundingSphere`] along a ray.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
pub struct BoundingSphereCast {
/// The ray along which to cast the bounding volume
pub ray: RayCast3d,
/// The sphere that is being cast
pub sphere: BoundingSphere,
}
impl BoundingSphereCast {
/// Construct a [`BoundingSphereCast`] from a [`BoundingSphere`], origin, [direction], and max distance.
///
/// [direction]: crate::direction::Dir3
pub fn new(
sphere: BoundingSphere,
origin: impl Into<Vec3A>,
direction: impl Into<Dir3A>,
max: f32,
) -> Self {
Self {
ray: RayCast3d::new(origin, direction, max),
sphere,
}
}
/// Construct a [`BoundingSphereCast`] from a [`BoundingSphere`], [`Ray3d`], and max distance.
pub fn from_ray(sphere: BoundingSphere, ray: Ray3d, max: f32) -> Self {
Self::new(sphere, ray.origin, ray.direction, max)
}
/// Get the distance at which the [`BoundingSphere`]s collide, if at all.
pub fn sphere_collision_at(&self, mut sphere: BoundingSphere) -> Option<f32> {
sphere.center -= self.sphere.center;
sphere.sphere.radius += self.sphere.radius();
self.ray.sphere_intersection_at(&sphere)
}
}
impl IntersectsVolume<BoundingSphere> for BoundingSphereCast {
fn intersects(&self, volume: &BoundingSphere) -> bool {
self.sphere_collision_at(*volume).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Dir3, Vec3};
const EPSILON: f32 = 0.001;
#[test]
fn test_ray_intersection_sphere_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered bounding sphere
RayCast3d::new(Vec3::Y * -5., Dir3::Y, 90.),
BoundingSphere::new(Vec3::ZERO, 1.),
4.,
),
(
// Hit the center of a centered bounding sphere, but from the other side
RayCast3d::new(Vec3::Y * 5., -Dir3::Y, 90.),
BoundingSphere::new(Vec3::ZERO, 1.),
4.,
),
(
// Hit the center of an offset sphere
RayCast3d::new(Vec3::ZERO, Dir3::Y, 90.),
BoundingSphere::new(Vec3::Y * 3., 2.),
1.,
),
(
// Just barely hit the sphere before the max distance
RayCast3d::new(Vec3::X, Dir3::Y, 1.),
BoundingSphere::new(Vec3::new(1., 1., 0.), 0.01),
0.99,
),
(
// Hit a sphere off-center
RayCast3d::new(Vec3::X, Dir3::Y, 90.),
BoundingSphere::new(Vec3::Y * 5., 2.),
3.268,
),
(
// Barely hit a sphere on the side
RayCast3d::new(Vec3::X * 0.99999, Dir3::Y, 90.),
BoundingSphere::new(Vec3::Y * 5., 1.),
4.996,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.sphere_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.origin, -test.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_sphere_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast3d::new(Vec3::ZERO, Dir3::X, 90.),
BoundingSphere::new(Vec3::Y * 2., 1.),
),
(
// Ray's alignment isn't enough to hit the sphere
RayCast3d::new(Vec3::ZERO, Dir3::from_xyz(1., 1., 1.).unwrap(), 90.),
BoundingSphere::new(Vec3::Y * 2., 1.),
),
(
// The ray's maximum distance isn't high enough
RayCast3d::new(Vec3::ZERO, Dir3::Y, 0.5),
BoundingSphere::new(Vec3::Y * 2., 1.),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_sphere_inside() {
let volume = BoundingSphere::new(Vec3::splat(0.5), 1.);
for origin in &[Vec3::X, Vec3::Y, Vec3::ONE, Vec3::ZERO] {
for direction in &[Dir3::X, Dir3::Y, Dir3::Z, -Dir3::X, -Dir3::Y, -Dir3::Z] {
for max in &[0., 1., 900.] {
let test = RayCast3d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.sphere_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_ray_intersection_aabb_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of a centered aabb
RayCast3d::new(Vec3::Y * -5., Dir3::Y, 90.),
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
4.,
),
(
// Hit the center of a centered aabb, but from the other side
RayCast3d::new(Vec3::Y * 5., -Dir3::Y, 90.),
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
4.,
),
(
// Hit the center of an offset aabb
RayCast3d::new(Vec3::ZERO, Dir3::Y, 90.),
Aabb3d::new(Vec3::Y * 3., Vec3::splat(2.)),
1.,
),
(
// Just barely hit the aabb before the max distance
RayCast3d::new(Vec3::X, Dir3::Y, 1.),
Aabb3d::new(Vec3::new(1., 1., 0.), Vec3::splat(0.01)),
0.99,
),
(
// Hit an aabb off-center
RayCast3d::new(Vec3::X, Dir3::Y, 90.),
Aabb3d::new(Vec3::Y * 5., Vec3::splat(2.)),
3.,
),
(
// Barely hit an aabb on corner
RayCast3d::new(Vec3::X * -0.001, Dir3::from_xyz(1., 1., 1.).unwrap(), 90.),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
1.732,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_intersection_at(volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.origin, -test.direction, test.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_misses() {
for (test, volume) in &[
(
// The ray doesn't go in the right direction
RayCast3d::new(Vec3::ZERO, Dir3::X, 90.),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
),
(
// Ray's alignment isn't enough to hit the aabb
RayCast3d::new(Vec3::ZERO, Dir3::from_xyz(1., 0.99, 1.).unwrap(), 90.),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
),
(
// The ray's maximum distance isn't high enough
RayCast3d::new(Vec3::ZERO, Dir3::Y, 0.5),
Aabb3d::new(Vec3::Y * 2., Vec3::ONE),
),
] {
assert!(
!test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}",
);
}
}
#[test]
fn test_ray_intersection_aabb_inside() {
let volume = Aabb3d::new(Vec3::splat(0.5), Vec3::ONE);
for origin in &[Vec3::X, Vec3::Y, Vec3::ONE, Vec3::ZERO] {
for direction in &[Dir3::X, Dir3::Y, Dir3::Z, -Dir3::X, -Dir3::Y, -Dir3::Z] {
for max in &[0., 1., 900.] {
let test = RayCast3d::new(*origin, *direction, *max);
assert!(
test.intersects(&volume),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
let actual_distance = test.aabb_intersection_at(&volume);
assert_eq!(
actual_distance,
Some(0.),
"Case:\n origin: {origin:?}\n Direction: {direction:?}\n Max: {max}",
);
}
}
}
}
#[test]
fn test_aabb_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the aabb, that a ray would've also hit
AabbCast3d::new(Aabb3d::new(Vec3::ZERO, Vec3::ONE), Vec3::ZERO, Dir3::Y, 90.),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
(
// Hit the center of the aabb, but from the other side
AabbCast3d::new(
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
Vec3::Y * 10.,
-Dir3::Y,
90.,
),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
(
// Hit the edge of the aabb, that a ray would've missed
AabbCast3d::new(
Aabb3d::new(Vec3::ZERO, Vec3::ONE),
Vec3::X * 1.5,
Dir3::Y,
90.,
),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
(
// Hit the edge of the aabb, by casting an off-center AABB
AabbCast3d::new(
Aabb3d::new(Vec3::X * -2., Vec3::ONE),
Vec3::X * 3.,
Dir3::Y,
90.,
),
Aabb3d::new(Vec3::Y * 5., Vec3::ONE),
3.,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.aabb_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.ray.origin, -test.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
#[test]
fn test_sphere_cast_hits() {
for (test, volume, expected_distance) in &[
(
// Hit the center of the bounding sphere, that a ray would've also hit
BoundingSphereCast::new(
BoundingSphere::new(Vec3::ZERO, 1.),
Vec3::ZERO,
Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.,
),
(
// Hit the center of the bounding sphere, but from the other side
BoundingSphereCast::new(
BoundingSphere::new(Vec3::ZERO, 1.),
Vec3::Y * 10.,
-Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.,
),
(
// Hit the bounding sphere off-center, that a ray would've missed
BoundingSphereCast::new(
BoundingSphere::new(Vec3::ZERO, 1.),
Vec3::X * 1.5,
Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.677,
),
(
// Hit the bounding sphere off-center, by casting a sphere that is off-center
BoundingSphereCast::new(
BoundingSphere::new(Vec3::X * -1.5, 1.),
Vec3::X * 3.,
Dir3::Y,
90.,
),
BoundingSphere::new(Vec3::Y * 5., 1.),
3.677,
),
] {
assert!(
test.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
let actual_distance = test.sphere_collision_at(*volume).unwrap();
assert!(
ops::abs(actual_distance - expected_distance) < EPSILON,
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}\n Actual distance: {actual_distance}",
);
let inverted_ray = RayCast3d::new(test.ray.origin, -test.ray.direction, test.ray.max);
assert!(
!inverted_ray.intersects(volume),
"Case:\n Test: {test:?}\n Volume: {volume:?}\n Expected distance: {expected_distance:?}",
);
}
}
}

471
vendor/bevy_math/src/common_traits.rs vendored Normal file
View File

@@ -0,0 +1,471 @@
//! This module contains abstract mathematical traits shared by types used in `bevy_math`.
use crate::{ops, Dir2, Dir3, Dir3A, Quat, Rot2, Vec2, Vec3, Vec3A, Vec4};
use core::{
fmt::Debug,
ops::{Add, Div, Mul, Neg, Sub},
};
use variadics_please::all_tuples_enumerated;
/// A type that supports the mathematical operations of a real vector space, irrespective of dimension.
/// In particular, this means that the implementing type supports:
/// - Scalar multiplication and division on the right by elements of `f32`
/// - Negation
/// - Addition and subtraction
/// - Zero
///
/// Within the limitations of floating point arithmetic, all the following are required to hold:
/// - (Associativity of addition) For all `u, v, w: Self`, `(u + v) + w == u + (v + w)`.
/// - (Commutativity of addition) For all `u, v: Self`, `u + v == v + u`.
/// - (Additive identity) For all `v: Self`, `v + Self::ZERO == v`.
/// - (Additive inverse) For all `v: Self`, `v - v == v + (-v) == Self::ZERO`.
/// - (Compatibility of multiplication) For all `a, b: f32`, `v: Self`, `v * (a * b) == (v * a) * b`.
/// - (Multiplicative identity) For all `v: Self`, `v * 1.0 == v`.
/// - (Distributivity for vector addition) For all `a: f32`, `u, v: Self`, `(u + v) * a == u * a + v * a`.
/// - (Distributivity for scalar addition) For all `a, b: f32`, `v: Self`, `v * (a + b) == v * a + v * b`.
///
/// Note that, because implementing types use floating point arithmetic, they are not required to actually
/// implement `PartialEq` or `Eq`.
pub trait VectorSpace:
Mul<f32, Output = Self>
+ Div<f32, Output = Self>
+ Add<Self, Output = Self>
+ Sub<Self, Output = Self>
+ Neg<Output = Self>
+ Default
+ Debug
+ Clone
+ Copy
{
/// The zero vector, which is the identity of addition for the vector space type.
const ZERO: Self;
/// Perform vector space linear interpolation between this element and another, based
/// on the parameter `t`. When `t` is `0`, `self` is recovered. When `t` is `1`, `rhs`
/// is recovered.
///
/// Note that the value of `t` is not clamped by this function, so extrapolating outside
/// of the interval `[0,1]` is allowed.
#[inline]
fn lerp(self, rhs: Self, t: f32) -> Self {
self * (1. - t) + rhs * t
}
}
impl VectorSpace for Vec4 {
const ZERO: Self = Vec4::ZERO;
}
impl VectorSpace for Vec3 {
const ZERO: Self = Vec3::ZERO;
}
impl VectorSpace for Vec3A {
const ZERO: Self = Vec3A::ZERO;
}
impl VectorSpace for Vec2 {
const ZERO: Self = Vec2::ZERO;
}
impl VectorSpace for f32 {
const ZERO: Self = 0.0;
}
/// A type consisting of formal sums of elements from `V` and `W`. That is,
/// each value `Sum(v, w)` is thought of as `v + w`, with no available
/// simplification. In particular, if `V` and `W` are [vector spaces], then
/// `Sum<V, W>` is a vector space whose dimension is the sum of those of `V`
/// and `W`, and the field accessors `.0` and `.1` are vector space projections.
///
/// [vector spaces]: VectorSpace
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Sum<V, W>(pub V, pub W);
impl<V, W> Mul<f32> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Sum(self.0 * rhs, self.1 * rhs)
}
}
impl<V, W> Div<f32> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Sum(self.0 / rhs, self.1 / rhs)
}
}
impl<V, W> Add<Self> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Sum(self.0 + other.0, self.1 + other.1)
}
}
impl<V, W> Sub<Self> for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
Sum(self.0 - other.0, self.1 - other.1)
}
}
impl<V, W> Neg for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
type Output = Self;
fn neg(self) -> Self::Output {
Sum(-self.0, -self.1)
}
}
impl<V, W> Default for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
fn default() -> Self {
Sum(V::default(), W::default())
}
}
impl<V, W> VectorSpace for Sum<V, W>
where
V: VectorSpace,
W: VectorSpace,
{
const ZERO: Self = Sum(V::ZERO, W::ZERO);
}
/// A type that supports the operations of a normed vector space; i.e. a norm operation in addition
/// to those of [`VectorSpace`]. Specifically, the implementor must guarantee that the following
/// relationships hold, within the limitations of floating point arithmetic:
/// - (Nonnegativity) For all `v: Self`, `v.norm() >= 0.0`.
/// - (Positive definiteness) For all `v: Self`, `v.norm() == 0.0` implies `v == Self::ZERO`.
/// - (Absolute homogeneity) For all `c: f32`, `v: Self`, `(v * c).norm() == v.norm() * c.abs()`.
/// - (Triangle inequality) For all `v, w: Self`, `(v + w).norm() <= v.norm() + w.norm()`.
///
/// Note that, because implementing types use floating point arithmetic, they are not required to actually
/// implement `PartialEq` or `Eq`.
pub trait NormedVectorSpace: VectorSpace {
/// The size of this element. The return value should always be nonnegative.
fn norm(self) -> f32;
/// The squared norm of this element. Computing this is often faster than computing
/// [`NormedVectorSpace::norm`].
#[inline]
fn norm_squared(self) -> f32 {
self.norm() * self.norm()
}
/// The distance between this element and another, as determined by the norm.
#[inline]
fn distance(self, rhs: Self) -> f32 {
(rhs - self).norm()
}
/// The squared distance between this element and another, as determined by the norm. Note that
/// this is often faster to compute in practice than [`NormedVectorSpace::distance`].
#[inline]
fn distance_squared(self, rhs: Self) -> f32 {
(rhs - self).norm_squared()
}
}
impl NormedVectorSpace for Vec4 {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for Vec3 {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for Vec3A {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for Vec2 {
#[inline]
fn norm(self) -> f32 {
self.length()
}
#[inline]
fn norm_squared(self) -> f32 {
self.length_squared()
}
}
impl NormedVectorSpace for f32 {
#[inline]
fn norm(self) -> f32 {
ops::abs(self)
}
#[inline]
fn norm_squared(self) -> f32 {
self * self
}
}
/// A type with a natural interpolation that provides strong subdivision guarantees.
///
/// Although the only required method is `interpolate_stable`, many things are expected of it:
///
/// 1. The notion of interpolation should follow naturally from the semantics of the type, so
/// that inferring the interpolation mode from the type alone is sensible.
///
/// 2. The interpolation recovers something equivalent to the starting value at `t = 0.0`
/// and likewise with the ending value at `t = 1.0`. They do not have to be data-identical, but
/// they should be semantically identical. For example, [`Quat::slerp`] doesn't always yield its
/// second rotation input exactly at `t = 1.0`, but it always returns an equivalent rotation.
///
/// 3. Importantly, the interpolation must be *subdivision-stable*: for any interpolation curve
/// between two (unnamed) values and any parameter-value pairs `(t0, p)` and `(t1, q)`, the
/// interpolation curve between `p` and `q` must be the *linear* reparameterization of the original
/// interpolation curve restricted to the interval `[t0, t1]`.
///
/// The last of these conditions is very strong and indicates something like constant speed. It
/// is called "subdivision stability" because it guarantees that breaking up the interpolation
/// into segments and joining them back together has no effect.
///
/// Here is a diagram depicting it:
/// ```text
/// top curve = u.interpolate_stable(v, t)
///
/// t0 => p t1 => q
/// |-------------|---------|-------------|
/// 0 => u / \ 1 => v
/// / \
/// / \
/// / linear \
/// / reparameterization \
/// / t = t0 * (1 - s) + t1 * s \
/// / \
/// |-------------------------------------|
/// 0 => p 1 => q
///
/// bottom curve = p.interpolate_stable(q, s)
/// ```
///
/// Note that some common forms of interpolation do not satisfy this criterion. For example,
/// [`Quat::lerp`] and [`Rot2::nlerp`] are not subdivision-stable.
///
/// Furthermore, this is not to be used as a general trait for abstract interpolation.
/// Consumers rely on the strong guarantees in order for behavior based on this trait to be
/// well-behaved.
///
/// [`Quat::slerp`]: crate::Quat::slerp
/// [`Quat::lerp`]: crate::Quat::lerp
/// [`Rot2::nlerp`]: crate::Rot2::nlerp
pub trait StableInterpolate: Clone {
/// Interpolate between this value and the `other` given value using the parameter `t`. At
/// `t = 0.0`, a value equivalent to `self` is recovered, while `t = 1.0` recovers a value
/// equivalent to `other`, with intermediate values interpolating between the two.
/// See the [trait-level documentation] for details.
///
/// [trait-level documentation]: StableInterpolate
fn interpolate_stable(&self, other: &Self, t: f32) -> Self;
/// A version of [`interpolate_stable`] that assigns the result to `self` for convenience.
///
/// [`interpolate_stable`]: StableInterpolate::interpolate_stable
fn interpolate_stable_assign(&mut self, other: &Self, t: f32) {
*self = self.interpolate_stable(other, t);
}
/// Smoothly nudge this value towards the `target` at a given decay rate. The `decay_rate`
/// parameter controls how fast the distance between `self` and `target` decays relative to
/// the units of `delta`; the intended usage is for `decay_rate` to generally remain fixed,
/// while `delta` is something like `delta_time` from an updating system. This produces a
/// smooth following of the target that is independent of framerate.
///
/// More specifically, when this is called repeatedly, the result is that the distance between
/// `self` and a fixed `target` attenuates exponentially, with the rate of this exponential
/// decay given by `decay_rate`.
///
/// For example, at `decay_rate = 0.0`, this has no effect.
/// At `decay_rate = f32::INFINITY`, `self` immediately snaps to `target`.
/// In general, higher rates mean that `self` moves more quickly towards `target`.
///
/// # Example
/// ```
/// # use bevy_math::{Vec3, StableInterpolate};
/// # let delta_time: f32 = 1.0 / 60.0;
/// let mut object_position: Vec3 = Vec3::ZERO;
/// let target_position: Vec3 = Vec3::new(2.0, 3.0, 5.0);
/// // Decay rate of ln(10) => after 1 second, remaining distance is 1/10th
/// let decay_rate = f32::ln(10.0);
/// // Calling this repeatedly will move `object_position` towards `target_position`:
/// object_position.smooth_nudge(&target_position, decay_rate, delta_time);
/// ```
fn smooth_nudge(&mut self, target: &Self, decay_rate: f32, delta: f32) {
self.interpolate_stable_assign(target, 1.0 - ops::exp(-decay_rate * delta));
}
}
// Conservatively, we presently only apply this for normed vector spaces, where the notion
// of being constant-speed is literally true. The technical axioms are satisfied for any
// VectorSpace type, but the "natural from the semantics" part is less clear in general.
impl<V> StableInterpolate for V
where
V: NormedVectorSpace,
{
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.lerp(*other, t)
}
}
impl StableInterpolate for Rot2 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Quat {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir2 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir3 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir3A {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
macro_rules! impl_stable_interpolate_tuple {
($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => {
$(#[$meta])*
impl<$($T: StableInterpolate),*> StableInterpolate for ($($T,)*) {
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
(
$(
<$T as StableInterpolate>::interpolate_stable(&self.$n, &other.$n, t),
)*
)
}
}
};
}
all_tuples_enumerated!(
#[doc(fake_variadic)]
impl_stable_interpolate_tuple,
1,
11,
T
);
/// A type that has tangents.
pub trait HasTangent {
/// The tangent type.
type Tangent: VectorSpace;
}
/// A value with its derivative.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct WithDerivative<T>
where
T: HasTangent,
{
/// The underlying value.
pub value: T,
/// The derivative at `value`.
pub derivative: T::Tangent,
}
/// A value together with its first and second derivatives.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct WithTwoDerivatives<T>
where
T: HasTangent,
{
/// The underlying value.
pub value: T,
/// The derivative at `value`.
pub derivative: T::Tangent,
/// The second derivative at `value`.
pub second_derivative: <T::Tangent as HasTangent>::Tangent,
}
impl<V: VectorSpace> HasTangent for V {
type Tangent = V;
}
impl<M, N> HasTangent for (M, N)
where
M: HasTangent,
N: HasTangent,
{
type Tangent = Sum<M::Tangent, N::Tangent>;
}

585
vendor/bevy_math/src/compass.rs vendored Normal file
View File

@@ -0,0 +1,585 @@
use core::ops::Neg;
use crate::Dir2;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A compass enum with 4 directions.
/// ```text
/// N (North)
/// ▲
/// │
/// │
/// W (West) ┼─────► E (East)
/// │
/// │
/// ▼
/// S (South)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum CompassQuadrant {
/// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
North,
/// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
East,
/// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
South,
/// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
West,
}
impl CompassQuadrant {
/// Converts a standard index to a [`CompassQuadrant`].
///
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::North),
1 => Some(Self::East),
2 => Some(Self::South),
3 => Some(Self::West),
_ => None,
}
}
/// Converts a [`CompassQuadrant`] to a standard index.
///
/// Starts at 0 for [`CompassQuadrant::North`] and increments clockwise.
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::East => 1,
Self::South => 2,
Self::West => 3,
}
}
/// Returns the opposite [`CompassQuadrant`], located 180 degrees from `self`.
///
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
pub const fn opposite(&self) -> CompassQuadrant {
match self {
Self::North => Self::South,
Self::East => Self::West,
Self::South => Self::North,
Self::West => Self::East,
}
}
}
/// A compass enum with 8 directions.
/// ```text
/// N (North)
/// ▲
/// NW │ NE
/// ╲ │
/// W (West) ┼─────► E (East)
/// │ ╲
/// SW │ SE
/// ▼
/// S (South)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub enum CompassOctant {
/// Corresponds to [`Dir2::Y`] and [`Dir2::NORTH`]
North,
/// Corresponds to [`Dir2::NORTH_EAST`]
NorthEast,
/// Corresponds to [`Dir2::X`] and [`Dir2::EAST`]
East,
/// Corresponds to [`Dir2::SOUTH_EAST`]
SouthEast,
/// Corresponds to [`Dir2::NEG_X`] and [`Dir2::SOUTH`]
South,
/// Corresponds to [`Dir2::SOUTH_WEST`]
SouthWest,
/// Corresponds to [`Dir2::NEG_Y`] and [`Dir2::WEST`]
West,
/// Corresponds to [`Dir2::NORTH_WEST`]
NorthWest,
}
impl CompassOctant {
/// Converts a standard index to a [`CompassOctant`].
///
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
pub const fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::North),
1 => Some(Self::NorthEast),
2 => Some(Self::East),
3 => Some(Self::SouthEast),
4 => Some(Self::South),
5 => Some(Self::SouthWest),
6 => Some(Self::West),
7 => Some(Self::NorthWest),
_ => None,
}
}
/// Converts a [`CompassOctant`] to a standard index.
///
/// Starts at 0 for [`CompassOctant::North`] and increments clockwise.
pub const fn to_index(self) -> usize {
match self {
Self::North => 0,
Self::NorthEast => 1,
Self::East => 2,
Self::SouthEast => 3,
Self::South => 4,
Self::SouthWest => 5,
Self::West => 6,
Self::NorthWest => 7,
}
}
/// Returns the opposite [`CompassOctant`], located 180 degrees from `self`.
///
/// This can also be accessed via the `-` operator, using the [`Neg`] trait.
pub const fn opposite(&self) -> CompassOctant {
match self {
Self::North => Self::South,
Self::NorthEast => Self::SouthWest,
Self::East => Self::West,
Self::SouthEast => Self::NorthWest,
Self::South => Self::North,
Self::SouthWest => Self::NorthEast,
Self::West => Self::East,
Self::NorthWest => Self::SouthEast,
}
}
}
impl From<CompassQuadrant> for Dir2 {
fn from(q: CompassQuadrant) -> Self {
match q {
CompassQuadrant::North => Dir2::NORTH,
CompassQuadrant::East => Dir2::EAST,
CompassQuadrant::South => Dir2::SOUTH,
CompassQuadrant::West => Dir2::WEST,
}
}
}
impl From<Dir2> for CompassQuadrant {
/// Converts a [`Dir2`] to a [`CompassQuadrant`] in a lossy manner.
/// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
fn from(dir: Dir2) -> Self {
let angle = dir.to_angle().to_degrees();
match angle {
-135.0..=-45.0 => Self::South,
-45.0..=45.0 => Self::East,
45.0..=135.0 => Self::North,
135.0..=180.0 | -180.0..=-135.0 => Self::West,
_ => unreachable!(),
}
}
}
impl From<CompassOctant> for Dir2 {
fn from(o: CompassOctant) -> Self {
match o {
CompassOctant::North => Dir2::NORTH,
CompassOctant::NorthEast => Dir2::NORTH_EAST,
CompassOctant::East => Dir2::EAST,
CompassOctant::SouthEast => Dir2::SOUTH_EAST,
CompassOctant::South => Dir2::SOUTH,
CompassOctant::SouthWest => Dir2::SOUTH_WEST,
CompassOctant::West => Dir2::WEST,
CompassOctant::NorthWest => Dir2::NORTH_WEST,
}
}
}
impl From<Dir2> for CompassOctant {
/// Converts a [`Dir2`] to a [`CompassOctant`] in a lossy manner.
/// Converting back to a [`Dir2`] is not guaranteed to yield the same value.
fn from(dir: Dir2) -> Self {
let angle = dir.to_angle().to_degrees();
match angle {
-112.5..=-67.5 => Self::South,
-67.5..=-22.5 => Self::SouthEast,
-22.5..=22.5 => Self::East,
22.5..=67.5 => Self::NorthEast,
67.5..=112.5 => Self::North,
112.5..=157.5 => Self::NorthWest,
157.5..=180.0 | -180.0..=-157.5 => Self::West,
-157.5..=-112.5 => Self::SouthWest,
_ => unreachable!(),
}
}
}
impl Neg for CompassQuadrant {
type Output = CompassQuadrant;
fn neg(self) -> Self::Output {
self.opposite()
}
}
impl Neg for CompassOctant {
type Output = CompassOctant;
fn neg(self) -> Self::Output {
self.opposite()
}
}
#[cfg(test)]
mod test_compass_quadrant {
use crate::{CompassQuadrant, Dir2, Vec2};
#[test]
fn test_cardinal_directions() {
let tests = [
(
Dir2::new(Vec2::new(1.0, 0.0)).unwrap(),
CompassQuadrant::East,
),
(
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
CompassQuadrant::North,
),
(
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
CompassQuadrant::West,
),
(
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
CompassQuadrant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_north_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
CompassQuadrant::North,
),
(
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
CompassQuadrant::North,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.9, 0.1)).unwrap(),
CompassQuadrant::East,
),
(
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
CompassQuadrant::East,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_south_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
CompassQuadrant::South,
),
(
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
CompassQuadrant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn test_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
CompassQuadrant::West,
),
(
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
CompassQuadrant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassQuadrant::from(dir), expected);
}
}
#[test]
fn out_of_bounds_indexes_return_none() {
assert_eq!(CompassQuadrant::from_index(4), None);
assert_eq!(CompassQuadrant::from_index(5), None);
assert_eq!(CompassQuadrant::from_index(usize::MAX), None);
}
#[test]
fn compass_indexes_are_reversible() {
for i in 0..4 {
let quadrant = CompassQuadrant::from_index(i).unwrap();
assert_eq!(quadrant.to_index(), i);
}
}
#[test]
fn opposite_directions_reverse_themselves() {
for i in 0..4 {
let quadrant = CompassQuadrant::from_index(i).unwrap();
assert_eq!(-(-quadrant), quadrant);
}
}
}
#[cfg(test)]
mod test_compass_octant {
use crate::{CompassOctant, Dir2, Vec2};
#[test]
fn test_cardinal_directions() {
let tests = [
(
Dir2::new(Vec2::new(-0.5, 0.5)).unwrap(),
CompassOctant::NorthWest,
),
(
Dir2::new(Vec2::new(0.0, 1.0)).unwrap(),
CompassOctant::North,
),
(
Dir2::new(Vec2::new(0.5, 0.5)).unwrap(),
CompassOctant::NorthEast,
),
(Dir2::new(Vec2::new(1.0, 0.0)).unwrap(), CompassOctant::East),
(
Dir2::new(Vec2::new(0.5, -0.5)).unwrap(),
CompassOctant::SouthEast,
),
(
Dir2::new(Vec2::new(0.0, -1.0)).unwrap(),
CompassOctant::South,
),
(
Dir2::new(Vec2::new(-0.5, -0.5)).unwrap(),
CompassOctant::SouthWest,
),
(
Dir2::new(Vec2::new(-1.0, 0.0)).unwrap(),
CompassOctant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, 0.9)).unwrap(),
CompassOctant::North,
),
(
Dir2::new(Vec2::new(0.1, 0.9)).unwrap(),
CompassOctant::North,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.4, 0.6)).unwrap(),
CompassOctant::NorthEast,
),
(
Dir2::new(Vec2::new(0.6, 0.4)).unwrap(),
CompassOctant::NorthEast,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_east_pie_slice() {
let tests = [
(Dir2::new(Vec2::new(0.9, 0.1)).unwrap(), CompassOctant::East),
(
Dir2::new(Vec2::new(0.9, -0.1)).unwrap(),
CompassOctant::East,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_east_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(0.4, -0.6)).unwrap(),
CompassOctant::SouthEast,
),
(
Dir2::new(Vec2::new(0.6, -0.4)).unwrap(),
CompassOctant::SouthEast,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.1, -0.9)).unwrap(),
CompassOctant::South,
),
(
Dir2::new(Vec2::new(0.1, -0.9)).unwrap(),
CompassOctant::South,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_south_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.4, -0.6)).unwrap(),
CompassOctant::SouthWest,
),
(
Dir2::new(Vec2::new(-0.6, -0.4)).unwrap(),
CompassOctant::SouthWest,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.9, -0.1)).unwrap(),
CompassOctant::West,
),
(
Dir2::new(Vec2::new(-0.9, 0.1)).unwrap(),
CompassOctant::West,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn test_north_west_pie_slice() {
let tests = [
(
Dir2::new(Vec2::new(-0.4, 0.6)).unwrap(),
CompassOctant::NorthWest,
),
(
Dir2::new(Vec2::new(-0.6, 0.4)).unwrap(),
CompassOctant::NorthWest,
),
];
for (dir, expected) in tests {
assert_eq!(CompassOctant::from(dir), expected);
}
}
#[test]
fn out_of_bounds_indexes_return_none() {
assert_eq!(CompassOctant::from_index(8), None);
assert_eq!(CompassOctant::from_index(9), None);
assert_eq!(CompassOctant::from_index(usize::MAX), None);
}
#[test]
fn compass_indexes_are_reversible() {
for i in 0..8 {
let octant = CompassOctant::from_index(i).unwrap();
assert_eq!(octant.to_index(), i);
}
}
#[test]
fn opposite_directions_reverse_themselves() {
for i in 0..8 {
let octant = CompassOctant::from_index(i).unwrap();
assert_eq!(-(-octant), octant);
}
}
}

View File

@@ -0,0 +1,159 @@
use super::{CubicSegment, RationalSegment};
use crate::common_traits::{VectorSpace, WithDerivative, WithTwoDerivatives};
use crate::curve::{
derivatives::{SampleDerivative, SampleTwoDerivatives},
Curve, Interval,
};
#[cfg(feature = "alloc")]
use super::{CubicCurve, RationalCurve};
// -- CubicSegment
impl<P: VectorSpace> Curve<P> for CubicSegment<P> {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
impl<P: VectorSpace> SampleDerivative<P> for CubicSegment<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
impl<P: VectorSpace> SampleTwoDerivatives<P> for CubicSegment<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}
// -- CubicCurve
#[cfg(feature = "alloc")]
impl<P: VectorSpace> Curve<P> for CubicCurve<P> {
#[inline]
fn domain(&self) -> Interval {
// The non-emptiness invariant guarantees that this succeeds.
Interval::new(0.0, self.segments.len() as f32)
.expect("CubicCurve is invalid because it has no segments")
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleDerivative<P> for CubicCurve<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleTwoDerivatives<P> for CubicCurve<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}
// -- RationalSegment
impl<P: VectorSpace> Curve<P> for RationalSegment<P> {
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
impl<P: VectorSpace> SampleDerivative<P> for RationalSegment<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
impl<P: VectorSpace> SampleTwoDerivatives<P> for RationalSegment<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}
// -- RationalCurve
#[cfg(feature = "alloc")]
impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
#[inline]
fn domain(&self) -> Interval {
// The non-emptiness invariant guarantees the success of this.
Interval::new(0.0, self.length())
.expect("RationalCurve is invalid because it has zero length")
}
#[inline]
fn sample_unchecked(&self, t: f32) -> P {
self.position(t)
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleDerivative<P> for RationalCurve<P> {
#[inline]
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
WithDerivative {
value: self.position(t),
derivative: self.velocity(t),
}
}
}
#[cfg(feature = "alloc")]
impl<P: VectorSpace> SampleTwoDerivatives<P> for RationalCurve<P> {
#[inline]
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
WithTwoDerivatives {
value: self.position(t),
derivative: self.velocity(t),
second_derivative: self.acceleration(t),
}
}
}

1845
vendor/bevy_math/src/cubic_splines/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

815
vendor/bevy_math/src/curve/adaptors.rs vendored Normal file
View File

@@ -0,0 +1,815 @@
//! Adaptors used by the Curve API for transforming and combining curves together.
use super::interval::*;
use super::Curve;
use crate::ops;
use crate::VectorSpace;
use core::any::type_name;
use core::fmt::{self, Debug};
use core::marker::PhantomData;
#[cfg(feature = "bevy_reflect")]
use {
alloc::format,
bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath},
};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
#[expect(unused, reason = "imported just for doc links")]
use super::CurveExt;
// NOTE ON REFLECTION:
//
// Function members of structs pose an obstacle for reflection, because they don't implement
// reflection traits themselves. Some of these are more problematic than others; for example,
// `FromReflect` is basically hopeless for function members regardless, so function-containing
// adaptors will just never be `FromReflect` (at least until function item types implement
// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
//
// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
// - are currently never `FromReflect`;
// - have custom `TypePath` implementations which are not fully stable;
// - have custom `Debug` implementations which display the function only by type name.
/// A curve with a constant value over its domain.
///
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ConstantCurve<T> {
pub(crate) domain: Interval,
pub(crate) value: T,
}
impl<T> ConstantCurve<T>
where
T: Clone,
{
/// Create a constant curve, which has the given `domain` and always produces the given `value`
/// when sampled.
pub fn new(domain: Interval, value: T) -> Self {
Self { domain, value }
}
}
impl<T> Curve<T> for ConstantCurve<T>
where
T: Clone,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, _t: f32) -> T {
self.value.clone()
}
}
/// A curve defined by a function together with a fixed domain.
///
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct FunctionCurve<T, F> {
pub(crate) domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, F> Debug for FunctionCurve<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunctionCurve")
.field("domain", &self.domain)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, F> TypePath for FunctionCurve<T, F>
where
T: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::FunctionCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"FunctionCurve<{},{}>",
T::short_type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("FunctionCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, F> FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
/// `function` is evaluated at the sample time to compute the output.
pub fn new(domain: Interval, function: F) -> Self {
FunctionCurve {
domain,
f: function,
_phantom: PhantomData,
}
}
}
impl<T, F> Curve<T> for FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(t)
}
}
/// A curve whose samples are defined by mapping samples from another curve through a
/// given function. Curves of this type are produced by [`CurveExt::map`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where S: TypePath, T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct MapCurve<S, T, C, F> {
pub(crate) preimage: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<(fn() -> S, fn(S) -> T)>,
}
impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapCurve")
.field("preimage", &self.preimage)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
where
S: TypePath,
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::MapCurve<{},{},{},{}>",
paths::THIS_MODULE,
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"MapCurve<{},{},{},{}>",
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("MapCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
where
C: Curve<S>,
F: Fn(S) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.preimage.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(self.preimage.sample_unchecked(t))
}
}
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
/// Curves of this type are produced by [`CurveExt::reparametrize`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct ReparamCurve<T, C, F> {
pub(crate) domain: Interval,
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, F> Debug for ReparamCurve<T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReparamCurve")
.field("domain", &self.domain)
.field("base", &self.base)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, C, F> TypePath for ReparamCurve<T, C, F>
where
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::ReparamCurve<{},{},{}>",
paths::THIS_MODULE,
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"ReparamCurve<{},{},{}>",
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("ReparamCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
where
C: Curve<T>,
F: Fn(f32) -> f32,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.base.sample_unchecked((self.f)(t))
}
}
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
/// Curves of this type are produced by [`CurveExt::reparametrize_linear`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct LinearReparamCurve<T, C> {
/// Invariants: The domain of this curve must always be bounded.
pub(crate) base: C,
/// Invariants: This interval must always be bounded.
pub(crate) new_domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.new_domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// The invariants imply this unwrap always succeeds.
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
self.base.sample_unchecked(f(t))
}
}
/// A curve that has been reparametrized by another curve, using that curve to transform the
/// sample times before sampling. Curves of this type are produced by [`CurveExt::reparametrize_by_curve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct CurveReparamCurve<T, C, D> {
pub(crate) base: C,
pub(crate) reparam_curve: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
where
C: Curve<T>,
D: Curve<f32>,
{
#[inline]
fn domain(&self) -> Interval {
self.reparam_curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let sample_time = self.reparam_curve.sample_unchecked(t);
self.base.sample_unchecked(sample_time)
}
}
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
/// produced by [`CurveExt::graph`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct GraphCurve<T, C> {
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.base.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (f32, T) {
(t, self.base.sample_unchecked(t))
}
}
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
/// of this type are produced by [`CurveExt::zip`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ZipCurve<S, T, C, D> {
pub(crate) domain: Interval,
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> (S, T)>,
}
impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
where
C: Curve<S>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (S, T) {
(
self.first.sample_unchecked(t),
self.second.sample_unchecked(t),
)
}
}
/// The curve that results from chaining one curve with another. The second curve is
/// effectively reparametrized so that its start is at the end of the first.
///
/// For this to be well-formed, the first curve's domain must be right-finite and the second's
/// must be left-finite.
///
/// Curves of this type are produced by [`CurveExt::chain`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ChainCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
where
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `first` has a valid Interval as its domain and the
// length of `second` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_unchecked(t)
}
}
}
/// The curve that results from reversing another.
///
/// Curves of this type are produced by [`CurveExt::reverse`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ReverseCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ReverseCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.curve
.sample_unchecked(self.domain().end() - (t - self.domain().start()))
}
}
/// The curve that results from repeating a curve `N` times.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::repeat`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct RepeatCurve<T, C> {
pub(crate) domain: Interval,
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let t = self.base_curve_sample_time(t);
self.curve.sample_unchecked(t)
}
}
impl<T, C> RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
// the domain is bounded by construction
let d = self.curve.domain();
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from repeating a curve forever.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::forever`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ForeverCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
Interval::EVERYWHERE
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let t = self.base_curve_sample_time(t);
self.curve.sample_unchecked(t)
}
}
impl<T, C> ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
// the domain is bounded by construction
let d = self.curve.domain();
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from chaining a curve with its reversed version. The transition point
/// is guaranteed to make no jump.
///
/// Curves of this type are produced by [`CurveExt::ping_pong`].
///
/// # Domain
///
/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct PingPongCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for PingPongCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.curve.domain().start(),
self.curve.domain().end() + self.curve.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// the domain is bounded by construction
let final_t = if t > self.curve.domain().end() {
self.curve.domain().end() * 2.0 - t
} else {
t
};
self.curve.sample_unchecked(final_t)
}
}
/// The curve that results from chaining two curves.
///
/// Additionally the transition of the samples is guaranteed to not make sudden jumps. This is
/// useful if you really just know about the shapes of your curves and don't want to deal with
/// stitching them together properly when it would just introduce useless complexity. It is
/// realized by translating the second curve so that its start sample point coincides with the
/// first curves' end sample point.
///
/// Curves of this type are produced by [`CurveExt::chain_continue`].
///
/// # Domain
///
/// The first curve's domain must be right-finite and the second's must be left-finite to get a
/// valid [`ContinuationCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ContinuationCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
// cache the offset in the curve directly to prevent triple sampling for every sample we make
pub(crate) offset: T,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
) + self.offset
} else {
self.first.sample_unchecked(t)
}
}
}

826
vendor/bevy_math/src/curve/cores.rs vendored Normal file
View File

@@ -0,0 +1,826 @@
//! Core data structures to be used internally in Curve implementations, encapsulating storage
//! and access patterns for reuse.
//!
//! The `Core` types here expose their fields publicly so that it is easier to manipulate and
//! extend them, but in doing so, you must maintain the invariants of those fields yourself. The
//! provided methods all maintain the invariants, so this is only a concern if you manually mutate
//! the fields.
use crate::ops;
use super::interval::Interval;
use core::fmt::Debug;
use thiserror::Error;
#[cfg(feature = "alloc")]
use {alloc::vec::Vec, itertools::Itertools};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// This type expresses the relationship of a value to a fixed collection of values. It is a kind
/// of summary used intermediately by sampling operations.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum InterpolationDatum<T> {
/// This value lies exactly on a value in the family.
Exact(T),
/// This value is off the left tail of the family; the inner value is the family's leftmost.
LeftTail(T),
/// This value is off the right tail of the family; the inner value is the family's rightmost.
RightTail(T),
/// This value lies on the interior, in between two points, with a third parameter expressing
/// the interpolation factor between the two.
Between(T, T, f32),
}
impl<T> InterpolationDatum<T> {
/// Map all values using a given function `f`, leaving the interpolation parameters in any
/// [`Between`] variants unchanged.
///
/// [`Between`]: `InterpolationDatum::Between`
#[must_use]
pub fn map<S>(self, f: impl Fn(T) -> S) -> InterpolationDatum<S> {
match self {
InterpolationDatum::Exact(v) => InterpolationDatum::Exact(f(v)),
InterpolationDatum::LeftTail(v) => InterpolationDatum::LeftTail(f(v)),
InterpolationDatum::RightTail(v) => InterpolationDatum::RightTail(f(v)),
InterpolationDatum::Between(u, v, s) => InterpolationDatum::Between(f(u), f(v), s),
}
}
}
/// The data core of a curve derived from evenly-spaced samples. The intention is to use this
/// in addition to explicit or inferred interpolation information in user-space in order to
/// implement curves using [`domain`] and [`sample_with`].
///
/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
/// enforces the required invariants, and the methods maintain those invariants.
///
/// [the provided constructor]: EvenCore::new
/// [`domain`]: EvenCore::domain
/// [`sample_with`]: EvenCore::sample_with
///
/// # Example
/// ```rust
/// # use bevy_math::curve::*;
/// # use bevy_math::curve::cores::*;
/// // Let's make a curve that interpolates evenly spaced samples using either linear interpolation
/// // or step "interpolation" — i.e. just using the most recent sample as the source of truth.
/// enum InterpolationMode {
/// Linear,
/// Step,
/// }
///
/// // Linear interpolation mode is driven by a trait.
/// trait LinearInterpolate {
/// fn lerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// // Step interpolation just uses an explicit function.
/// fn step<T: Clone>(first: &T, second: &T, t: f32) -> T {
/// if t >= 1.0 {
/// second.clone()
/// } else {
/// first.clone()
/// }
/// }
///
/// // Omitted: Implementing `LinearInterpolate` on relevant types; e.g. `f32`, `Vec3`, and so on.
///
/// // The curve itself uses `EvenCore` to hold the evenly-spaced samples, and the `sample_with`
/// // function will do all the work of interpolating once given a function to do it with.
/// struct MyCurve<T> {
/// core: EvenCore<T>,
/// interpolation_mode: InterpolationMode,
/// }
///
/// impl<T> Curve<T> for MyCurve<T>
/// where
/// T: LinearInterpolate + Clone,
/// {
/// fn domain(&self) -> Interval {
/// self.core.domain()
/// }
///
/// fn sample_unchecked(&self, t: f32) -> T {
/// // To sample this curve, check the interpolation mode and dispatch accordingly.
/// match self.interpolation_mode {
/// InterpolationMode::Linear => self.core.sample_with(t, <T as LinearInterpolate>::lerp),
/// InterpolationMode::Step => self.core.sample_with(t, step),
/// }
/// }
/// }
/// ```
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct EvenCore<T> {
/// The domain over which the samples are taken, which corresponds to the domain of the curve
/// formed by interpolating them.
///
/// # Invariants
/// This must always be a bounded interval; i.e. its endpoints must be finite.
pub domain: Interval,
/// The samples that are interpolated to extract values.
///
/// # Invariants
/// This must always have a length of at least 2.
pub samples: Vec<T>,
}
/// An error indicating that an [`EvenCore`] could not be constructed.
#[derive(Debug, Error)]
#[error("Could not construct an EvenCore")]
pub enum EvenCoreError {
/// Not enough samples were provided.
#[error("Need at least two samples to create an EvenCore, but {samples} were provided")]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
/// Unbounded domains are not compatible with `EvenCore`.
#[error("Cannot create an EvenCore over an unbounded domain")]
UnboundedDomain,
}
#[cfg(feature = "alloc")]
impl<T> EvenCore<T> {
/// Create a new [`EvenCore`] from the specified `domain` and `samples`. The samples are
/// regarded to be evenly spaced within the given domain interval, so that the outermost
/// samples form the boundary of that interval. An error is returned if there are not at
/// least 2 samples or if the given domain is unbounded.
#[inline]
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
) -> Result<Self, EvenCoreError> {
let samples: Vec<T> = samples.into_iter().collect();
if samples.len() < 2 {
return Err(EvenCoreError::NotEnoughSamples {
samples: samples.len(),
});
}
if !domain.is_bounded() {
return Err(EvenCoreError::UnboundedDomain);
}
Ok(EvenCore { domain, samples })
}
/// The domain of the curve derived from this core.
#[inline]
pub const fn domain(&self) -> Interval {
self.domain
}
/// Obtain a value from the held samples using the given `interpolation` to interpolate
/// between adjacent samples.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
#[inline]
pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
match even_interp(self.domain, self.samples.len(), t) {
InterpolationDatum::Exact(idx)
| InterpolationDatum::LeftTail(idx)
| InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
InterpolationDatum::Between(lower_idx, upper_idx, s) => {
interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
}
}
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
even_interp(self.domain, self.samples.len(), t).map(|idx| &self.samples[idx])
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: EvenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
let segment_len = self.domain.length() / (self.samples.len() - 1) as f32;
even_interp(self.domain, self.samples.len(), t).map(|idx| {
(
self.domain.start() + segment_len * idx as f32,
&self.samples[idx],
)
})
}
}
/// Given a domain and a number of samples taken over that interval, return an [`InterpolationDatum`]
/// that governs how samples are extracted relative to the stored data.
///
/// `domain` must be a bounded interval (i.e. `domain.is_bounded() == true`).
///
/// `samples` must be at least 2.
///
/// This function will never panic, but it may return invalid indices if its assumptions are violated.
pub fn even_interp(domain: Interval, samples: usize, t: f32) -> InterpolationDatum<usize> {
let subdivs = samples - 1;
let step = domain.length() / subdivs as f32;
let t_shifted = t - domain.start();
let steps_taken = t_shifted / step;
if steps_taken <= 0.0 {
// To the left side of all the samples.
InterpolationDatum::LeftTail(0)
} else if steps_taken >= subdivs as f32 {
// To the right side of all the samples
InterpolationDatum::RightTail(samples - 1)
} else {
let lower_index = ops::floor(steps_taken) as usize;
// This upper index is always valid because `steps_taken` is a finite value
// strictly less than `samples - 1`, so its floor is at most `samples - 2`
let upper_index = lower_index + 1;
let s = ops::fract(steps_taken);
InterpolationDatum::Between(lower_index, upper_index, s)
}
}
/// The data core of a curve defined by unevenly-spaced samples or keyframes. The intention is to
/// use this in concert with implicitly or explicitly-defined interpolation in user-space in
/// order to implement the curve interface using [`domain`] and [`sample_with`].
///
/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
/// enforces the required invariants, and the methods maintain those invariants.
///
/// # Example
/// ```rust
/// # use bevy_math::curve::*;
/// # use bevy_math::curve::cores::*;
/// // Let's make a curve formed by interpolating rotations.
/// // We'll support two common modes of interpolation:
/// // - Normalized linear: First do linear interpolation, then normalize to get a valid rotation.
/// // - Spherical linear: Interpolate through valid rotations with constant angular velocity.
/// enum InterpolationMode {
/// NormalizedLinear,
/// SphericalLinear,
/// }
///
/// // Our interpolation modes will be driven by traits.
/// trait NormalizedLinearInterpolate {
/// fn nlerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// trait SphericalLinearInterpolate {
/// fn slerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// // Omitted: These traits would be implemented for `Rot2`, `Quat`, and other rotation representations.
///
/// // The curve itself just needs to use the curve core for keyframes, `UnevenCore`, which handles
/// // everything except for the explicit interpolation used.
/// struct RotationCurve<T> {
/// core: UnevenCore<T>,
/// interpolation_mode: InterpolationMode,
/// }
///
/// impl<T> Curve<T> for RotationCurve<T>
/// where
/// T: NormalizedLinearInterpolate + SphericalLinearInterpolate + Clone,
/// {
/// fn domain(&self) -> Interval {
/// self.core.domain()
/// }
///
/// fn sample_unchecked(&self, t: f32) -> T {
/// // To sample the curve, we just look at the interpolation mode and
/// // dispatch accordingly.
/// match self.interpolation_mode {
/// InterpolationMode::NormalizedLinear =>
/// self.core.sample_with(t, <T as NormalizedLinearInterpolate>::nlerp),
/// InterpolationMode::SphericalLinear =>
/// self.core.sample_with(t, <T as SphericalLinearInterpolate>::slerp),
/// }
/// }
/// }
/// ```
///
/// [`domain`]: UnevenCore::domain
/// [`sample_with`]: UnevenCore::sample_with
/// [the provided constructor]: UnevenCore::new
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct UnevenCore<T> {
/// The times for the samples of this curve.
///
/// # Invariants
/// This must always have a length of at least 2, be sorted, and have no
/// duplicated or non-finite times.
pub times: Vec<f32>,
/// The samples corresponding to the times for this curve.
///
/// # Invariants
/// This must always have the same length as `times`.
pub samples: Vec<T>,
}
/// An error indicating that an [`UnevenCore`] could not be constructed.
#[derive(Debug, Error)]
#[error("Could not construct an UnevenCore")]
pub enum UnevenCoreError {
/// Not enough samples were provided.
#[error(
"Need at least two unique samples to create an UnevenCore, but {samples} were provided"
)]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
}
#[cfg(feature = "alloc")]
impl<T> UnevenCore<T> {
/// Create a new [`UnevenCore`]. The given samples are filtered to finite times and
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
/// returned.
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
// Filter out non-finite sample times first so they don't interfere with sorting/deduplication.
let mut timed_samples = timed_samples
.into_iter()
.filter(|(t, _)| t.is_finite())
.collect_vec();
timed_samples
// Using `total_cmp` is fine because no NANs remain and because deduplication uses
// `PartialEq` anyway (so -0.0 and 0.0 will be considered equal later regardless).
.sort_by(|(t0, _), (t1, _)| t0.total_cmp(t1));
timed_samples.dedup_by_key(|(t, _)| *t);
if timed_samples.len() < 2 {
return Err(UnevenCoreError::NotEnoughSamples {
samples: timed_samples.len(),
});
}
let (times, samples): (Vec<f32>, Vec<T>) = timed_samples.into_iter().unzip();
Ok(UnevenCore { times, samples })
}
/// The domain of the curve derived from this core.
///
/// # Panics
/// This method may panic if the type's invariants aren't satisfied.
#[inline]
pub fn domain(&self) -> Interval {
let start = self.times.first().unwrap();
let end = self.times.last().unwrap();
Interval::new(*start, *end).unwrap()
}
/// Obtain a value from the held samples using the given `interpolation` to interpolate
/// between adjacent samples.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
#[inline]
pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
match uneven_interp(&self.times, t) {
InterpolationDatum::Exact(idx)
| InterpolationDatum::LeftTail(idx)
| InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
InterpolationDatum::Between(lower_idx, upper_idx, s) => {
interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
}
}
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
uneven_interp(&self.times, t).map(|idx| &self.samples[idx])
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: UnevenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
uneven_interp(&self.times, t).map(|idx| (self.times[idx], &self.samples[idx]))
}
/// This core, but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the set of sample times, otherwise
/// data will be deleted.
///
/// [`CurveExt::reparametrize`]: crate::curve::CurveExt::reparametrize
#[must_use]
pub fn map_sample_times(mut self, f: impl Fn(f32) -> f32) -> UnevenCore<T> {
let mut timed_samples = self
.times
.into_iter()
.map(f)
.zip(self.samples)
.collect_vec();
timed_samples.sort_by(|(t1, _), (t2, _)| t1.total_cmp(t2));
timed_samples.dedup_by_key(|(t, _)| *t);
(self.times, self.samples) = timed_samples.into_iter().unzip();
self
}
}
/// The data core of a curve using uneven samples (i.e. keyframes), where each sample time
/// yields some fixed number of values — the [sampling width]. This may serve as storage for
/// curves that yield vectors or iterators, and in some cases, it may be useful for cache locality
/// if the sample type can effectively be encoded as a fixed-length slice of values.
///
/// [sampling width]: ChunkedUnevenCore::width
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ChunkedUnevenCore<T> {
/// The times, one for each sample.
///
/// # Invariants
/// This must always have a length of at least 2, be sorted, and have no duplicated or
/// non-finite times.
pub times: Vec<f32>,
/// The values that are used in sampling. Each width-worth of these correspond to a single sample.
///
/// # Invariants
/// The length of this vector must always be some fixed integer multiple of that of `times`.
pub values: Vec<T>,
}
/// An error that indicates that a [`ChunkedUnevenCore`] could not be formed.
#[derive(Debug, Error)]
#[error("Could not create a ChunkedUnevenCore")]
pub enum ChunkedUnevenCoreError {
/// The width of a `ChunkedUnevenCore` cannot be zero.
#[error("Chunk width must be at least 1")]
ZeroWidth,
/// At least two sample times are necessary to interpolate in `ChunkedUnevenCore`.
#[error(
"Need at least two unique samples to create a ChunkedUnevenCore, but {samples} were provided"
)]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
/// The length of the value buffer is supposed to be the `width` times the number of samples.
#[error("Expected {expected} total values based on width, but {actual} were provided")]
MismatchedLengths {
/// The expected length of the value buffer.
expected: usize,
/// The actual length of the value buffer.
actual: usize,
},
/// Tried to infer the width, but the ratio of lengths wasn't an integer, so no such length exists.
#[error("The length of the list of values ({values_len}) was not divisible by that of the list of times ({times_len})")]
NonDivisibleLengths {
/// The length of the value buffer.
values_len: usize,
/// The length of the time buffer.
times_len: usize,
},
}
#[cfg(feature = "alloc")]
impl<T> ChunkedUnevenCore<T> {
/// Create a new [`ChunkedUnevenCore`]. The given `times` are sorted, filtered to finite times,
/// and deduplicated. See the [type-level documentation] for more information about this type.
///
/// Produces an error in any of the following circumstances:
/// - `width` is zero.
/// - `times` has less than `2` unique valid entries.
/// - `values` has the incorrect length relative to `times`.
///
/// [type-level documentation]: ChunkedUnevenCore
pub fn new(
times: impl IntoIterator<Item = f32>,
values: impl IntoIterator<Item = T>,
width: usize,
) -> Result<Self, ChunkedUnevenCoreError> {
let times = times.into_iter().collect_vec();
let values = values.into_iter().collect_vec();
if width == 0 {
return Err(ChunkedUnevenCoreError::ZeroWidth);
}
let times = filter_sort_dedup_times(times);
if times.len() < 2 {
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
samples: times.len(),
});
}
if values.len() != times.len() * width {
return Err(ChunkedUnevenCoreError::MismatchedLengths {
expected: times.len() * width,
actual: values.len(),
});
}
Ok(Self { times, values })
}
/// Create a new [`ChunkedUnevenCore`], inferring the width from the sizes of the inputs.
/// The given `times` are sorted, filtered to finite times, and deduplicated. See the
/// [type-level documentation] for more information about this type. Prefer using [`new`]
/// if possible, since that constructor has richer error checking.
///
/// Produces an error in any of the following circumstances:
/// - `values` has length zero.
/// - `times` has less than `2` unique valid entries.
/// - The length of `values` is not divisible by that of `times` (once sorted, filtered,
/// and deduplicated).
///
/// The [width] is implicitly taken to be the length of `values` divided by that of `times`
/// (once sorted, filtered, and deduplicated).
///
/// [type-level documentation]: ChunkedUnevenCore
/// [`new`]: ChunkedUnevenCore::new
/// [width]: ChunkedUnevenCore::width
pub fn new_width_inferred(
times: impl IntoIterator<Item = f32>,
values: impl IntoIterator<Item = T>,
) -> Result<Self, ChunkedUnevenCoreError> {
let times = times.into_iter().collect_vec();
let values = values.into_iter().collect_vec();
let times = filter_sort_dedup_times(times);
if times.len() < 2 {
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
samples: times.len(),
});
}
if values.len() % times.len() != 0 {
return Err(ChunkedUnevenCoreError::NonDivisibleLengths {
values_len: values.len(),
times_len: times.len(),
});
}
if values.is_empty() {
return Err(ChunkedUnevenCoreError::ZeroWidth);
}
Ok(Self { times, values })
}
/// The domain of the curve derived from this core.
///
/// # Panics
/// This may panic if this type's invariants aren't met.
#[inline]
pub fn domain(&self) -> Interval {
let start = self.times.first().unwrap();
let end = self.times.last().unwrap();
Interval::new(*start, *end).unwrap()
}
/// The sample width: the number of values that are contained in each sample.
#[inline]
pub fn width(&self) -> usize {
self.values.len() / self.times.len()
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
#[inline]
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&[T]> {
uneven_interp(&self.times, t).map(|idx| self.time_index_to_slice(idx))
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: ChunkedUnevenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &[T])> {
uneven_interp(&self.times, t).map(|idx| (self.times[idx], self.time_index_to_slice(idx)))
}
/// Given an index in [times], returns the slice of [values] that correspond to the sample at
/// that time.
///
/// [times]: ChunkedUnevenCore::times
/// [values]: ChunkedUnevenCore::values
#[inline]
fn time_index_to_slice(&self, idx: usize) -> &[T] {
let width = self.width();
let lower_idx = width * idx;
let upper_idx = lower_idx + width;
&self.values[lower_idx..upper_idx]
}
}
/// Sort the given times, deduplicate them, and filter them to only finite times.
#[cfg(feature = "alloc")]
fn filter_sort_dedup_times(times: impl IntoIterator<Item = f32>) -> Vec<f32> {
// Filter before sorting/deduplication so that NAN doesn't interfere with them.
let mut times = times.into_iter().filter(|t| t.is_finite()).collect_vec();
times.sort_by(f32::total_cmp);
times.dedup();
times
}
/// Given a list of `times` and a target value, get the interpolation relationship for the
/// target value in terms of the indices of the starting list. In a sense, this encapsulates the
/// heart of uneven/keyframe sampling.
///
/// `times` is assumed to be sorted, deduplicated, and consisting only of finite values. It is also
/// assumed to contain at least two values.
///
/// # Panics
/// This function will panic if `times` contains NAN.
pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum<usize> {
match times.binary_search_by(|pt| pt.partial_cmp(&t).unwrap()) {
Ok(index) => InterpolationDatum::Exact(index),
Err(index) => {
if index == 0 {
// This is before the first keyframe.
InterpolationDatum::LeftTail(0)
} else if index >= times.len() {
// This is after the last keyframe.
InterpolationDatum::RightTail(times.len() - 1)
} else {
// This is actually in the middle somewhere.
let t_lower = times[index - 1];
let t_upper = times[index];
let s = (t - t_lower) / (t_upper - t_lower);
InterpolationDatum::Between(index - 1, index, s)
}
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::{ChunkedUnevenCore, EvenCore, UnevenCore};
use crate::curve::{cores::InterpolationDatum, interval};
use alloc::vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
fn approx_between<T>(datum: InterpolationDatum<T>, start: T, end: T, p: f32) -> bool
where
T: PartialEq,
{
if let InterpolationDatum::Between(m_start, m_end, m_p) = datum {
m_start == start && m_end == end && m_p.abs_diff_eq(&p, 1e-6)
} else {
false
}
}
fn is_left_tail<T>(datum: InterpolationDatum<T>) -> bool {
matches!(datum, InterpolationDatum::LeftTail(_))
}
fn is_right_tail<T>(datum: InterpolationDatum<T>) -> bool {
matches!(datum, InterpolationDatum::RightTail(_))
}
fn is_exact<T>(datum: InterpolationDatum<T>, target: T) -> bool
where
T: PartialEq,
{
if let InterpolationDatum::Exact(v) = datum {
v == target
} else {
false
}
}
#[test]
fn even_sample_interp() {
let even_core = EvenCore::<f32>::new(
interval(0.0, 1.0).unwrap(),
// 11 entries -> 10 segments
vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
)
.expect("Failed to construct test core");
let datum = even_core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = even_core.sample_interp(0.0);
assert!(is_left_tail(datum));
let datum = even_core.sample_interp(1.0);
assert!(is_right_tail(datum));
let datum = even_core.sample_interp(2.0);
assert!(is_right_tail(datum));
let datum = even_core.sample_interp(0.05);
let InterpolationDatum::Between(0.0, 1.0, p) = datum else {
panic!("Sample did not lie in the correct subinterval")
};
assert_abs_diff_eq!(p, 0.5);
let datum = even_core.sample_interp(0.05);
assert!(approx_between(datum, &0.0, &1.0, 0.5));
let datum = even_core.sample_interp(0.33);
assert!(approx_between(datum, &3.0, &4.0, 0.3));
let datum = even_core.sample_interp(0.78);
assert!(approx_between(datum, &7.0, &8.0, 0.8));
let datum = even_core.sample_interp(0.5);
assert!(approx_between(datum, &4.0, &5.0, 1.0) || approx_between(datum, &5.0, &6.0, 0.0));
let datum = even_core.sample_interp(0.7);
assert!(approx_between(datum, &6.0, &7.0, 1.0) || approx_between(datum, &7.0, &8.0, 0.0));
}
#[test]
fn uneven_sample_interp() {
let uneven_core = UnevenCore::<f32>::new(vec![
(0.0, 0.0),
(1.0, 3.0),
(2.0, 9.0),
(4.0, 10.0),
(8.0, -5.0),
])
.expect("Failed to construct test core");
let datum = uneven_core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = uneven_core.sample_interp(0.0);
assert!(is_exact(datum, &0.0));
let datum = uneven_core.sample_interp(8.0);
assert!(is_exact(datum, &(-5.0)));
let datum = uneven_core.sample_interp(9.0);
assert!(is_right_tail(datum));
let datum = uneven_core.sample_interp(0.5);
assert!(approx_between(datum, &0.0, &3.0, 0.5));
let datum = uneven_core.sample_interp(2.5);
assert!(approx_between(datum, &9.0, &10.0, 0.25));
let datum = uneven_core.sample_interp(7.0);
assert!(approx_between(datum, &10.0, &(-5.0), 0.75));
let datum = uneven_core.sample_interp(2.0);
assert!(is_exact(datum, &9.0));
let datum = uneven_core.sample_interp(4.0);
assert!(is_exact(datum, &10.0));
}
#[test]
fn chunked_uneven_sample_interp() {
let core =
ChunkedUnevenCore::new(vec![0.0, 2.0, 8.0], vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2)
.expect("Failed to construct test core");
let datum = core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = core.sample_interp(0.0);
assert!(is_exact(datum, &[0.0, 1.0]));
let datum = core.sample_interp(8.0);
assert!(is_exact(datum, &[4.0, 5.0]));
let datum = core.sample_interp(10.0);
assert!(is_right_tail(datum));
let datum = core.sample_interp(1.0);
assert!(approx_between(datum, &[0.0, 1.0], &[2.0, 3.0], 0.5));
let datum = core.sample_interp(3.0);
assert!(approx_between(datum, &[2.0, 3.0], &[4.0, 5.0], 1.0 / 6.0));
let datum = core.sample_interp(2.0);
assert!(is_exact(datum, &[2.0, 3.0]));
}
}

View File

@@ -0,0 +1,648 @@
//! Implementations of derivatives on curve adaptors. These allow
//! compositionality for derivatives.
use super::{SampleDerivative, SampleTwoDerivatives};
use crate::common_traits::{HasTangent, Sum, VectorSpace, WithDerivative, WithTwoDerivatives};
use crate::curve::{
adaptors::{
ChainCurve, ConstantCurve, ContinuationCurve, CurveReparamCurve, ForeverCurve, GraphCurve,
LinearReparamCurve, PingPongCurve, RepeatCurve, ReverseCurve, ZipCurve,
},
Curve,
};
// -- ConstantCurve
impl<T> SampleDerivative<T> for ConstantCurve<T>
where
T: HasTangent + Clone,
{
fn sample_with_derivative_unchecked(&self, _t: f32) -> WithDerivative<T> {
WithDerivative {
value: self.value.clone(),
derivative: VectorSpace::ZERO,
}
}
}
impl<T> SampleTwoDerivatives<T> for ConstantCurve<T>
where
T: HasTangent + Clone,
{
fn sample_with_two_derivatives_unchecked(&self, _t: f32) -> WithTwoDerivatives<T> {
WithTwoDerivatives {
value: self.value.clone(),
derivative: VectorSpace::ZERO,
second_derivative: VectorSpace::ZERO,
}
}
}
// -- ChainCurve
impl<T, C, D> SampleDerivative<T> for ChainCurve<T, C, D>
where
T: HasTangent,
C: SampleDerivative<T>,
D: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
if t > self.first.domain().end() {
self.second.sample_with_derivative_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_with_derivative_unchecked(t)
}
}
}
impl<T, C, D> SampleTwoDerivatives<T> for ChainCurve<T, C, D>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
D: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
if t > self.first.domain().end() {
self.second.sample_with_two_derivatives_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
)
} else {
self.first.sample_with_two_derivatives_unchecked(t)
}
}
}
// -- ContinuationCurve
impl<T, C, D> SampleDerivative<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: SampleDerivative<T>,
D: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
if t > self.first.domain().end() {
let mut output = self.second.sample_with_derivative_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
);
output.value = output.value + self.offset;
output
} else {
self.first.sample_with_derivative_unchecked(t)
}
}
}
impl<T, C, D> SampleTwoDerivatives<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: SampleTwoDerivatives<T>,
D: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
if t > self.first.domain().end() {
let mut output = self.second.sample_with_two_derivatives_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
);
output.value = output.value + self.offset;
output
} else {
self.first.sample_with_two_derivatives_unchecked(t)
}
}
}
// -- RepeatCurve
impl<T, C> SampleDerivative<T> for RepeatCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_derivative_unchecked(t)
}
}
impl<T, C> SampleTwoDerivatives<T> for RepeatCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_two_derivatives_unchecked(t)
}
}
// -- ForeverCurve
impl<T, C> SampleDerivative<T> for ForeverCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_derivative_unchecked(t)
}
}
impl<T, C> SampleTwoDerivatives<T> for ForeverCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
let t = self.base_curve_sample_time(t);
self.curve.sample_with_two_derivatives_unchecked(t)
}
}
// -- PingPongCurve
impl<T, C> SampleDerivative<T> for PingPongCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
if t > self.curve.domain().end() {
let t = self.curve.domain().end() * 2.0 - t;
// The derivative of the preceding expression is -1, so the chain
// rule implies the derivative should be negated.
let mut output = self.curve.sample_with_derivative_unchecked(t);
output.derivative = -output.derivative;
output
} else {
self.curve.sample_with_derivative_unchecked(t)
}
}
}
impl<T, C> SampleTwoDerivatives<T> for PingPongCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
if t > self.curve.domain().end() {
let t = self.curve.domain().end() * 2.0 - t;
// See the implementation on `ReverseCurve` for an explanation of
// why this is correct.
let mut output = self.curve.sample_with_two_derivatives_unchecked(t);
output.derivative = -output.derivative;
output
} else {
self.curve.sample_with_two_derivatives_unchecked(t)
}
}
}
// -- ZipCurve
impl<S, T, C, D> SampleDerivative<(S, T)> for ZipCurve<S, T, C, D>
where
S: HasTangent,
T: HasTangent,
C: SampleDerivative<S>,
D: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(S, T)> {
let first_output = self.first.sample_with_derivative_unchecked(t);
let second_output = self.second.sample_with_derivative_unchecked(t);
WithDerivative {
value: (first_output.value, second_output.value),
derivative: Sum(first_output.derivative, second_output.derivative),
}
}
}
impl<S, T, C, D> SampleTwoDerivatives<(S, T)> for ZipCurve<S, T, C, D>
where
S: HasTangent,
T: HasTangent,
C: SampleTwoDerivatives<S>,
D: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(S, T)> {
let first_output = self.first.sample_with_two_derivatives_unchecked(t);
let second_output = self.second.sample_with_two_derivatives_unchecked(t);
WithTwoDerivatives {
value: (first_output.value, second_output.value),
derivative: Sum(first_output.derivative, second_output.derivative),
second_derivative: Sum(
first_output.second_derivative,
second_output.second_derivative,
),
}
}
}
// -- GraphCurve
impl<T, C> SampleDerivative<(f32, T)> for GraphCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(f32, T)> {
let output = self.base.sample_with_derivative_unchecked(t);
WithDerivative {
value: (t, output.value),
derivative: Sum(1.0, output.derivative),
}
}
}
impl<T, C> SampleTwoDerivatives<(f32, T)> for GraphCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(f32, T)> {
let output = self.base.sample_with_two_derivatives_unchecked(t);
WithTwoDerivatives {
value: (t, output.value),
derivative: Sum(1.0, output.derivative),
second_derivative: Sum(0.0, output.second_derivative),
}
}
}
// -- ReverseCurve
impl<T, C> SampleDerivative<T> for ReverseCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
// This gets almost the correct value, but we haven't accounted for the
// reversal of orientation yet.
let mut output = self
.curve
.sample_with_derivative_unchecked(self.domain().end() - (t - self.domain().start()));
output.derivative = -output.derivative;
output
}
}
impl<T, C> SampleTwoDerivatives<T> for ReverseCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
// This gets almost the correct value, but we haven't accounted for the
// reversal of orientation yet.
let mut output = self.curve.sample_with_two_derivatives_unchecked(
self.domain().end() - (t - self.domain().start()),
);
output.derivative = -output.derivative;
// (Note that the reparametrization that reverses the curve satisfies
// g'(t)^2 = 1 and g''(t) = 0, so the second derivative is already
// correct.)
output
}
}
// -- CurveReparamCurve
impl<T, C, D> SampleDerivative<T> for CurveReparamCurve<T, C, D>
where
T: HasTangent,
C: SampleDerivative<T>,
D: SampleDerivative<f32>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)
// is `self.reparam_curve`.
// Start by computing g(t) and g'(t).
let reparam_output = self.reparam_curve.sample_with_derivative_unchecked(t);
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
let mut output = self
.base
.sample_with_derivative_unchecked(reparam_output.value);
// Do the multiplication part of the chain rule.
output.derivative = output.derivative * reparam_output.derivative;
// value: f(g(t)), derivative: f'(g(t)) g'(t)
output
}
}
impl<T, C, D> SampleTwoDerivatives<T> for CurveReparamCurve<T, C, D>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
D: SampleTwoDerivatives<f32>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)
// is `self.reparam_curve`.
// Start by computing g(t), g'(t), g''(t).
let reparam_output = self.reparam_curve.sample_with_two_derivatives_unchecked(t);
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
// - second derivative: f''(g(t))
let mut output = self
.base
.sample_with_two_derivatives_unchecked(reparam_output.value);
// Set the second derivative according to the chain and product rules
// r''(t) = f''(g(t)) g'(t)^2 + f'(g(t)) g''(t)
output.second_derivative = (output.second_derivative
* (reparam_output.derivative * reparam_output.derivative))
+ (output.derivative * reparam_output.second_derivative);
// Set the first derivative according to the chain rule
// r'(t) = f'(g(t)) g'(t)
output.derivative = output.derivative * reparam_output.derivative;
output
}
}
// -- LinearReparamCurve
impl<T, C> SampleDerivative<T> for LinearReparamCurve<T, C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is
// the linear map bijecting `self.new_domain` onto `self.base.domain()`.
// The invariants imply this unwrap always succeeds.
let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();
// Compute g'(t) from the domain lengths.
let g_derivative = self.base.domain().length() / self.new_domain.length();
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
let mut output = self.base.sample_with_derivative_unchecked(g(t));
// Adjust the derivative according to the chain rule.
output.derivative = output.derivative * g_derivative;
output
}
}
impl<T, C> SampleTwoDerivatives<T> for LinearReparamCurve<T, C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is
// the linear map bijecting `self.new_domain` onto `self.base.domain()`.
// The invariants imply this unwrap always succeeds.
let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();
// Compute g'(t) from the domain lengths.
let g_derivative = self.base.domain().length() / self.new_domain.length();
// Compute:
// - value: f(g(t))
// - derivative: f'(g(t))
// - second derivative: f''(g(t))
let mut output = self.base.sample_with_two_derivatives_unchecked(g(t));
// Set the second derivative according to the chain and product rules
// r''(t) = f''(g(t)) g'(t)^2 (g''(t) = 0)
output.second_derivative = output.second_derivative * (g_derivative * g_derivative);
// Set the first derivative according to the chain rule
// r'(t) = f'(g(t)) g'(t)
output.derivative = output.derivative * g_derivative;
output
}
}
#[cfg(test)]
mod tests {
use approx::assert_abs_diff_eq;
use super::*;
use crate::cubic_splines::{CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator};
use crate::curve::{Curve, CurveExt, Interval};
use crate::{vec2, Vec2, Vec3};
fn test_curve() -> CubicCurve<Vec2> {
let control_pts = [[
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(0.0, 1.0),
vec2(1.0, 1.0),
]];
CubicBezier::new(control_pts).to_curve().unwrap()
}
fn other_test_curve() -> CubicCurve<Vec2> {
let control_pts = [
vec2(1.0, 1.0),
vec2(2.0, 1.0),
vec2(2.0, 0.0),
vec2(1.0, 0.0),
];
CubicCardinalSpline::new(0.5, control_pts)
.to_curve()
.unwrap()
}
fn reparam_curve() -> CubicCurve<f32> {
let control_pts = [[0.0, 0.25, 0.75, 1.0]];
CubicBezier::new(control_pts).to_curve().unwrap()
}
#[test]
fn constant_curve() {
let curve = ConstantCurve::new(Interval::UNIT, Vec3::new(0.2, 1.5, -2.6));
let jet = curve.sample_with_derivative(0.5).unwrap();
assert_abs_diff_eq!(jet.derivative, Vec3::ZERO);
}
#[test]
fn chain_curve() {
let curve1 = test_curve();
let curve2 = other_test_curve();
let curve = curve1.by_ref().chain(&curve2).unwrap();
let jet = curve.sample_with_derivative(0.65).unwrap();
let true_jet = curve1.sample_with_derivative(0.65).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(1.1).unwrap();
let true_jet = curve2.sample_with_derivative(0.1).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn continuation_curve() {
let curve1 = test_curve();
let curve2 = other_test_curve();
let curve = curve1.by_ref().chain_continue(&curve2).unwrap();
let jet = curve.sample_with_derivative(0.99).unwrap();
let true_jet = curve1.sample_with_derivative(0.99).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(1.3).unwrap();
let true_jet = curve2.sample_with_derivative(0.3).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn repeat_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().repeat(3).unwrap();
let jet = curve.sample_with_derivative(0.73).unwrap();
let true_jet = curve1.sample_with_derivative(0.73).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(3.5).unwrap();
let true_jet = curve1.sample_with_derivative(0.5).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn forever_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().forever().unwrap();
let jet = curve.sample_with_derivative(0.12).unwrap();
let true_jet = curve1.sample_with_derivative(0.12).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
let jet = curve.sample_with_derivative(500.5).unwrap();
let true_jet = curve1.sample_with_derivative(0.5).unwrap();
assert_abs_diff_eq!(jet.value, true_jet.value);
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
}
#[test]
fn ping_pong_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().ping_pong().unwrap();
let jet = curve.sample_with_derivative(0.99).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.99).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative);
let jet = curve.sample_with_derivative(1.3).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.7).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative, epsilon = 1.0e-5);
}
#[test]
fn zip_curve() {
let curve1 = test_curve();
let curve2 = other_test_curve();
let curve = curve1.by_ref().zip(&curve2).unwrap();
let jet = curve.sample_with_derivative(0.7).unwrap();
let comparison_jet1 = curve1.sample_with_derivative(0.7).unwrap();
let comparison_jet2 = curve2.sample_with_derivative(0.7).unwrap();
assert_abs_diff_eq!(jet.value.0, comparison_jet1.value);
assert_abs_diff_eq!(jet.value.1, comparison_jet2.value);
let Sum(derivative1, derivative2) = jet.derivative;
assert_abs_diff_eq!(derivative1, comparison_jet1.derivative);
assert_abs_diff_eq!(derivative2, comparison_jet2.derivative);
}
#[test]
fn graph_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().graph();
let jet = curve.sample_with_derivative(0.25).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.25).unwrap();
assert_abs_diff_eq!(jet.value.0, 0.25);
assert_abs_diff_eq!(jet.value.1, comparison_jet.value);
let Sum(one, derivative) = jet.derivative;
assert_abs_diff_eq!(one, 1.0);
assert_abs_diff_eq!(derivative, comparison_jet.derivative);
}
#[test]
fn reverse_curve() {
let curve1 = test_curve();
let curve = curve1.by_ref().reverse().unwrap();
let jet = curve.sample_with_derivative(0.23).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.77).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative);
}
#[test]
fn curve_reparam_curve() {
let reparam_curve = reparam_curve();
let reparam_jet = reparam_curve.sample_with_derivative(0.6).unwrap();
let curve1 = test_curve();
let curve = curve1.by_ref().reparametrize_by_curve(&reparam_curve);
let jet = curve.sample_with_derivative(0.6).unwrap();
let base_jet = curve1
.sample_with_derivative(reparam_curve.sample(0.6).unwrap())
.unwrap();
assert_abs_diff_eq!(jet.value, base_jet.value);
assert_abs_diff_eq!(jet.derivative, base_jet.derivative * reparam_jet.derivative);
}
#[test]
fn linear_reparam_curve() {
let curve1 = test_curve();
let curve = curve1
.by_ref()
.reparametrize_linear(Interval::new(0.0, 0.5).unwrap())
.unwrap();
let jet = curve.sample_with_derivative(0.23).unwrap();
let comparison_jet = curve1.sample_with_derivative(0.46).unwrap();
assert_abs_diff_eq!(jet.value, comparison_jet.value);
assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative * 2.0);
}
}

View File

@@ -0,0 +1,230 @@
//! This module holds traits related to extracting derivatives from curves. In
//! applications, the derivatives of interest are chiefly the first and second;
//! in this module, these are provided by the traits [`CurveWithDerivative`]
//! and [`CurveWithTwoDerivatives`].
//!
//! These take ownership of the curve they are used on by default, so that
//! the resulting output may be used in more durable contexts. For example,
//! `CurveWithDerivative<T>` is not dyn-compatible, but `Curve<WithDerivative<T>>`
//! is, so if such a curve needs to be stored in a dynamic context, calling
//! [`with_derivative`] and then placing the result in a
//! `Box<Curve<WithDerivative<T>>>` is sensible.
//!
//! On the other hand, in more transient contexts, consuming a value merely to
//! sample derivatives is inconvenient, and in these cases, it is recommended
//! to use [`by_ref`] when possible to create a referential curve first, retaining
//! liveness of the original.
//!
//! This module also holds the [`SampleDerivative`] and [`SampleTwoDerivatives`]
//! traits, which can be used to easily implement `CurveWithDerivative` and its
//! counterpart.
//!
//! [`with_derivative`]: CurveWithDerivative::with_derivative
//! [`by_ref`]: crate::curve::CurveExt::by_ref
pub mod adaptor_impls;
use crate::{
common_traits::{HasTangent, WithDerivative, WithTwoDerivatives},
curve::{Curve, Interval},
};
use core::ops::Deref;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{FromReflect, Reflect};
/// Trait for curves that have a well-defined notion of derivative, allowing for
/// derivatives to be extracted along with values.
///
/// This is implemented by implementing [`SampleDerivative`].
pub trait CurveWithDerivative<T>: SampleDerivative<T> + Sized
where
T: HasTangent,
{
/// This curve, but with its first derivative included in sampling.
///
/// Notably, the output type is a `Curve<WithDerivative<T>>`.
fn with_derivative(self) -> SampleDerivativeWrapper<Self>;
}
/// Trait for curves that have a well-defined notion of second derivative,
/// allowing for two derivatives to be extracted along with values.
///
/// This is implemented by implementing [`SampleTwoDerivatives`].
pub trait CurveWithTwoDerivatives<T>: SampleTwoDerivatives<T> + Sized
where
T: HasTangent,
{
/// This curve, but with its first two derivatives included in sampling.
///
/// Notably, the output type is a `Curve<WithTwoDerivatives<T>>`.
fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper<Self>;
}
/// A trait for curves that can sample derivatives in addition to values.
///
/// Types that implement this trait automatically implement [`CurveWithDerivative`];
/// the curve produced by [`with_derivative`] uses the sampling defined in the trait
/// implementation.
///
/// [`with_derivative`]: CurveWithDerivative::with_derivative
pub trait SampleDerivative<T>: Curve<T>
where
T: HasTangent,
{
/// Sample this curve at the parameter value `t`, extracting the associated value
/// in addition to its derivative. This is the unchecked version of sampling, which
/// should only be used if the sample time `t` is already known to lie within the
/// curve's domain.
///
/// See [`Curve::sample_unchecked`] for more information.
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T>;
/// Sample this curve's value and derivative at the parameter value `t`, returning
/// `None` if the point is outside of the curve's domain.
fn sample_with_derivative(&self, t: f32) -> Option<WithDerivative<T>> {
match self.domain().contains(t) {
true => Some(self.sample_with_derivative_unchecked(t)),
false => None,
}
}
/// Sample this curve's value and derivative at the parameter value `t`, clamping `t`
/// to lie inside the domain of the curve.
fn sample_with_derivative_clamped(&self, t: f32) -> WithDerivative<T> {
let t = self.domain().clamp(t);
self.sample_with_derivative_unchecked(t)
}
}
impl<T, C, D> SampleDerivative<T> for D
where
T: HasTangent,
C: SampleDerivative<T> + ?Sized,
D: Deref<Target = C>,
{
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
<C as SampleDerivative<T>>::sample_with_derivative_unchecked(self, t)
}
}
/// A trait for curves that can sample two derivatives in addition to values.
///
/// Types that implement this trait automatically implement [`CurveWithTwoDerivatives`];
/// the curve produced by [`with_two_derivatives`] uses the sampling defined in the trait
/// implementation.
///
/// [`with_two_derivatives`]: CurveWithTwoDerivatives::with_two_derivatives
pub trait SampleTwoDerivatives<T>: Curve<T>
where
T: HasTangent,
{
/// Sample this curve at the parameter value `t`, extracting the associated value
/// in addition to two derivatives. This is the unchecked version of sampling, which
/// should only be used if the sample time `t` is already known to lie within the
/// curve's domain.
///
/// See [`Curve::sample_unchecked`] for more information.
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T>;
/// Sample this curve's value and two derivatives at the parameter value `t`, returning
/// `None` if the point is outside of the curve's domain.
fn sample_with_two_derivatives(&self, t: f32) -> Option<WithTwoDerivatives<T>> {
match self.domain().contains(t) {
true => Some(self.sample_with_two_derivatives_unchecked(t)),
false => None,
}
}
/// Sample this curve's value and two derivatives at the parameter value `t`, clamping `t`
/// to lie inside the domain of the curve.
fn sample_with_two_derivatives_clamped(&self, t: f32) -> WithTwoDerivatives<T> {
let t = self.domain().clamp(t);
self.sample_with_two_derivatives_unchecked(t)
}
}
/// A wrapper that uses a [`SampleDerivative<T>`] curve to produce a `Curve<WithDerivative<T>>`.
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct SampleDerivativeWrapper<C>(C);
impl<T, C> Curve<WithDerivative<T>> for SampleDerivativeWrapper<C>
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn domain(&self) -> Interval {
self.0.domain()
}
fn sample_unchecked(&self, t: f32) -> WithDerivative<T> {
self.0.sample_with_derivative_unchecked(t)
}
fn sample(&self, t: f32) -> Option<WithDerivative<T>> {
self.0.sample_with_derivative(t)
}
fn sample_clamped(&self, t: f32) -> WithDerivative<T> {
self.0.sample_with_derivative_clamped(t)
}
}
/// A wrapper that uses a [`SampleTwoDerivatives<T>`] curve to produce a
/// `Curve<WithTwoDerivatives<T>>`.
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct SampleTwoDerivativesWrapper<C>(C);
impl<T, C> Curve<WithTwoDerivatives<T>> for SampleTwoDerivativesWrapper<C>
where
T: HasTangent,
C: SampleTwoDerivatives<T>,
{
fn domain(&self) -> Interval {
self.0.domain()
}
fn sample_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
self.0.sample_with_two_derivatives_unchecked(t)
}
fn sample(&self, t: f32) -> Option<WithTwoDerivatives<T>> {
self.0.sample_with_two_derivatives(t)
}
fn sample_clamped(&self, t: f32) -> WithTwoDerivatives<T> {
self.0.sample_with_two_derivatives_clamped(t)
}
}
impl<T, C> CurveWithDerivative<T> for C
where
T: HasTangent,
C: SampleDerivative<T>,
{
fn with_derivative(self) -> SampleDerivativeWrapper<Self> {
SampleDerivativeWrapper(self)
}
}
impl<T, C> CurveWithTwoDerivatives<T> for C
where
T: HasTangent,
C: SampleTwoDerivatives<T> + CurveWithDerivative<T>,
{
fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper<Self> {
SampleTwoDerivativesWrapper(self)
}
}

1202
vendor/bevy_math/src/curve/easing.rs vendored Normal file

File diff suppressed because it is too large Load Diff

382
vendor/bevy_math/src/curve/interval.rs vendored Normal file
View File

@@ -0,0 +1,382 @@
//! The [`Interval`] type for nonempty intervals used by the [`Curve`](super::Curve) trait.
use core::{
cmp::{max_by, min_by},
ops::RangeInclusive,
};
use itertools::Either;
use thiserror::Error;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A nonempty closed interval, possibly unbounded in either direction.
///
/// In other words, the interval may stretch all the way to positive or negative infinity, but it
/// will always have some nonempty interior.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Interval {
start: f32,
end: f32,
}
/// An error that indicates that an operation would have returned an invalid [`Interval`].
#[derive(Debug, Error)]
#[error("The resulting interval would be invalid (empty or with a NaN endpoint)")]
pub struct InvalidIntervalError;
/// An error indicating that spaced points could not be extracted from an unbounded interval.
#[derive(Debug, Error)]
#[error("Cannot extract spaced points from an unbounded interval")]
pub struct SpacedPointsError;
/// An error indicating that a linear map between intervals could not be constructed because of
/// unboundedness.
#[derive(Debug, Error)]
#[error("Could not construct linear function to map between intervals")]
pub(super) enum LinearMapError {
/// The source interval being mapped out of was unbounded.
#[error("The source interval is unbounded")]
SourceUnbounded,
/// The target interval being mapped into was unbounded.
#[error("The target interval is unbounded")]
TargetUnbounded,
}
impl Interval {
/// Create a new [`Interval`] with the specified `start` and `end`. The interval can be unbounded
/// but cannot be empty (so `start` must be less than `end`) and neither endpoint can be NaN; invalid
/// parameters will result in an error.
#[inline]
pub fn new(start: f32, end: f32) -> Result<Self, InvalidIntervalError> {
if start >= end || start.is_nan() || end.is_nan() {
Err(InvalidIntervalError)
} else {
Ok(Self { start, end })
}
}
/// An interval of length 1.0, starting at 0.0 and ending at 1.0.
pub const UNIT: Self = Self {
start: 0.0,
end: 1.0,
};
/// An interval which stretches across the entire real line from negative infinity to infinity.
pub const EVERYWHERE: Self = Self {
start: f32::NEG_INFINITY,
end: f32::INFINITY,
};
/// Get the start of this interval.
#[inline]
pub const fn start(self) -> f32 {
self.start
}
/// Get the end of this interval.
#[inline]
pub const fn end(self) -> f32 {
self.end
}
/// Create an [`Interval`] by intersecting this interval with another. Returns an error if the
/// intersection would be empty (hence an invalid interval).
pub fn intersect(self, other: Interval) -> Result<Interval, InvalidIntervalError> {
let lower = max_by(self.start, other.start, f32::total_cmp);
let upper = min_by(self.end, other.end, f32::total_cmp);
Self::new(lower, upper)
}
/// Get the length of this interval. Note that the result may be infinite (`f32::INFINITY`).
#[inline]
pub fn length(self) -> f32 {
self.end - self.start
}
/// Returns `true` if this interval is bounded — that is, if both its start and end are finite.
///
/// Equivalently, an interval is bounded if its length is finite.
#[inline]
pub fn is_bounded(self) -> bool {
self.length().is_finite()
}
/// Returns `true` if this interval has a finite start.
#[inline]
pub fn has_finite_start(self) -> bool {
self.start.is_finite()
}
/// Returns `true` if this interval has a finite end.
#[inline]
pub fn has_finite_end(self) -> bool {
self.end.is_finite()
}
/// Returns `true` if `item` is contained in this interval.
#[inline]
pub fn contains(self, item: f32) -> bool {
(self.start..=self.end).contains(&item)
}
/// Returns `true` if the other interval is contained in this interval.
///
/// This is non-strict: each interval will contain itself.
#[inline]
pub fn contains_interval(self, other: Self) -> bool {
self.start <= other.start && self.end >= other.end
}
/// Clamp the given `value` to lie within this interval.
#[inline]
pub fn clamp(self, value: f32) -> f32 {
value.clamp(self.start, self.end)
}
/// Get an iterator over equally-spaced points from this interval in increasing order.
/// If `points` is 1, the start of this interval is returned. If `points` is 0, an empty
/// iterator is returned. An error is returned if the interval is unbounded.
#[inline]
pub fn spaced_points(
self,
points: usize,
) -> Result<impl Iterator<Item = f32>, SpacedPointsError> {
if !self.is_bounded() {
return Err(SpacedPointsError);
}
if points < 2 {
// If `points` is 1, this is `Some(self.start)` as an iterator, and if `points` is 0,
// then this is `None` as an iterator. This is written this way to avoid having to
// introduce a ternary disjunction of iterators.
let iter = (points == 1).then_some(self.start).into_iter();
return Ok(Either::Left(iter));
}
let step = self.length() / (points - 1) as f32;
let iter = (0..points).map(move |x| self.start + x as f32 * step);
Ok(Either::Right(iter))
}
/// Get the linear function which maps this interval onto the `other` one. Returns an error if either
/// interval is unbounded.
#[inline]
pub(super) fn linear_map_to(self, other: Self) -> Result<impl Fn(f32) -> f32, LinearMapError> {
if !self.is_bounded() {
return Err(LinearMapError::SourceUnbounded);
}
if !other.is_bounded() {
return Err(LinearMapError::TargetUnbounded);
}
let scale = other.length() / self.length();
Ok(move |x| (x - self.start) * scale + other.start)
}
}
impl TryFrom<RangeInclusive<f32>> for Interval {
type Error = InvalidIntervalError;
fn try_from(range: RangeInclusive<f32>) -> Result<Self, Self::Error> {
Interval::new(*range.start(), *range.end())
}
}
/// Create an [`Interval`] with a given `start` and `end`. Alias of [`Interval::new`].
#[inline]
pub fn interval(start: f32, end: f32) -> Result<Interval, InvalidIntervalError> {
Interval::new(start, end)
}
#[cfg(test)]
mod tests {
use crate::ops;
use super::*;
use alloc::vec::Vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
#[test]
fn make_intervals() {
let ivl = Interval::new(2.0, -1.0);
assert!(ivl.is_err());
let ivl = Interval::new(-0.0, 0.0);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NEG_INFINITY, 15.5);
assert!(ivl.is_ok());
let ivl = Interval::new(-2.0, f32::INFINITY);
assert!(ivl.is_ok());
let ivl = Interval::new(f32::NEG_INFINITY, f32::INFINITY);
assert!(ivl.is_ok());
let ivl = Interval::new(f32::INFINITY, f32::NEG_INFINITY);
assert!(ivl.is_err());
let ivl = Interval::new(-1.0, f32::NAN);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NAN, -42.0);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NAN, f32::NAN);
assert!(ivl.is_err());
let ivl = Interval::new(0.0, 1.0);
assert!(ivl.is_ok());
}
#[test]
fn lengths() {
let ivl = interval(-5.0, 10.0).unwrap();
assert!(ops::abs(ivl.length() - 15.0) <= f32::EPSILON);
let ivl = interval(5.0, 100.0).unwrap();
assert!(ops::abs(ivl.length() - 95.0) <= f32::EPSILON);
let ivl = interval(0.0, f32::INFINITY).unwrap();
assert_eq!(ivl.length(), f32::INFINITY);
let ivl = interval(f32::NEG_INFINITY, 0.0).unwrap();
assert_eq!(ivl.length(), f32::INFINITY);
let ivl = Interval::EVERYWHERE;
assert_eq!(ivl.length(), f32::INFINITY);
}
#[test]
fn intersections() {
let ivl1 = interval(-1.0, 1.0).unwrap();
let ivl2 = interval(0.0, 2.0).unwrap();
let ivl3 = interval(-3.0, 0.0).unwrap();
let ivl4 = interval(0.0, f32::INFINITY).unwrap();
let ivl5 = interval(f32::NEG_INFINITY, 0.0).unwrap();
let ivl6 = Interval::EVERYWHERE;
assert!(ivl1.intersect(ivl2).is_ok_and(|ivl| ivl == Interval::UNIT));
assert!(ivl1
.intersect(ivl3)
.is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
assert!(ivl2.intersect(ivl3).is_err());
assert!(ivl1.intersect(ivl4).is_ok_and(|ivl| ivl == Interval::UNIT));
assert!(ivl1
.intersect(ivl5)
.is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
assert!(ivl4.intersect(ivl5).is_err());
assert_eq!(ivl1.intersect(ivl6).unwrap(), ivl1);
assert_eq!(ivl4.intersect(ivl6).unwrap(), ivl4);
assert_eq!(ivl5.intersect(ivl6).unwrap(), ivl5);
}
#[test]
fn containment() {
let ivl = Interval::UNIT;
assert!(ivl.contains(0.0));
assert!(ivl.contains(1.0));
assert!(ivl.contains(0.5));
assert!(!ivl.contains(-0.1));
assert!(!ivl.contains(1.1));
assert!(!ivl.contains(f32::NAN));
let ivl = interval(3.0, f32::INFINITY).unwrap();
assert!(ivl.contains(3.0));
assert!(ivl.contains(2.0e5));
assert!(ivl.contains(3.5e6));
assert!(!ivl.contains(2.5));
assert!(!ivl.contains(-1e5));
assert!(!ivl.contains(f32::NAN));
}
#[test]
fn interval_containment() {
let ivl = Interval::UNIT;
assert!(ivl.contains_interval(interval(-0.0, 0.5).unwrap()));
assert!(ivl.contains_interval(interval(0.5, 1.0).unwrap()));
assert!(ivl.contains_interval(interval(0.25, 0.75).unwrap()));
assert!(!ivl.contains_interval(interval(-0.25, 0.5).unwrap()));
assert!(!ivl.contains_interval(interval(0.5, 1.25).unwrap()));
assert!(!ivl.contains_interval(interval(0.25, f32::INFINITY).unwrap()));
assert!(!ivl.contains_interval(interval(f32::NEG_INFINITY, 0.75).unwrap()));
let big_ivl = interval(0.0, f32::INFINITY).unwrap();
assert!(big_ivl.contains_interval(interval(0.0, 5.0).unwrap()));
assert!(big_ivl.contains_interval(interval(0.0, f32::INFINITY).unwrap()));
assert!(big_ivl.contains_interval(interval(1.0, 5.0).unwrap()));
assert!(!big_ivl.contains_interval(interval(-1.0, f32::INFINITY).unwrap()));
assert!(!big_ivl.contains_interval(interval(-2.0, 5.0).unwrap()));
}
#[test]
fn boundedness() {
assert!(!Interval::EVERYWHERE.is_bounded());
assert!(interval(0.0, 3.5e5).unwrap().is_bounded());
assert!(!interval(-2.0, f32::INFINITY).unwrap().is_bounded());
assert!(!interval(f32::NEG_INFINITY, 5.0).unwrap().is_bounded());
}
#[test]
fn linear_maps() {
let ivl1 = interval(-3.0, 5.0).unwrap();
let ivl2 = Interval::UNIT;
let map = ivl1.linear_map_to(ivl2);
assert!(map.is_ok_and(|f| f(-3.0).abs_diff_eq(&0.0, f32::EPSILON)
&& f(5.0).abs_diff_eq(&1.0, f32::EPSILON)
&& f(1.0).abs_diff_eq(&0.5, f32::EPSILON)));
let ivl1 = Interval::UNIT;
let ivl2 = Interval::EVERYWHERE;
assert!(ivl1.linear_map_to(ivl2).is_err());
let ivl1 = interval(f32::NEG_INFINITY, -4.0).unwrap();
let ivl2 = Interval::UNIT;
assert!(ivl1.linear_map_to(ivl2).is_err());
}
#[test]
fn spaced_points() {
let ivl = interval(0.0, 50.0).unwrap();
let points_iter: Vec<f32> = ivl.spaced_points(1).unwrap().collect();
assert_abs_diff_eq!(points_iter[0], 0.0);
assert_eq!(points_iter.len(), 1);
let points_iter: Vec<f32> = ivl.spaced_points(2).unwrap().collect();
assert_abs_diff_eq!(points_iter[0], 0.0);
assert_abs_diff_eq!(points_iter[1], 50.0);
let points_iter = ivl.spaced_points(21).unwrap();
let step = ivl.length() / 20.0;
for (index, point) in points_iter.enumerate() {
let expected = ivl.start() + step * index as f32;
assert_abs_diff_eq!(point, expected);
}
let ivl = interval(-21.0, 79.0).unwrap();
let points_iter = ivl.spaced_points(10000).unwrap();
let step = ivl.length() / 9999.0;
for (index, point) in points_iter.enumerate() {
let expected = ivl.start() + step * index as f32;
assert_abs_diff_eq!(point, expected);
}
let ivl = interval(-1.0, f32::INFINITY).unwrap();
let points_iter = ivl.spaced_points(25);
assert!(points_iter.is_err());
let ivl = interval(f32::NEG_INFINITY, -25.0).unwrap();
let points_iter = ivl.spaced_points(9);
assert!(points_iter.is_err());
}
}

57
vendor/bevy_math/src/curve/iterable.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
//! Iterable curves, which sample in the form of an iterator in order to support `Vec`-like
//! output whose length cannot be known statically.
use super::Interval;
#[cfg(feature = "alloc")]
use {super::ConstantCurve, alloc::vec::Vec};
/// A curve which provides samples in the form of [`Iterator`]s.
///
/// This is an abstraction that provides an interface for curves which look like `Curve<Vec<T>>`
/// but side-stepping issues with allocation on sampling. This happens when the size of an output
/// array cannot be known statically.
pub trait IterableCurve<T> {
/// The interval over which this curve is parametrized.
fn domain(&self) -> Interval;
/// Sample a point on this curve at the parameter value `t`, producing an iterator over values.
/// This is the unchecked version of sampling, which should only be used if the sample time `t`
/// is already known to lie within the curve's domain.
///
/// Values sampled from outside of a curve's domain are generally considered invalid; data which
/// is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation
/// beyond a curve's domain should not be relied upon.
fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T>;
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
/// The parameter `t` is clamped to the domain of the curve.
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
let t_clamped = self.domain().clamp(t);
self.sample_iter_unchecked(t_clamped)
}
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
/// If the parameter `t` does not lie in the curve's domain, `None` is returned.
fn sample_iter(&self, t: f32) -> Option<impl Iterator<Item = T>> {
if self.domain().contains(t) {
Some(self.sample_iter_unchecked(t))
} else {
None
}
}
}
#[cfg(feature = "alloc")]
impl<T> IterableCurve<T> for ConstantCurve<Vec<T>>
where
T: Clone,
{
fn domain(&self) -> Interval {
self.domain
}
fn sample_iter_unchecked(&self, _t: f32) -> impl Iterator<Item = T> {
self.value.iter().cloned()
}
}

1343
vendor/bevy_math/src/curve/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,406 @@
//! Sample-interpolated curves constructed using the [`Curve`] API.
use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError};
use super::{Curve, Interval};
use crate::StableInterpolate;
#[cfg(feature = "bevy_reflect")]
use alloc::format;
use core::any::type_name;
use core::fmt::{self, Debug};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::sample_curves";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
/// A curve that is defined by explicit neighbor interpolation over a set of evenly-spaced samples.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct SampleCurve<T, I> {
pub(crate) core: EvenCore<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) interpolation: I,
}
impl<T, I> Debug for SampleCurve<T, I>
where
EvenCore<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SampleCurve")
.field("core", &self.core)
.field("interpolation", &type_name::<I>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, I> TypePath for SampleCurve<T, I>
where
T: TypePath,
I: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::SampleCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<I>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!("SampleCurve<{},{}>", T::type_path(), type_name::<I>())
})
}
fn type_ident() -> Option<&'static str> {
Some("SampleCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, I> Curve<T> for SampleCurve<T, I>
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `EvenCore::sample_with` is implicitly clamped.
self.core.sample_with(t, &self.interpolation)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T, I> SampleCurve<T, I> {
/// Create a new [`SampleCurve`] using the specified `interpolation` to interpolate between
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
/// given `domain` is unbounded.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
interpolation: I,
) -> Result<Self, EvenCoreError>
where
I: Fn(&T, &T, f32) -> T,
{
Ok(Self {
core: EvenCore::new(domain, samples)?,
interpolation,
})
}
}
/// A curve that is defined by neighbor interpolation over a set of evenly-spaced samples,
/// interpolated automatically using [a particularly well-behaved interpolation].
///
/// [a particularly well-behaved interpolation]: StableInterpolate
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct SampleAutoCurve<T> {
pub(crate) core: EvenCore<T>,
}
impl<T> Curve<T> for SampleAutoCurve<T>
where
T: StableInterpolate,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `EvenCore::sample_with` is implicitly clamped.
self.core
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T> SampleAutoCurve<T> {
/// Create a new [`SampleCurve`] using type-inferred interpolation to interpolate between
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
/// given `domain` is unbounded.
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
) -> Result<Self, EvenCoreError> {
Ok(Self {
core: EvenCore::new(domain, samples)?,
})
}
}
/// A curve that is defined by interpolation over unevenly spaced samples with explicit
/// interpolation.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct UnevenSampleCurve<T, I> {
pub(crate) core: UnevenCore<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) interpolation: I,
}
impl<T, I> Debug for UnevenSampleCurve<T, I>
where
UnevenCore<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SampleCurve")
.field("core", &self.core)
.field("interpolation", &type_name::<I>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, I> TypePath for UnevenSampleCurve<T, I>
where
T: TypePath,
I: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::UnevenSampleCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<I>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!("UnevenSampleCurve<{},{}>", T::type_path(), type_name::<I>())
})
}
fn type_ident() -> Option<&'static str> {
Some("UnevenSampleCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, I> Curve<T> for UnevenSampleCurve<T, I>
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `UnevenCore::sample_with` is implicitly clamped.
self.core.sample_with(t, &self.interpolation)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T, I> UnevenSampleCurve<T, I> {
/// Create a new [`UnevenSampleCurve`] using the provided `interpolation` to interpolate
/// between adjacent `timed_samples`. The given samples are filtered to finite times and
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
/// returned.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
pub fn new(
timed_samples: impl IntoIterator<Item = (f32, T)>,
interpolation: I,
) -> Result<Self, UnevenCoreError> {
Ok(Self {
core: UnevenCore::new(timed_samples)?,
interpolation,
})
}
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the sample times of the curve.
///
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {
Self {
core: self.core.map_sample_times(f),
interpolation: self.interpolation,
}
}
}
/// A curve that is defined by interpolation over unevenly spaced samples,
/// interpolated automatically using [a particularly well-behaved interpolation].
///
/// [a particularly well-behaved interpolation]: StableInterpolate
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct UnevenSampleAutoCurve<T> {
pub(crate) core: UnevenCore<T>,
}
impl<T> Curve<T> for UnevenSampleAutoCurve<T>
where
T: StableInterpolate,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `UnevenCore::sample_with` is implicitly clamped.
self.core
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T> UnevenSampleAutoCurve<T> {
/// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.
///
/// The samples are filtered to finite times and sorted internally; if there are not
/// at least 2 valid timed samples, an error will be returned.
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
Ok(Self {
core: UnevenCore::new(timed_samples)?,
})
}
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the sample times of the curve.
///
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {
Self {
core: self.core.map_sample_times(f),
}
}
}
#[cfg(test)]
#[cfg(feature = "bevy_reflect")]
mod tests {
//! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`
//! can be `Reflect` under reasonable circumstances where their interpolation is defined by:
//! - function items
//! - 'static closures
//! - function pointers
use super::{SampleCurve, UnevenSampleCurve};
use crate::{curve::Interval, VectorSpace};
use alloc::boxed::Box;
use bevy_reflect::Reflect;
#[test]
fn reflect_sample_curve() {
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
x.lerp(*y, t)
}
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
let baz: fn(&f32, &f32, f32) -> f32 = bar;
let samples = [0.0, 1.0, 2.0];
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());
}
#[test]
fn reflect_uneven_sample_curve() {
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
x.lerp(*y, t)
}
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
let baz: fn(&f32, &f32, f32) -> f32 = bar;
let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());
}
}

1093
vendor/bevy_math/src/direction.rs vendored Normal file

File diff suppressed because it is too large Load Diff

183
vendor/bevy_math/src/float_ord.rs vendored Normal file
View File

@@ -0,0 +1,183 @@
use core::{
cmp::Ordering,
hash::{Hash, Hasher},
ops::Neg,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// A wrapper for floats that implements [`Ord`], [`Eq`], and [`Hash`] traits.
///
/// This is a work around for the fact that the IEEE 754-2008 standard,
/// implemented by Rust's [`f32`] type,
/// doesn't define an ordering for [`NaN`](f32::NAN),
/// and `NaN` is not considered equal to any other `NaN`.
///
/// Wrapping a float with `FloatOrd` breaks conformance with the standard
/// by sorting `NaN` as less than all other numbers and equal to any other `NaN`.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
pub struct FloatOrd(pub f32);
impl PartialOrd for FloatOrd {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
fn lt(&self, other: &Self) -> bool {
!other.le(self)
}
// If `self` is NaN, it is equal to another NaN and less than all other floats, so return true.
// If `self` isn't NaN and `other` is, the float comparison returns false, which match the `FloatOrd` ordering.
// Otherwise, a standard float comparison happens.
fn le(&self, other: &Self) -> bool {
self.0.is_nan() || self.0 <= other.0
}
fn gt(&self, other: &Self) -> bool {
!self.le(other)
}
fn ge(&self, other: &Self) -> bool {
other.le(self)
}
}
impl Ord for FloatOrd {
#[expect(
clippy::comparison_chain,
reason = "This can't be rewritten with `match` and `cmp`, as this is `cmp` itself."
)]
fn cmp(&self, other: &Self) -> Ordering {
if self > other {
Ordering::Greater
} else if self < other {
Ordering::Less
} else {
Ordering::Equal
}
}
}
impl PartialEq for FloatOrd {
fn eq(&self, other: &Self) -> bool {
if self.0.is_nan() {
other.0.is_nan()
} else {
self.0 == other.0
}
}
}
impl Eq for FloatOrd {}
impl Hash for FloatOrd {
fn hash<H: Hasher>(&self, state: &mut H) {
if self.0.is_nan() {
// Ensure all NaN representations hash to the same value
state.write(&f32::to_ne_bytes(f32::NAN));
} else if self.0 == 0.0 {
// Ensure both zeroes hash to the same value
state.write(&f32::to_ne_bytes(0.0f32));
} else {
state.write(&f32::to_ne_bytes(self.0));
}
}
}
impl Neg for FloatOrd {
type Output = FloatOrd;
fn neg(self) -> Self::Output {
FloatOrd(-self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
const NAN: FloatOrd = FloatOrd(f32::NAN);
const ZERO: FloatOrd = FloatOrd(0.0);
const ONE: FloatOrd = FloatOrd(1.0);
#[test]
fn float_ord_eq() {
assert_eq!(NAN, NAN);
assert_ne!(NAN, ZERO);
assert_ne!(ZERO, NAN);
assert_eq!(ZERO, ZERO);
}
#[test]
fn float_ord_cmp() {
assert_eq!(NAN.cmp(&NAN), Ordering::Equal);
assert_eq!(NAN.cmp(&ZERO), Ordering::Less);
assert_eq!(ZERO.cmp(&NAN), Ordering::Greater);
assert_eq!(ZERO.cmp(&ZERO), Ordering::Equal);
assert_eq!(ONE.cmp(&ZERO), Ordering::Greater);
assert_eq!(ZERO.cmp(&ONE), Ordering::Less);
}
#[test]
#[expect(
clippy::nonminimal_bool,
reason = "This tests that all operators work as they should, and in the process requires some non-simplified boolean expressions."
)]
fn float_ord_cmp_operators() {
assert!(!(NAN < NAN));
assert!(NAN < ZERO);
assert!(!(ZERO < NAN));
assert!(!(ZERO < ZERO));
assert!(ZERO < ONE);
assert!(!(ONE < ZERO));
assert!(!(NAN > NAN));
assert!(!(NAN > ZERO));
assert!(ZERO > NAN);
assert!(!(ZERO > ZERO));
assert!(!(ZERO > ONE));
assert!(ONE > ZERO);
assert!(NAN <= NAN);
assert!(NAN <= ZERO);
assert!(!(ZERO <= NAN));
assert!(ZERO <= ZERO);
assert!(ZERO <= ONE);
assert!(!(ONE <= ZERO));
assert!(NAN >= NAN);
assert!(!(NAN >= ZERO));
assert!(ZERO >= NAN);
assert!(ZERO >= ZERO);
assert!(!(ZERO >= ONE));
assert!(ONE >= ZERO);
}
#[cfg(feature = "std")]
#[test]
fn float_ord_hash() {
let hash = |num| {
let mut h = std::hash::DefaultHasher::new();
FloatOrd(num).hash(&mut h);
h.finish()
};
assert_ne!((-0.0f32).to_bits(), 0.0f32.to_bits());
assert_eq!(hash(-0.0), hash(0.0));
let nan_1 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0001);
assert!(nan_1.is_nan());
let nan_2 = f32::from_bits(0b0111_1111_1000_0000_0000_0000_0000_0010);
assert!(nan_2.is_nan());
assert_ne!(nan_1.to_bits(), nan_2.to_bits());
assert_eq!(hash(nan_1), hash(nan_2));
}
}

688
vendor/bevy_math/src/isometry.rs vendored Normal file
View File

@@ -0,0 +1,688 @@
//! Isometry types for expressing rigid motions in two and three dimensions.
use crate::{Affine2, Affine3, Affine3A, Dir2, Dir3, Mat3, Mat3A, Quat, Rot2, Vec2, Vec3, Vec3A};
use core::ops::Mul;
#[cfg(feature = "approx")]
use approx::{AbsDiffEq, RelativeEq, UlpsEq};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// An isometry in two dimensions, representing a rotation followed by a translation.
/// This can often be useful for expressing relative positions and transformations from one position to another.
///
/// In particular, this type represents a distance-preserving transformation known as a *rigid motion* or a *direct motion*,
/// and belongs to the special [Euclidean group] SE(2). This includes translation and rotation, but excludes reflection.
///
/// For the three-dimensional version, see [`Isometry3d`].
///
/// [Euclidean group]: https://en.wikipedia.org/wiki/Euclidean_group
///
/// # Example
///
/// Isometries can be created from a given translation and rotation:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let iso = Isometry2d::new(Vec2::new(2.0, 1.0), Rot2::degrees(90.0));
/// ```
///
/// Or from separate parts:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let iso1 = Isometry2d::from_translation(Vec2::new(2.0, 1.0));
/// let iso2 = Isometry2d::from_rotation(Rot2::degrees(90.0));
/// ```
///
/// The isometries can be used to transform points:
///
/// ```
/// # use approx::assert_abs_diff_eq;
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let iso = Isometry2d::new(Vec2::new(2.0, 1.0), Rot2::degrees(90.0));
/// let point = Vec2::new(4.0, 4.0);
///
/// // These are equivalent
/// let result = iso.transform_point(point);
/// let result = iso * point;
///
/// assert_eq!(result, Vec2::new(-2.0, 5.0));
/// ```
///
/// Isometries can also be composed together:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// # let iso = Isometry2d::new(Vec2::new(2.0, 1.0), Rot2::degrees(90.0));
/// # let iso1 = Isometry2d::from_translation(Vec2::new(2.0, 1.0));
/// # let iso2 = Isometry2d::from_rotation(Rot2::degrees(90.0));
/// #
/// assert_eq!(iso1 * iso2, iso);
/// ```
///
/// One common operation is to compute an isometry representing the relative positions of two objects
/// for things like intersection tests. This can be done with an inverse transformation:
///
/// ```
/// # use bevy_math::{Isometry2d, Rot2, Vec2};
/// #
/// let circle_iso = Isometry2d::from_translation(Vec2::new(2.0, 1.0));
/// let rectangle_iso = Isometry2d::from_rotation(Rot2::degrees(90.0));
///
/// // Compute the relative position and orientation between the two shapes
/// let relative_iso = circle_iso.inverse() * rectangle_iso;
///
/// // Or alternatively, to skip an extra rotation operation:
/// let relative_iso = circle_iso.inverse_mul(rectangle_iso);
/// ```
#[derive(Copy, Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Isometry2d {
/// The rotational part of a two-dimensional isometry.
pub rotation: Rot2,
/// The translational part of a two-dimensional isometry.
pub translation: Vec2,
}
impl Isometry2d {
/// The identity isometry which represents the rigid motion of not doing anything.
pub const IDENTITY: Self = Isometry2d {
rotation: Rot2::IDENTITY,
translation: Vec2::ZERO,
};
/// Create a two-dimensional isometry from a rotation and a translation.
#[inline]
pub fn new(translation: Vec2, rotation: Rot2) -> Self {
Isometry2d {
rotation,
translation,
}
}
/// Create a two-dimensional isometry from a rotation.
#[inline]
pub fn from_rotation(rotation: Rot2) -> Self {
Isometry2d {
rotation,
translation: Vec2::ZERO,
}
}
/// Create a two-dimensional isometry from a translation.
#[inline]
pub fn from_translation(translation: Vec2) -> Self {
Isometry2d {
rotation: Rot2::IDENTITY,
translation,
}
}
/// Create a two-dimensional isometry from a translation with the given `x` and `y` components.
#[inline]
pub fn from_xy(x: f32, y: f32) -> Self {
Isometry2d {
rotation: Rot2::IDENTITY,
translation: Vec2::new(x, y),
}
}
/// The inverse isometry that undoes this one.
#[inline]
pub fn inverse(&self) -> Self {
let inv_rot = self.rotation.inverse();
Isometry2d {
rotation: inv_rot,
translation: inv_rot * -self.translation,
}
}
/// Compute `iso1.inverse() * iso2` in a more efficient way for one-shot cases.
///
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_mul(&self, rhs: Self) -> Self {
let inv_rot = self.rotation.inverse();
let delta_translation = rhs.translation - self.translation;
Self::new(inv_rot * delta_translation, inv_rot * rhs.rotation)
}
/// Transform a point by rotating and translating it using this isometry.
#[inline]
pub fn transform_point(&self, point: Vec2) -> Vec2 {
self.rotation * point + self.translation
}
/// Transform a point by rotating and translating it using the inverse of this isometry.
///
/// This is more efficient than `iso.inverse().transform_point(point)` for one-shot cases.
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 {
self.rotation.inverse() * (point - self.translation)
}
}
impl From<Isometry2d> for Affine2 {
#[inline]
fn from(iso: Isometry2d) -> Self {
Affine2 {
matrix2: iso.rotation.into(),
translation: iso.translation,
}
}
}
impl From<Vec2> for Isometry2d {
#[inline]
fn from(translation: Vec2) -> Self {
Isometry2d::from_translation(translation)
}
}
impl From<Rot2> for Isometry2d {
#[inline]
fn from(rotation: Rot2) -> Self {
Isometry2d::from_rotation(rotation)
}
}
impl Mul for Isometry2d {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
Isometry2d {
rotation: self.rotation * rhs.rotation,
translation: self.rotation * rhs.translation + self.translation,
}
}
}
impl Mul<Vec2> for Isometry2d {
type Output = Vec2;
#[inline]
fn mul(self, rhs: Vec2) -> Self::Output {
self.transform_point(rhs)
}
}
impl Mul<Dir2> for Isometry2d {
type Output = Dir2;
#[inline]
fn mul(self, rhs: Dir2) -> Self::Output {
self.rotation * rhs
}
}
#[cfg(feature = "approx")]
impl AbsDiffEq for Isometry2d {
type Epsilon = <f32 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
f32::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.rotation.abs_diff_eq(&other.rotation, epsilon)
&& self.translation.abs_diff_eq(other.translation, epsilon)
}
}
#[cfg(feature = "approx")]
impl RelativeEq for Isometry2d {
fn default_max_relative() -> Self::Epsilon {
Self::default_epsilon()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.rotation
.relative_eq(&other.rotation, epsilon, max_relative)
&& self
.translation
.relative_eq(&other.translation, epsilon, max_relative)
}
}
#[cfg(feature = "approx")]
impl UlpsEq for Isometry2d {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps)
&& self
.translation
.ulps_eq(&other.translation, epsilon, max_ulps)
}
}
/// An isometry in three dimensions, representing a rotation followed by a translation.
/// This can often be useful for expressing relative positions and transformations from one position to another.
///
/// In particular, this type represents a distance-preserving transformation known as a *rigid motion* or a *direct motion*,
/// and belongs to the special [Euclidean group] SE(3). This includes translation and rotation, but excludes reflection.
///
/// For the two-dimensional version, see [`Isometry2d`].
///
/// [Euclidean group]: https://en.wikipedia.org/wiki/Euclidean_group
///
/// # Example
///
/// Isometries can be created from a given translation and rotation:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let iso = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
/// ```
///
/// Or from separate parts:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let iso1 = Isometry3d::from_translation(Vec3::new(2.0, 1.0, 3.0));
/// let iso2 = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
/// ```
///
/// The isometries can be used to transform points:
///
/// ```
/// # use approx::assert_relative_eq;
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let iso = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
/// let point = Vec3::new(4.0, 4.0, 4.0);
///
/// // These are equivalent
/// let result = iso.transform_point(point);
/// let result = iso * point;
///
/// assert_relative_eq!(result, Vec3::new(-2.0, 5.0, 7.0));
/// ```
///
/// Isometries can also be composed together:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// # let iso = Isometry3d::new(Vec3::new(2.0, 1.0, 3.0), Quat::from_rotation_z(FRAC_PI_2));
/// # let iso1 = Isometry3d::from_translation(Vec3::new(2.0, 1.0, 3.0));
/// # let iso2 = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
/// #
/// assert_eq!(iso1 * iso2, iso);
/// ```
///
/// One common operation is to compute an isometry representing the relative positions of two objects
/// for things like intersection tests. This can be done with an inverse transformation:
///
/// ```
/// # use bevy_math::{Isometry3d, Quat, Vec3};
/// # use std::f32::consts::FRAC_PI_2;
/// #
/// let sphere_iso = Isometry3d::from_translation(Vec3::new(2.0, 1.0, 3.0));
/// let cuboid_iso = Isometry3d::from_rotation(Quat::from_rotation_z(FRAC_PI_2));
///
/// // Compute the relative position and orientation between the two shapes
/// let relative_iso = sphere_iso.inverse() * cuboid_iso;
///
/// // Or alternatively, to skip an extra rotation operation:
/// let relative_iso = sphere_iso.inverse_mul(cuboid_iso);
/// ```
#[derive(Copy, Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Isometry3d {
/// The rotational part of a three-dimensional isometry.
pub rotation: Quat,
/// The translational part of a three-dimensional isometry.
pub translation: Vec3A,
}
impl Isometry3d {
/// The identity isometry which represents the rigid motion of not doing anything.
pub const IDENTITY: Self = Isometry3d {
rotation: Quat::IDENTITY,
translation: Vec3A::ZERO,
};
/// Create a three-dimensional isometry from a rotation and a translation.
#[inline]
pub fn new(translation: impl Into<Vec3A>, rotation: Quat) -> Self {
Isometry3d {
rotation,
translation: translation.into(),
}
}
/// Create a three-dimensional isometry from a rotation.
#[inline]
pub fn from_rotation(rotation: Quat) -> Self {
Isometry3d {
rotation,
translation: Vec3A::ZERO,
}
}
/// Create a three-dimensional isometry from a translation.
#[inline]
pub fn from_translation(translation: impl Into<Vec3A>) -> Self {
Isometry3d {
rotation: Quat::IDENTITY,
translation: translation.into(),
}
}
/// Create a three-dimensional isometry from a translation with the given `x`, `y`, and `z` components.
#[inline]
pub fn from_xyz(x: f32, y: f32, z: f32) -> Self {
Isometry3d {
rotation: Quat::IDENTITY,
translation: Vec3A::new(x, y, z),
}
}
/// The inverse isometry that undoes this one.
#[inline]
pub fn inverse(&self) -> Self {
let inv_rot = self.rotation.inverse();
Isometry3d {
rotation: inv_rot,
translation: inv_rot * -self.translation,
}
}
/// Compute `iso1.inverse() * iso2` in a more efficient way for one-shot cases.
///
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_mul(&self, rhs: Self) -> Self {
let inv_rot = self.rotation.inverse();
let delta_translation = rhs.translation - self.translation;
Self::new(inv_rot * delta_translation, inv_rot * rhs.rotation)
}
/// Transform a point by rotating and translating it using this isometry.
#[inline]
pub fn transform_point(&self, point: impl Into<Vec3A>) -> Vec3A {
self.rotation * point.into() + self.translation
}
/// Transform a point by rotating and translating it using the inverse of this isometry.
///
/// This is more efficient than `iso.inverse().transform_point(point)` for one-shot cases.
/// If the same isometry is used multiple times, it is more efficient to instead compute
/// the inverse once and use that for each transformation.
#[inline]
pub fn inverse_transform_point(&self, point: impl Into<Vec3A>) -> Vec3A {
self.rotation.inverse() * (point.into() - self.translation)
}
}
impl From<Isometry3d> for Affine3 {
#[inline]
fn from(iso: Isometry3d) -> Self {
Affine3 {
matrix3: Mat3::from_quat(iso.rotation),
translation: iso.translation.into(),
}
}
}
impl From<Isometry3d> for Affine3A {
#[inline]
fn from(iso: Isometry3d) -> Self {
Affine3A {
matrix3: Mat3A::from_quat(iso.rotation),
translation: iso.translation,
}
}
}
impl From<Vec3> for Isometry3d {
#[inline]
fn from(translation: Vec3) -> Self {
Isometry3d::from_translation(translation)
}
}
impl From<Vec3A> for Isometry3d {
#[inline]
fn from(translation: Vec3A) -> Self {
Isometry3d::from_translation(translation)
}
}
impl From<Quat> for Isometry3d {
#[inline]
fn from(rotation: Quat) -> Self {
Isometry3d::from_rotation(rotation)
}
}
impl Mul for Isometry3d {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self::Output {
Isometry3d {
rotation: self.rotation * rhs.rotation,
translation: self.rotation * rhs.translation + self.translation,
}
}
}
impl Mul<Vec3A> for Isometry3d {
type Output = Vec3A;
#[inline]
fn mul(self, rhs: Vec3A) -> Self::Output {
self.transform_point(rhs)
}
}
impl Mul<Vec3> for Isometry3d {
type Output = Vec3;
#[inline]
fn mul(self, rhs: Vec3) -> Self::Output {
self.transform_point(rhs).into()
}
}
impl Mul<Dir3> for Isometry3d {
type Output = Dir3;
#[inline]
fn mul(self, rhs: Dir3) -> Self::Output {
self.rotation * rhs
}
}
#[cfg(feature = "approx")]
impl AbsDiffEq for Isometry3d {
type Epsilon = <f32 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
f32::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.rotation.abs_diff_eq(other.rotation, epsilon)
&& self.translation.abs_diff_eq(other.translation, epsilon)
}
}
#[cfg(feature = "approx")]
impl RelativeEq for Isometry3d {
fn default_max_relative() -> Self::Epsilon {
Self::default_epsilon()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.rotation
.relative_eq(&other.rotation, epsilon, max_relative)
&& self
.translation
.relative_eq(&other.translation, epsilon, max_relative)
}
}
#[cfg(feature = "approx")]
impl UlpsEq for Isometry3d {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps)
&& self
.translation
.ulps_eq(&other.translation, epsilon, max_ulps)
}
}
#[cfg(test)]
#[cfg(feature = "approx")]
mod tests {
use super::*;
use crate::{vec2, vec3, vec3a};
use approx::assert_abs_diff_eq;
use core::f32::consts::{FRAC_PI_2, FRAC_PI_3};
#[test]
fn mul_2d() {
let iso1 = Isometry2d::new(vec2(1.0, 0.0), Rot2::FRAC_PI_2);
let iso2 = Isometry2d::new(vec2(0.0, 1.0), Rot2::FRAC_PI_2);
let expected = Isometry2d::new(vec2(0.0, 0.0), Rot2::PI);
assert_abs_diff_eq!(iso1 * iso2, expected);
}
#[test]
fn inverse_mul_2d() {
let iso1 = Isometry2d::new(vec2(1.0, 0.0), Rot2::FRAC_PI_2);
let iso2 = Isometry2d::new(vec2(0.0, 0.0), Rot2::PI);
let expected = Isometry2d::new(vec2(0.0, 1.0), Rot2::FRAC_PI_2);
assert_abs_diff_eq!(iso1.inverse_mul(iso2), expected);
}
#[test]
fn mul_3d() {
let iso1 = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_x(FRAC_PI_2));
let iso2 = Isometry3d::new(vec3(0.0, 1.0, 0.0), Quat::IDENTITY);
let expected = Isometry3d::new(vec3(1.0, 0.0, 1.0), Quat::from_rotation_x(FRAC_PI_2));
assert_abs_diff_eq!(iso1 * iso2, expected);
}
#[test]
fn inverse_mul_3d() {
let iso1 = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_x(FRAC_PI_2));
let iso2 = Isometry3d::new(vec3(1.0, 0.0, 1.0), Quat::from_rotation_x(FRAC_PI_2));
let expected = Isometry3d::new(vec3(0.0, 1.0, 0.0), Quat::IDENTITY);
assert_abs_diff_eq!(iso1.inverse_mul(iso2), expected);
}
#[test]
fn identity_2d() {
let iso = Isometry2d::new(vec2(-1.0, -0.5), Rot2::degrees(75.0));
assert_abs_diff_eq!(Isometry2d::IDENTITY * iso, iso);
assert_abs_diff_eq!(iso * Isometry2d::IDENTITY, iso);
}
#[test]
fn identity_3d() {
let iso = Isometry3d::new(vec3(-1.0, 2.5, 3.3), Quat::from_rotation_z(FRAC_PI_3));
assert_abs_diff_eq!(Isometry3d::IDENTITY * iso, iso);
assert_abs_diff_eq!(iso * Isometry3d::IDENTITY, iso);
}
#[test]
fn inverse_2d() {
let iso = Isometry2d::new(vec2(-1.0, -0.5), Rot2::degrees(75.0));
let inv = iso.inverse();
assert_abs_diff_eq!(iso * inv, Isometry2d::IDENTITY);
assert_abs_diff_eq!(inv * iso, Isometry2d::IDENTITY);
}
#[test]
fn inverse_3d() {
let iso = Isometry3d::new(vec3(-1.0, 2.5, 3.3), Quat::from_rotation_z(FRAC_PI_3));
let inv = iso.inverse();
assert_abs_diff_eq!(iso * inv, Isometry3d::IDENTITY);
assert_abs_diff_eq!(inv * iso, Isometry3d::IDENTITY);
}
#[test]
fn transform_2d() {
let iso = Isometry2d::new(vec2(0.5, -0.5), Rot2::FRAC_PI_2);
let point = vec2(1.0, 1.0);
assert_abs_diff_eq!(vec2(-0.5, 0.5), iso * point);
}
#[test]
fn inverse_transform_2d() {
let iso = Isometry2d::new(vec2(0.5, -0.5), Rot2::FRAC_PI_2);
let point = vec2(-0.5, 0.5);
assert_abs_diff_eq!(vec2(1.0, 1.0), iso.inverse_transform_point(point));
}
#[test]
fn transform_3d() {
let iso = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_y(FRAC_PI_2));
let point = vec3(1.0, 1.0, 1.0);
assert_abs_diff_eq!(vec3(2.0, 1.0, -1.0), iso * point);
}
#[test]
fn inverse_transform_3d() {
let iso = Isometry3d::new(vec3(1.0, 0.0, 0.0), Quat::from_rotation_y(FRAC_PI_2));
let point = vec3(2.0, 1.0, -1.0);
assert_abs_diff_eq!(vec3a(1.0, 1.0, 1.0), iso.inverse_transform_point(point));
}
}

101
vendor/bevy_math/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
#![forbid(unsafe_code)]
#![cfg_attr(
any(docsrs, docsrs_dep),
expect(
internal_features,
reason = "rustdoc_internals is needed for fake_variadic"
)
)]
#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
#![no_std]
//! Provides math types and functionality for the Bevy game engine.
//!
//! The commonly used types are vectors like [`Vec2`] and [`Vec3`],
//! matrices like [`Mat2`], [`Mat3`] and [`Mat4`] and orientation representations
//! like [`Quat`].
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "alloc")]
extern crate alloc;
mod affine3;
mod aspect_ratio;
pub mod bounding;
pub mod common_traits;
mod compass;
pub mod cubic_splines;
mod direction;
mod float_ord;
mod isometry;
pub mod ops;
pub mod primitives;
mod ray;
mod rects;
mod rotation2d;
#[cfg(feature = "curve")]
pub mod curve;
#[cfg(feature = "rand")]
pub mod sampling;
pub use affine3::*;
pub use aspect_ratio::AspectRatio;
pub use common_traits::*;
pub use compass::{CompassOctant, CompassQuadrant};
pub use direction::*;
pub use float_ord::*;
pub use isometry::{Isometry2d, Isometry3d};
pub use ops::FloatPow;
pub use ray::{Ray2d, Ray3d};
pub use rects::*;
pub use rotation2d::Rot2;
#[cfg(feature = "curve")]
pub use curve::Curve;
#[cfg(feature = "rand")]
pub use sampling::{FromRng, ShapeSample};
/// The math prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
pub use crate::{
bvec2, bvec3, bvec3a, bvec4, bvec4a,
cubic_splines::{CubicNurbsError, CubicSegment, RationalSegment},
direction::{Dir2, Dir3, Dir3A},
ivec2, ivec3, ivec4, mat2, mat3, mat3a, mat4, ops,
primitives::*,
quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A,
Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2,
Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
};
#[doc(hidden)]
#[cfg(feature = "curve")]
pub use crate::curve::*;
#[doc(hidden)]
#[cfg(feature = "rand")]
pub use crate::sampling::{FromRng, ShapeSample};
#[cfg(feature = "alloc")]
#[doc(hidden)]
pub use crate::cubic_splines::{
CubicBSpline, CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator, CubicHermite,
CubicNurbs, CyclicCubicGenerator, RationalCurve, RationalGenerator,
};
}
pub use glam::*;

646
vendor/bevy_math/src/ops.rs vendored Normal file
View File

@@ -0,0 +1,646 @@
//! This mod re-exports the correct versions of floating-point operations with
//! unspecified precision in the standard library depending on whether the `libm`
//! crate feature is enabled.
//!
//! All the functions here are named according to their versions in the standard
//! library.
//!
//! It also provides `no_std` compatible alternatives to certain floating-point
//! operations which are not provided in the [`core`] library.
// Note: There are some Rust methods with unspecified precision without a `libm`
// equivalent:
// - `f32::powi` (integer powers)
// - `f32::log` (logarithm with specified base)
// - `f32::abs_sub` (actually unsure if `libm` has this, but don't use it regardless)
//
// Additionally, the following nightly API functions are not presently integrated
// into this, but they would be candidates once standardized:
// - `f32::gamma`
// - `f32::ln_gamma`
#[cfg(all(not(feature = "libm"), feature = "std"))]
#[expect(
clippy::disallowed_methods,
reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule."
)]
mod std_ops {
/// Raises a number to a floating point power.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn powf(x: f32, y: f32) -> f32 {
f32::powf(x, y)
}
/// Returns `e^(self)`, (the exponential function).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp(x: f32) -> f32 {
f32::exp(x)
}
/// Returns `2^(self)`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp2(x: f32) -> f32 {
f32::exp2(x)
}
/// Returns the natural logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln(x: f32) -> f32 {
f32::ln(x)
}
/// Returns the base 2 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log2(x: f32) -> f32 {
f32::log2(x)
}
/// Returns the base 10 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log10(x: f32) -> f32 {
f32::log10(x)
}
/// Returns the cube root of a number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cbrt(x: f32) -> f32 {
f32::cbrt(x)
}
/// Compute the distance between the origin and a point `(x, y)` on the Euclidean plane.
/// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `x.abs()` and `y.abs()`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn hypot(x: f32, y: f32) -> f32 {
f32::hypot(x, y)
}
/// Computes the sine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin(x: f32) -> f32 {
f32::sin(x)
}
/// Computes the cosine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cos(x: f32) -> f32 {
f32::cos(x)
}
/// Computes the tangent of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tan(x: f32) -> f32 {
f32::tan(x)
}
/// Computes the arcsine of a number. Return value is in radians in
/// the range [-pi/2, pi/2] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asin(x: f32) -> f32 {
f32::asin(x)
}
/// Computes the arccosine of a number. Return value is in radians in
/// the range [0, pi] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acos(x: f32) -> f32 {
f32::acos(x)
}
/// Computes the arctangent of a number. Return value is in radians in the
/// range [-pi/2, pi/2];
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan(x: f32) -> f32 {
f32::atan(x)
}
/// Computes the four-quadrant arctangent of `y` and `x` in radians.
///
/// * `x = 0`, `y = 0`: `0`
/// * `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]`
/// * `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]`
/// * `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)`
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan2(y: f32, x: f32) -> f32 {
f32::atan2(y, x)
}
/// Simultaneously computes the sine and cosine of the number, `x`. Returns
/// `(sin(x), cos(x))`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin_cos(x: f32) -> (f32, f32) {
f32::sin_cos(x)
}
/// Returns `e^(self) - 1` in a way that is accurate even if the
/// number is close to zero.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp_m1(x: f32) -> f32 {
f32::exp_m1(x)
}
/// Returns `ln(1+n)` (natural logarithm) more accurately than if
/// the operations were performed separately.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln_1p(x: f32) -> f32 {
f32::ln_1p(x)
}
/// Hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sinh(x: f32) -> f32 {
f32::sinh(x)
}
/// Hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cosh(x: f32) -> f32 {
f32::cosh(x)
}
/// Hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tanh(x: f32) -> f32 {
f32::tanh(x)
}
/// Inverse hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asinh(x: f32) -> f32 {
f32::asinh(x)
}
/// Inverse hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acosh(x: f32) -> f32 {
f32::acosh(x)
}
/// Inverse hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atanh(x: f32) -> f32 {
f32::atanh(x)
}
}
#[cfg(any(feature = "libm", all(feature = "nostd-libm", not(feature = "std"))))]
mod libm_ops {
/// Raises a number to a floating point power.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn powf(x: f32, y: f32) -> f32 {
libm::powf(x, y)
}
/// Returns `e^(self)`, (the exponential function).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp(x: f32) -> f32 {
libm::expf(x)
}
/// Returns `2^(self)`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp2(x: f32) -> f32 {
libm::exp2f(x)
}
/// Returns the natural logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln(x: f32) -> f32 {
// This isn't documented in `libm` but this is actually the base e logarithm.
libm::logf(x)
}
/// Returns the base 2 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log2(x: f32) -> f32 {
libm::log2f(x)
}
/// Returns the base 10 logarithm of the number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn log10(x: f32) -> f32 {
libm::log10f(x)
}
/// Returns the cube root of a number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cbrt(x: f32) -> f32 {
libm::cbrtf(x)
}
/// Compute the distance between the origin and a point `(x, y)` on the Euclidean plane.
///
/// Equivalently, compute the length of the hypotenuse of a right-angle triangle with other sides having length `x.abs()` and `y.abs()`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn hypot(x: f32, y: f32) -> f32 {
libm::hypotf(x, y)
}
/// Computes the sine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin(x: f32) -> f32 {
libm::sinf(x)
}
/// Computes the cosine of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cos(x: f32) -> f32 {
libm::cosf(x)
}
/// Computes the tangent of a number (in radians).
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tan(x: f32) -> f32 {
libm::tanf(x)
}
/// Computes the arcsine of a number. Return value is in radians in
/// the range [-pi/2, pi/2] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asin(x: f32) -> f32 {
libm::asinf(x)
}
/// Computes the arccosine of a number. Return value is in radians in
/// Hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
/// the range [0, pi] or NaN if the number is outside the range
/// [-1, 1].
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acos(x: f32) -> f32 {
libm::acosf(x)
}
/// Computes the arctangent of a number. Return value is in radians in the
/// range [-pi/2, pi/2];
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan(x: f32) -> f32 {
libm::atanf(x)
}
/// Computes the four-quadrant arctangent of `y` and `x` in radians.
///
/// * `x = 0`, `y = 0`: `0`
/// * `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]`
/// * `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]`
/// * `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)`
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atan2(y: f32, x: f32) -> f32 {
libm::atan2f(y, x)
}
/// Simultaneously computes the sine and cosine of the number, `x`. Returns
/// `(sin(x), cos(x))`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sin_cos(x: f32) -> (f32, f32) {
libm::sincosf(x)
}
/// Returns `e^(self) - 1` in a way that is accurate even if the
/// number is close to zero.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn exp_m1(x: f32) -> f32 {
libm::expm1f(x)
}
/// Returns `ln(1+n)` (natural logarithm) more accurately than if
/// the operations were performed separately.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ln_1p(x: f32) -> f32 {
libm::log1pf(x)
}
/// Hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sinh(x: f32) -> f32 {
libm::sinhf(x)
}
/// Hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn cosh(x: f32) -> f32 {
libm::coshf(x)
}
/// Hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn tanh(x: f32) -> f32 {
libm::tanhf(x)
}
/// Inverse hyperbolic sine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn asinh(x: f32) -> f32 {
libm::asinhf(x)
}
/// Inverse hyperbolic cosine function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn acosh(x: f32) -> f32 {
libm::acoshf(x)
}
/// Inverse hyperbolic tangent function.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn atanh(x: f32) -> f32 {
libm::atanhf(x)
}
}
#[cfg(all(any(feature = "libm", feature = "nostd-libm"), not(feature = "std")))]
mod libm_ops_for_no_std {
//! Provides standardized names for [`f32`] operations which may not be
//! supported on `no_std` platforms.
//! On `no_std` platforms, this forwards to the implementations provided
//! by [`libm`].
/// Calculates the least nonnegative remainder of `self (mod rhs)`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn rem_euclid(x: f32, y: f32) -> f32 {
let result = libm::remainderf(x, y);
// libm::remainderf has a range of -y/2 to +y/2
if result < 0. {
result + y
} else {
result
}
}
/// Computes the absolute value of x.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn abs(x: f32) -> f32 {
libm::fabsf(x)
}
/// Returns the square root of a number.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn sqrt(x: f32) -> f32 {
libm::sqrtf(x)
}
/// Returns a number composed of the magnitude of `x` and the sign of `y`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn copysign(x: f32, y: f32) -> f32 {
libm::copysignf(x, y)
}
/// Returns the nearest integer to `x`. If a value is half-way between two integers, round away from `0.0`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn round(x: f32) -> f32 {
libm::roundf(x)
}
/// Returns the largest integer less than or equal to `x`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn floor(x: f32) -> f32 {
libm::floorf(x)
}
/// Returns the smallest integer greater than or equal to `x`.
///
/// Precision is specified when the `libm` feature is enabled.
#[inline(always)]
pub fn ceil(x: f32) -> f32 {
libm::ceilf(x)
}
/// Returns the fractional part of `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn fract(x: f32) -> f32 {
libm::modff(x).0
}
}
#[cfg(feature = "std")]
#[expect(
clippy::disallowed_methods,
reason = "Many of the disallowed methods are disallowed to force code to use the feature-conditional re-exports from this module, but this module itself is exempt from that rule."
)]
mod std_ops_for_no_std {
//! Provides standardized names for [`f32`] operations which may not be
//! supported on `no_std` platforms.
//! On `std` platforms, this forwards directly to the implementations provided
//! by [`std`].
/// Calculates the least nonnegative remainder of `x (mod y)`.
///
/// The result of this operation is guaranteed to be the rounded infinite-precision result.
#[inline(always)]
pub fn rem_euclid(x: f32, y: f32) -> f32 {
f32::rem_euclid(x, y)
}
/// Computes the absolute value of x.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn abs(x: f32) -> f32 {
f32::abs(x)
}
/// Returns the square root of a number.
///
/// The result of this operation is guaranteed to be the rounded infinite-precision result.
/// It is specified by IEEE 754 as `squareRoot` and guaranteed not to change.
#[inline(always)]
pub fn sqrt(x: f32) -> f32 {
f32::sqrt(x)
}
/// Returns a number composed of the magnitude of `x` and the sign of `y`.
///
/// Equal to `x` if the sign of `x` and `y` are the same, otherwise equal to `-x`. If `x` is a
/// `NaN`, then a `NaN` with the sign bit of `y` is returned. Note, however, that conserving the
/// sign bit on `NaN` across arithmetical operations is not generally guaranteed.
#[inline(always)]
pub fn copysign(x: f32, y: f32) -> f32 {
f32::copysign(x, y)
}
/// Returns the nearest integer to `x`. If a value is half-way between two integers, round away from `0.0`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn round(x: f32) -> f32 {
f32::round(x)
}
/// Returns the largest integer less than or equal to `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn floor(x: f32) -> f32 {
f32::floor(x)
}
/// Returns the smallest integer greater than or equal to `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn ceil(x: f32) -> f32 {
f32::ceil(x)
}
/// Returns the fractional part of `x`.
///
/// This function always returns the precise result.
#[inline(always)]
pub fn fract(x: f32) -> f32 {
f32::fract(x)
}
}
#[cfg(any(feature = "libm", all(feature = "nostd-libm", not(feature = "std"))))]
pub use libm_ops::*;
#[cfg(all(not(feature = "libm"), feature = "std"))]
pub use std_ops::*;
#[cfg(feature = "std")]
pub use std_ops_for_no_std::*;
#[cfg(all(any(feature = "libm", feature = "nostd-libm"), not(feature = "std")))]
pub use libm_ops_for_no_std::*;
#[cfg(all(
not(feature = "libm"),
not(feature = "std"),
not(feature = "nostd-libm")
))]
compile_error!("Either the `libm`, `std`, or `nostd-libm` feature must be enabled.");
/// This extension trait covers shortfall in determinacy from the lack of a `libm` counterpart
/// to `f32::powi`. Use this for the common small exponents.
pub trait FloatPow {
/// Squares the f32
fn squared(self) -> Self;
/// Cubes the f32
fn cubed(self) -> Self;
}
impl FloatPow for f32 {
#[inline]
fn squared(self) -> Self {
self * self
}
#[inline]
fn cubed(self) -> Self {
self * self * self
}
}

2486
vendor/bevy_math/src/primitives/dim2.rs vendored Normal file

File diff suppressed because it is too large Load Diff

1875
vendor/bevy_math/src/primitives/dim3.rs vendored Normal file

File diff suppressed because it is too large Load Diff

50
vendor/bevy_math/src/primitives/mod.rs vendored Normal file
View File

@@ -0,0 +1,50 @@
//! This module defines primitive shapes.
//! The origin is (0, 0) for 2D primitives and (0, 0, 0) for 3D primitives,
//! unless stated otherwise.
mod dim2;
pub use dim2::*;
mod dim3;
pub use dim3::*;
mod polygon;
#[cfg(feature = "serialize")]
mod serde;
/// A marker trait for 2D primitives
pub trait Primitive2d {}
/// A marker trait for 3D primitives
pub trait Primitive3d {}
/// The winding order for a set of points
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[doc(alias = "Orientation")]
pub enum WindingOrder {
/// A clockwise winding order
Clockwise,
/// A counterclockwise winding order
#[doc(alias = "AntiClockwise")]
CounterClockwise,
/// An invalid winding order indicating that it could not be computed reliably.
/// This often happens in *degenerate cases* where the points lie on the same line
#[doc(alias("Degenerate", "Collinear"))]
Invalid,
}
/// A trait for getting measurements of 2D shapes
pub trait Measured2d {
/// Get the perimeter of the shape
fn perimeter(&self) -> f32;
/// Get the area of the shape
fn area(&self) -> f32;
}
/// A trait for getting measurements of 3D shapes
pub trait Measured3d {
/// Get the surface area of the shape
fn area(&self) -> f32;
/// Get the volume of the shape
fn volume(&self) -> f32;
}

View File

@@ -0,0 +1,374 @@
#[cfg(feature = "alloc")]
use {
super::{Measured2d, Triangle2d},
alloc::{collections::BTreeMap, vec::Vec},
};
use core::cmp::Ordering;
use crate::Vec2;
#[cfg_attr(
not(feature = "alloc"),
expect(dead_code, reason = "this type is only used with the alloc feature")
)]
#[derive(Debug, Clone, Copy)]
enum Endpoint {
Left,
Right,
}
/// An event in the [`EventQueue`] is either the left or right vertex of an edge of the polygon.
///
/// Events are ordered so that any event `e1` which is to the left of another event `e2` is less than that event.
/// If `e1.position().x == e2.position().x` the events are ordered from bottom to top.
///
/// This is the order expected by the [`SweepLine`].
#[derive(Debug, Clone, Copy)]
#[cfg_attr(
not(feature = "alloc"),
allow(dead_code, reason = "this type is only used with the alloc feature")
)]
struct SweepLineEvent {
segment: Segment,
/// Type of the vertex (left or right)
endpoint: Endpoint,
}
impl SweepLineEvent {
#[cfg_attr(
not(feature = "alloc"),
allow(dead_code, reason = "this type is only used with the alloc feature")
)]
fn position(&self) -> Vec2 {
match self.endpoint {
Endpoint::Left => self.segment.left,
Endpoint::Right => self.segment.right,
}
}
}
impl PartialEq for SweepLineEvent {
fn eq(&self, other: &Self) -> bool {
self.position() == other.position()
}
}
impl Eq for SweepLineEvent {}
impl PartialOrd for SweepLineEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SweepLineEvent {
fn cmp(&self, other: &Self) -> Ordering {
xy_order(self.position(), other.position())
}
}
/// Orders 2D points according to the order expected by the sweep line and event queue from -X to +X and then -Y to Y.
#[cfg_attr(
not(feature = "alloc"),
allow(dead_code, reason = "this type is only used with the alloc feature")
)]
fn xy_order(a: Vec2, b: Vec2) -> Ordering {
a.x.total_cmp(&b.x).then_with(|| a.y.total_cmp(&b.y))
}
/// The event queue holds an ordered list of all events the [`SweepLine`] will encounter when checking the current polygon.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
struct EventQueue {
events: Vec<SweepLineEvent>,
}
#[cfg(feature = "alloc")]
impl EventQueue {
/// Initialize a new `EventQueue` with all events from the polygon represented by `vertices`.
///
/// The events in the event queue will be ordered.
fn new(vertices: &[Vec2]) -> Self {
if vertices.is_empty() {
return Self { events: Vec::new() };
}
let mut events = Vec::with_capacity(vertices.len() * 2);
for i in 0..vertices.len() {
let v1 = vertices[i];
let v2 = *vertices.get(i + 1).unwrap_or(&vertices[0]);
let (left, right) = if xy_order(v1, v2) == Ordering::Less {
(v1, v2)
} else {
(v2, v1)
};
let segment = Segment {
edge_index: i,
left,
right,
};
events.push(SweepLineEvent {
segment,
endpoint: Endpoint::Left,
});
events.push(SweepLineEvent {
segment,
endpoint: Endpoint::Right,
});
}
events.sort();
Self { events }
}
}
/// Represents a segment or rather an edge of the polygon in the [`SweepLine`].
///
/// Segments are ordered from bottom to top based on their left vertices if possible.
/// If their y values are identical, the segments are ordered based on the y values of their right vertices.
#[derive(Debug, Clone, Copy)]
struct Segment {
edge_index: usize,
left: Vec2,
right: Vec2,
}
impl PartialEq for Segment {
fn eq(&self, other: &Self) -> bool {
self.edge_index == other.edge_index
}
}
impl Eq for Segment {}
impl PartialOrd for Segment {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Segment {
fn cmp(&self, other: &Self) -> Ordering {
self.left
.y
.total_cmp(&other.left.y)
.then_with(|| self.right.y.total_cmp(&other.right.y))
}
}
/// Holds information about which segment is above and which is below a given [`Segment`]
#[cfg_attr(
not(feature = "alloc"),
expect(dead_code, reason = "this type is only used with the alloc feature")
)]
#[derive(Debug, Clone, Copy)]
struct SegmentOrder {
above: Option<usize>,
below: Option<usize>,
}
/// A sweep line allows for an efficient search for intersections between [segments](`Segment`).
///
/// It can be thought of as a vertical line sweeping from -X to +X across the polygon that keeps track of the order of the segments
/// the sweep line is intersecting at any given moment.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
struct SweepLine<'a> {
vertices: &'a [Vec2],
tree: BTreeMap<Segment, SegmentOrder>,
}
#[cfg(feature = "alloc")]
impl<'a> SweepLine<'a> {
fn new(vertices: &'a [Vec2]) -> Self {
Self {
vertices,
tree: BTreeMap::new(),
}
}
/// Determine whether the given edges of the polygon intersect.
fn intersects(&self, edge1: Option<usize>, edge2: Option<usize>) -> bool {
let Some(edge1) = edge1 else {
return false;
};
let Some(edge2) = edge2 else {
return false;
};
// All adjacent edges intersect at their shared vertex
// but these intersections do not count so we ignore them here.
// Likewise a segment will always intersect itself / an identical edge.
if edge1 == edge2
|| (edge1 + 1) % self.vertices.len() == edge2
|| (edge2 + 1) % self.vertices.len() == edge1
{
return false;
}
let s11 = self.vertices[edge1];
let s12 = *self.vertices.get(edge1 + 1).unwrap_or(&self.vertices[0]);
let s21 = self.vertices[edge2];
let s22 = *self.vertices.get(edge2 + 1).unwrap_or(&self.vertices[0]);
// When both points of the second edge are on the same side of the first edge, no intersection is possible.
if point_side(s11, s12, s21) * point_side(s11, s12, s22) > 0.0 {
return false;
}
if point_side(s21, s22, s11) * point_side(s21, s22, s12) > 0.0 {
return false;
}
true
}
/// Add a new segment to the sweep line
fn add(&mut self, s: Segment) -> SegmentOrder {
let above = if let Some((next_s, next_ord)) = self.tree.range_mut(s..).next() {
next_ord.below.replace(s.edge_index);
Some(next_s.edge_index)
} else {
None
};
let below = if let Some((prev_s, prev_ord)) = self.tree.range_mut(..s).next_back() {
prev_ord.above.replace(s.edge_index);
Some(prev_s.edge_index)
} else {
None
};
let s_ord = SegmentOrder { above, below };
self.tree.insert(s, s_ord);
s_ord
}
/// Get the segment order for the given segment.
///
/// If `s` has not been added to the [`SweepLine`] `None` will be returned.
fn find(&self, s: &Segment) -> Option<&SegmentOrder> {
self.tree.get(s)
}
/// Remove `s` from the [`SweepLine`].
fn remove(&mut self, s: &Segment) {
let Some(s_ord) = self.tree.get(s).copied() else {
return;
};
if let Some((_, above_ord)) = self.tree.range_mut(s..).next() {
above_ord.below = s_ord.below;
}
if let Some((_, below_ord)) = self.tree.range_mut(..s).next_back() {
below_ord.above = s_ord.above;
}
self.tree.remove(s);
}
}
/// Test what side of the line through `p1` and `p2` `q` is.
///
/// The result will be `0` if the `q` is on the segment, negative for one side and positive for the other.
#[cfg_attr(
not(feature = "alloc"),
expect(
dead_code,
reason = "this function is only used with the alloc feature"
)
)]
#[inline(always)]
fn point_side(p1: Vec2, p2: Vec2, q: Vec2) -> f32 {
(p2.x - p1.x) * (q.y - p1.y) - (q.x - p1.x) * (p2.y - p1.y)
}
/// Tests whether the `vertices` describe a simple polygon.
/// The last vertex must not be equal to the first vertex.
///
/// A polygon is simple if it is not self intersecting and not self tangent.
/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge.
///
/// Any 'polygon' with less than three vertices is simple.
///
/// The algorithm used is the Shamos-Hoey algorithm, a version of the Bentley-Ottman algorithm adapted to only detect whether any intersections exist.
/// This function will run in O(n * log n)
#[cfg(feature = "alloc")]
pub fn is_polygon_simple(vertices: &[Vec2]) -> bool {
if vertices.len() < 3 {
return true;
}
if vertices.len() == 3 {
return Triangle2d::new(vertices[0], vertices[1], vertices[2]).area() > 0.0;
}
let event_queue = EventQueue::new(vertices);
let mut sweep_line = SweepLine::new(vertices);
for e in event_queue.events {
match e.endpoint {
Endpoint::Left => {
let s = sweep_line.add(e.segment);
if sweep_line.intersects(Some(e.segment.edge_index), s.above)
|| sweep_line.intersects(Some(e.segment.edge_index), s.below)
{
return false;
}
}
Endpoint::Right => {
if let Some(s) = sweep_line.find(&e.segment) {
if sweep_line.intersects(s.above, s.below) {
return false;
}
sweep_line.remove(&e.segment);
}
}
}
}
true
}
#[cfg(test)]
mod tests {
use crate::{primitives::polygon::is_polygon_simple, Vec2};
#[test]
fn complex_polygon() {
// A square with one side punching through the opposite side.
let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(2.0, 0.5)];
assert!(!is_polygon_simple(&verts));
// A square with a vertex from one side touching the opposite side.
let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(1.0, 0.5)];
assert!(!is_polygon_simple(&verts));
// A square with one side touching the opposite side.
let verts = [
Vec2::ZERO,
Vec2::X,
Vec2::ONE,
Vec2::Y,
Vec2::new(1.0, 0.6),
Vec2::new(1.0, 0.4),
];
assert!(!is_polygon_simple(&verts));
// Four points lying on a line
let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::new(5., 3.), Vec2::NEG_X];
assert!(!is_polygon_simple(&verts));
// Three points lying on a line
let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::NEG_X];
assert!(!is_polygon_simple(&verts));
// Two identical points and one other point
let verts = [Vec2::ONE, Vec2::ONE, Vec2::NEG_X];
assert!(!is_polygon_simple(&verts));
// Two triangles with one shared side
let verts = [Vec2::ZERO, Vec2::X, Vec2::Y, Vec2::ONE, Vec2::X, Vec2::Y];
assert!(!is_polygon_simple(&verts));
}
#[test]
fn simple_polygon() {
// A square
let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y];
assert!(is_polygon_simple(&verts));
let verts = [];
assert!(is_polygon_simple(&verts));
}
}

View File

@@ -0,0 +1,67 @@
//! This module defines serialization/deserialization for const generic arrays.
//! Unlike serde's default behavior, it supports arbitrarily large arrays.
//! The code is based on this github comment:
//! <https://github.com/serde-rs/serde/issues/1937#issuecomment-812137971>
pub(crate) mod array {
use core::marker::PhantomData;
use serde::{
de::{SeqAccess, Visitor},
ser::SerializeTuple,
Deserialize, Deserializer, Serialize, Serializer,
};
pub fn serialize<S: Serializer, T: Serialize, const N: usize>(
data: &[T; N],
ser: S,
) -> Result<S::Ok, S::Error> {
let mut s = ser.serialize_tuple(N)?;
for item in data {
s.serialize_element(item)?;
}
s.end()
}
struct GenericArrayVisitor<T, const N: usize>(PhantomData<T>);
impl<'de, T, const N: usize> Visitor<'de> for GenericArrayVisitor<T, N>
where
T: Deserialize<'de>,
{
type Value = [T; N];
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_fmt(format_args!("an array of length {}", N))
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut data = [const { Option::<T>::None }; N];
for element in data.iter_mut() {
match (seq.next_element())? {
Some(val) => *element = Some(val),
None => return Err(serde::de::Error::invalid_length(N, &self)),
}
}
let data = data.map(|value| match value {
Some(value) => value,
None => unreachable!(),
});
Ok(data)
}
}
pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result<[T; N], D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
deserializer.deserialize_tuple(N, GenericArrayVisitor::<T, N>(PhantomData))
}
}

190
vendor/bevy_math/src/ray.rs vendored Normal file
View File

@@ -0,0 +1,190 @@
use crate::{
ops,
primitives::{InfinitePlane3d, Plane2d},
Dir2, Dir3, Vec2, Vec3,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// An infinite half-line starting at `origin` and going in `direction` in 2D space.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub struct Ray2d {
/// The origin of the ray.
pub origin: Vec2,
/// The direction of the ray.
pub direction: Dir2,
}
impl Ray2d {
/// Create a new `Ray2d` from a given origin and direction
#[inline]
pub const fn new(origin: Vec2, direction: Dir2) -> Self {
Self { origin, direction }
}
/// Get a point at a given distance along the ray
#[inline]
pub fn get_point(&self, distance: f32) -> Vec2 {
self.origin + *self.direction * distance
}
/// Get the distance to a plane if the ray intersects it
#[inline]
pub fn intersect_plane(&self, plane_origin: Vec2, plane: Plane2d) -> Option<f32> {
let denominator = plane.normal.dot(*self.direction);
if ops::abs(denominator) > f32::EPSILON {
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
if distance > f32::EPSILON {
return Some(distance);
}
}
None
}
}
/// An infinite half-line starting at `origin` and going in `direction` in 3D space.
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Deserialize, Serialize)
)]
pub struct Ray3d {
/// The origin of the ray.
pub origin: Vec3,
/// The direction of the ray.
pub direction: Dir3,
}
impl Ray3d {
/// Create a new `Ray3d` from a given origin and direction
#[inline]
pub const fn new(origin: Vec3, direction: Dir3) -> Self {
Self { origin, direction }
}
/// Get a point at a given distance along the ray
#[inline]
pub fn get_point(&self, distance: f32) -> Vec3 {
self.origin + *self.direction * distance
}
/// Get the distance to a plane if the ray intersects it
#[inline]
pub fn intersect_plane(&self, plane_origin: Vec3, plane: InfinitePlane3d) -> Option<f32> {
let denominator = plane.normal.dot(*self.direction);
if ops::abs(denominator) > f32::EPSILON {
let distance = (plane_origin - self.origin).dot(*plane.normal) / denominator;
if distance > f32::EPSILON {
return Some(distance);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intersect_plane_2d() {
let ray = Ray2d::new(Vec2::ZERO, Dir2::Y);
// Orthogonal, and test that an inverse plane_normal has the same result
assert_eq!(
ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::Y)),
Some(1.0)
);
assert_eq!(
ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::NEG_Y)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::Y))
.is_none());
assert!(ray
.intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::NEG_Y))
.is_none());
// Diagonal
assert_eq!(
ray.intersect_plane(Vec2::Y, Plane2d::new(Vec2::ONE)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec2::NEG_Y, Plane2d::new(Vec2::ONE))
.is_none());
// Parallel
assert!(ray
.intersect_plane(Vec2::X, Plane2d::new(Vec2::X))
.is_none());
// Parallel with simulated rounding error
assert!(ray
.intersect_plane(Vec2::X, Plane2d::new(Vec2::X + Vec2::Y * f32::EPSILON))
.is_none());
}
#[test]
fn intersect_plane_3d() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::Z);
// Orthogonal, and test that an inverse plane_normal has the same result
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::Z)),
Some(1.0)
);
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::NEG_Z)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::Z))
.is_none());
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::NEG_Z))
.is_none());
// Diagonal
assert_eq!(
ray.intersect_plane(Vec3::Z, InfinitePlane3d::new(Vec3::ONE)),
Some(1.0)
);
assert!(ray
.intersect_plane(Vec3::NEG_Z, InfinitePlane3d::new(Vec3::ONE))
.is_none());
// Parallel
assert!(ray
.intersect_plane(Vec3::X, InfinitePlane3d::new(Vec3::X))
.is_none());
// Parallel with simulated rounding error
assert!(ray
.intersect_plane(
Vec3::X,
InfinitePlane3d::new(Vec3::X + Vec3::Z * f32::EPSILON)
)
.is_none());
}
}

481
vendor/bevy_math/src/rects/irect.rs vendored Normal file
View File

@@ -0,0 +1,481 @@
use crate::{IVec2, Rect, URect};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A rectangle defined by two opposite corners.
///
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
/// stored in `IRect::min` and `IRect::max`, respectively. The minimum/maximum invariant
/// must be upheld by the user when directly assigning the fields, otherwise some methods
/// produce invalid results. It is generally recommended to use one of the constructor
/// methods instead, which will ensure this invariant is met, unless you already have
/// the minimum and maximum corners.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct IRect {
/// The minimum corner point of the rect.
pub min: IVec2,
/// The maximum corner point of the rect.
pub max: IVec2,
}
impl IRect {
/// An empty `IRect`, represented by maximum and minimum corner points
/// with `max == IVec2::MIN` and `min == IVec2::MAX`, so the
/// rect has an extremely large negative size.
/// This is useful, because when taking a union B of a non-empty `IRect` A and
/// this empty `IRect`, B will simply equal A.
pub const EMPTY: Self = Self {
max: IVec2::MIN,
min: IVec2::MAX,
};
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 4, 10, 6); // w=10 h=2
/// let r = IRect::new(2, 3, 5, -1); // w=3 h=4
/// ```
#[inline]
pub fn new(x0: i32, y0: i32, x1: i32, y1: i32) -> Self {
Self::from_corners(IVec2::new(x0, y0), IVec2::new(x1, y1))
}
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// // Unit rect from [0,0] to [1,1]
/// let r = IRect::from_corners(IVec2::ZERO, IVec2::ONE); // w=1 h=1
/// // Same; the points do not need to be ordered
/// let r = IRect::from_corners(IVec2::ONE, IVec2::ZERO); // w=1 h=1
/// ```
#[inline]
pub fn from_corners(p0: IVec2, p1: IVec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
/// Create a new rectangle from its center and size.
///
/// # Rounding Behavior
///
/// If the size contains odd numbers they will be rounded down to the nearest whole number.
///
/// # Panics
///
/// This method panics if any of the components of the size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::from_center_size(IVec2::ZERO, IVec2::new(3, 2)); // w=2 h=2
/// assert_eq!(r.min, IVec2::splat(-1));
/// assert_eq!(r.max, IVec2::splat(1));
/// ```
#[inline]
pub fn from_center_size(origin: IVec2, size: IVec2) -> Self {
debug_assert!(size.cmpge(IVec2::ZERO).all(), "IRect size must be positive");
let half_size = size / 2;
Self::from_center_half_size(origin, half_size)
}
/// Create a new rectangle from its center and half-size.
///
/// # Panics
///
/// This method panics if any of the components of the half-size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::from_center_half_size(IVec2::ZERO, IVec2::ONE); // w=2 h=2
/// assert_eq!(r.min, IVec2::splat(-1));
/// assert_eq!(r.max, IVec2::splat(1));
/// ```
#[inline]
pub fn from_center_half_size(origin: IVec2, half_size: IVec2) -> Self {
assert!(
half_size.cmpge(IVec2::ZERO).all(),
"IRect half_size must be positive"
);
Self {
min: origin - half_size,
max: origin + half_size,
}
}
/// Check if the rectangle is empty.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::from_corners(IVec2::ZERO, IVec2::new(0, 1)); // w=0 h=1
/// assert!(r.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
/// Rectangle width (max.x - min.x).
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.width(), 5);
/// ```
#[inline]
pub fn width(&self) -> i32 {
self.max.x - self.min.x
}
/// Rectangle height (max.y - min.y).
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.height(), 1);
/// ```
#[inline]
pub fn height(&self) -> i32 {
self.max.y - self.min.y
}
/// Rectangle size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.size(), IVec2::new(5, 1));
/// ```
#[inline]
pub fn size(&self) -> IVec2 {
self.max - self.min
}
/// Rectangle half-size.
///
/// # Rounding Behavior
///
/// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 4, 3); // w=4 h=3
/// assert_eq!(r.half_size(), IVec2::new(2, 1));
/// ```
#[inline]
pub fn half_size(&self) -> IVec2 {
self.size() / 2
}
/// The center point of the rectangle.
///
/// # Rounding Behavior
///
/// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 2); // w=5 h=2
/// assert_eq!(r.center(), IVec2::new(2, 1));
/// ```
#[inline]
pub fn center(&self) -> IVec2 {
(self.min + self.max) / 2
}
/// Check if a point lies within this rectangle, inclusive of its edges.
///
/// # Examples
///
/// ```
/// # use bevy_math::IRect;
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// assert!(r.contains(r.center()));
/// assert!(r.contains(r.min));
/// assert!(r.contains(r.max));
/// ```
#[inline]
pub fn contains(&self, point: IVec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
/// Build a new rectangle formed of the union of this rectangle and another rectangle.
///
/// The union is the smallest rectangle enclosing both rectangles.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4
/// let r = r1.union(r2);
/// assert_eq!(r.min, IVec2::new(0, -1));
/// assert_eq!(r.max, IVec2::new(5, 3));
/// ```
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
/// Build a new rectangle formed of the union of this rectangle and a point.
///
/// The union is the smallest rectangle enclosing both the rectangle and the point. If the
/// point is already inside the rectangle, this method returns a copy of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let u = r.union_point(IVec2::new(3, 6));
/// assert_eq!(u.min, IVec2::ZERO);
/// assert_eq!(u.max, IVec2::new(5, 6));
/// ```
#[inline]
pub fn union_point(&self, other: IVec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
/// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
///
/// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
/// is empty, this method returns an empty rectangle ([`IRect::is_empty()`] returns `true`), but
/// the actual values of [`IRect::min`] and [`IRect::max`] are implementation-dependent.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r1 = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = IRect::new(1, -1, 3, 3); // w=2 h=4
/// let r = r1.intersect(r2);
/// assert_eq!(r.min, IVec2::new(1, 0));
/// assert_eq!(r.max, IVec2::new(3, 1));
/// ```
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Create a new rectangle by expanding it evenly on all sides.
///
/// A positive expansion value produces a larger rectangle,
/// while a negative expansion value produces a smaller rectangle.
/// If this would result in zero or negative width or height, [`IRect::EMPTY`] is returned instead.
///
/// # Examples
///
/// ```
/// # use bevy_math::{IRect, IVec2};
/// let r = IRect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = r.inflate(3); // w=11 h=7
/// assert_eq!(r2.min, IVec2::splat(-3));
/// assert_eq!(r2.max, IVec2::new(8, 4));
///
/// let r = IRect::new(0, -1, 4, 3); // w=4 h=4
/// let r2 = r.inflate(-1); // w=2 h=2
/// assert_eq!(r2.min, IVec2::new(1, 0));
/// assert_eq!(r2.max, IVec2::new(3, 2));
/// ```
#[inline]
pub fn inflate(&self, expansion: i32) -> Self {
let mut r = Self {
min: self.min - expansion,
max: self.max + expansion,
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Returns self as [`Rect`] (f32)
#[inline]
pub fn as_rect(&self) -> Rect {
Rect::from_corners(self.min.as_vec2(), self.max.as_vec2())
}
/// Returns self as [`URect`] (u32)
#[inline]
pub fn as_urect(&self) -> URect {
URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn well_formed() {
let r = IRect::from_center_size(IVec2::new(3, -5), IVec2::new(8, 12));
assert_eq!(r.min, IVec2::new(-1, -11));
assert_eq!(r.max, IVec2::new(7, 1));
assert_eq!(r.center(), IVec2::new(3, -5));
assert_eq!(r.width().abs(), 8);
assert_eq!(r.height().abs(), 12);
assert_eq!(r.size(), IVec2::new(8, 12));
assert_eq!(r.half_size(), IVec2::new(4, 6));
assert!(r.contains(IVec2::new(3, -5)));
assert!(r.contains(IVec2::new(-1, -10)));
assert!(r.contains(IVec2::new(-1, 0)));
assert!(r.contains(IVec2::new(7, -10)));
assert!(r.contains(IVec2::new(7, 0)));
assert!(!r.contains(IVec2::new(50, -5)));
}
#[test]
fn rect_union() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2, -2] - [2, 2]
// overlapping
let r2 = IRect {
min: IVec2::new(1, 1),
max: IVec2::new(3, 3),
};
let u = r.union(r2);
assert_eq!(u.min, IVec2::new(-2, -2));
assert_eq!(u.max, IVec2::new(3, 3));
// disjoint
let r2 = IRect {
min: IVec2::new(1, 4),
max: IVec2::new(4, 6),
};
let u = r.union(r2);
assert_eq!(u.min, IVec2::new(-2, -2));
assert_eq!(u.max, IVec2::new(4, 6));
// included
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2));
let u = r.union(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// including
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(6));
let u = r.union(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.min, r2.min);
}
#[test]
fn rect_union_pt() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2]
// inside
let v = IVec2::new(1, -1);
let u = r.union_point(v);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// outside
let v = IVec2::new(10, -3);
let u = r.union_point(v);
assert_eq!(u.min, IVec2::new(-2, -3));
assert_eq!(u.max, IVec2::new(10, 2));
}
#[test]
fn rect_intersect() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(8)); // [-4,-4] - [4,4]
// overlapping
let r2 = IRect {
min: IVec2::new(2, 2),
max: IVec2::new(6, 6),
};
let u = r.intersect(r2);
assert_eq!(u.min, IVec2::new(2, 2));
assert_eq!(u.max, IVec2::new(4, 4));
// disjoint
let r2 = IRect {
min: IVec2::new(-8, -2),
max: IVec2::new(-6, 2),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert_eq!(u.width(), 0);
// included
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(2));
let u = r.intersect(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.max, r2.max);
// including
let r2 = IRect::from_center_size(IVec2::ZERO, IVec2::splat(10));
let u = r.intersect(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
}
#[test]
fn rect_inflate() {
let r = IRect::from_center_size(IVec2::ZERO, IVec2::splat(4)); // [-2,-2] - [2,2]
let r2 = r.inflate(2);
assert_eq!(r2.min, IVec2::new(-4, -4));
assert_eq!(r2.max, IVec2::new(4, 4));
}
}

7
vendor/bevy_math/src/rects/mod.rs vendored Normal file
View File

@@ -0,0 +1,7 @@
mod irect;
mod rect;
mod urect;
pub use irect::IRect;
pub use rect::Rect;
pub use urect::URect;

495
vendor/bevy_math/src/rects/rect.rs vendored Normal file
View File

@@ -0,0 +1,495 @@
use crate::{IRect, URect, Vec2};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A rectangle defined by two opposite corners.
///
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
/// stored in `Rect::min` and `Rect::max`, respectively. The minimum/maximum invariant
/// must be upheld by the user when directly assigning the fields, otherwise some methods
/// produce invalid results. It is generally recommended to use one of the constructor
/// methods instead, which will ensure this invariant is met, unless you already have
/// the minimum and maximum corners.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Rect {
/// The minimum corner point of the rect.
pub min: Vec2,
/// The maximum corner point of the rect.
pub max: Vec2,
}
impl Rect {
/// An empty `Rect`, represented by maximum and minimum corner points
/// at `Vec2::NEG_INFINITY` and `Vec2::INFINITY`, respectively.
/// This is so the `Rect` has a infinitely negative size.
/// This is useful, because when taking a union B of a non-empty `Rect` A and
/// this empty `Rect`, B will simply equal A.
pub const EMPTY: Self = Self {
max: Vec2::NEG_INFINITY,
min: Vec2::INFINITY,
};
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 4., 10., 6.); // w=10 h=2
/// let r = Rect::new(2., 3., 5., -1.); // w=3 h=4
/// ```
#[inline]
pub fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
Self::from_corners(Vec2::new(x0, y0), Vec2::new(x1, y1))
}
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// // Unit rect from [0,0] to [1,1]
/// let r = Rect::from_corners(Vec2::ZERO, Vec2::ONE); // w=1 h=1
/// // Same; the points do not need to be ordered
/// let r = Rect::from_corners(Vec2::ONE, Vec2::ZERO); // w=1 h=1
/// ```
#[inline]
pub fn from_corners(p0: Vec2, p1: Vec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
/// Create a new rectangle from its center and size.
///
/// # Panics
///
/// This method panics if any of the components of the size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // w=1 h=1
/// assert!(r.min.abs_diff_eq(Vec2::splat(-0.5), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::splat(0.5), 1e-5));
/// ```
#[inline]
pub fn from_center_size(origin: Vec2, size: Vec2) -> Self {
assert!(size.cmpge(Vec2::ZERO).all(), "Rect size must be positive");
let half_size = size / 2.;
Self::from_center_half_size(origin, half_size)
}
/// Create a new rectangle from its center and half-size.
///
/// # Panics
///
/// This method panics if any of the components of the half-size is negative.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::from_center_half_size(Vec2::ZERO, Vec2::ONE); // w=2 h=2
/// assert!(r.min.abs_diff_eq(Vec2::splat(-1.), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::splat(1.), 1e-5));
/// ```
#[inline]
pub fn from_center_half_size(origin: Vec2, half_size: Vec2) -> Self {
assert!(
half_size.cmpge(Vec2::ZERO).all(),
"Rect half_size must be positive"
);
Self {
min: origin - half_size,
max: origin + half_size,
}
}
/// Check if the rectangle is empty.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::from_corners(Vec2::ZERO, Vec2::new(0., 1.)); // w=0 h=1
/// assert!(r.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
/// Rectangle width (max.x - min.x).
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!((r.width() - 5.).abs() <= 1e-5);
/// ```
#[inline]
pub fn width(&self) -> f32 {
self.max.x - self.min.x
}
/// Rectangle height (max.y - min.y).
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!((r.height() - 1.).abs() <= 1e-5);
/// ```
#[inline]
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}
/// Rectangle size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.size().abs_diff_eq(Vec2::new(5., 1.), 1e-5));
/// ```
#[inline]
pub fn size(&self) -> Vec2 {
self.max - self.min
}
/// Rectangle half-size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.half_size().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
/// ```
#[inline]
pub fn half_size(&self) -> Vec2 {
self.size() * 0.5
}
/// The center point of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.center().abs_diff_eq(Vec2::new(2.5, 0.5), 1e-5));
/// ```
#[inline]
pub fn center(&self) -> Vec2 {
(self.min + self.max) * 0.5
}
/// Check if a point lies within this rectangle, inclusive of its edges.
///
/// # Examples
///
/// ```
/// # use bevy_math::Rect;
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// assert!(r.contains(r.center()));
/// assert!(r.contains(r.min));
/// assert!(r.contains(r.max));
/// ```
#[inline]
pub fn contains(&self, point: Vec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
/// Build a new rectangle formed of the union of this rectangle and another rectangle.
///
/// The union is the smallest rectangle enclosing both rectangles.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
/// let r = r1.union(r2);
/// assert!(r.min.abs_diff_eq(Vec2::new(0., -1.), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::new(5., 3.), 1e-5));
/// ```
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
/// Build a new rectangle formed of the union of this rectangle and a point.
///
/// The union is the smallest rectangle enclosing both the rectangle and the point. If the
/// point is already inside the rectangle, this method returns a copy of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let u = r.union_point(Vec2::new(3., 6.));
/// assert!(u.min.abs_diff_eq(Vec2::ZERO, 1e-5));
/// assert!(u.max.abs_diff_eq(Vec2::new(5., 6.), 1e-5));
/// ```
#[inline]
pub fn union_point(&self, other: Vec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
/// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
///
/// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
/// is empty, this method returns an empty rectangle ([`Rect::is_empty()`] returns `true`), but
/// the actual values of [`Rect::min`] and [`Rect::max`] are implementation-dependent.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r1 = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let r2 = Rect::new(1., -1., 3., 3.); // w=2 h=4
/// let r = r1.intersect(r2);
/// assert!(r.min.abs_diff_eq(Vec2::new(1., 0.), 1e-5));
/// assert!(r.max.abs_diff_eq(Vec2::new(3., 1.), 1e-5));
/// ```
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Create a new rectangle by expanding it evenly on all sides.
///
/// A positive expansion value produces a larger rectangle,
/// while a negative expansion value produces a smaller rectangle.
/// If this would result in zero or negative width or height, [`Rect::EMPTY`] is returned instead.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(0., 0., 5., 1.); // w=5 h=1
/// let r2 = r.inflate(3.); // w=11 h=7
/// assert!(r2.min.abs_diff_eq(Vec2::splat(-3.), 1e-5));
/// assert!(r2.max.abs_diff_eq(Vec2::new(8., 4.), 1e-5));
///
/// let r = Rect::new(0., -1., 6., 7.); // w=6 h=8
/// let r2 = r.inflate(-2.); // w=11 h=7
/// assert!(r2.min.abs_diff_eq(Vec2::new(2., 1.), 1e-5));
/// assert!(r2.max.abs_diff_eq(Vec2::new(4., 5.), 1e-5));
/// ```
#[inline]
pub fn inflate(&self, expansion: f32) -> Self {
let mut r = Self {
min: self.min - expansion,
max: self.max + expansion,
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Build a new rectangle from this one with its coordinates expressed
/// relative to `other` in a normalized ([0..1] x [0..1]) coordinate system.
///
/// # Examples
///
/// ```
/// # use bevy_math::{Rect, Vec2};
/// let r = Rect::new(2., 3., 4., 6.);
/// let s = Rect::new(0., 0., 10., 10.);
/// let n = r.normalize(s);
///
/// assert_eq!(n.min.x, 0.2);
/// assert_eq!(n.min.y, 0.3);
/// assert_eq!(n.max.x, 0.4);
/// assert_eq!(n.max.y, 0.6);
/// ```
pub fn normalize(&self, other: Self) -> Self {
let outer_size = other.size();
Self {
min: (self.min - other.min) / outer_size,
max: (self.max - other.min) / outer_size,
}
}
/// Returns self as [`IRect`] (i32)
#[inline]
pub fn as_irect(&self) -> IRect {
IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
}
/// Returns self as [`URect`] (u32)
#[inline]
pub fn as_urect(&self) -> URect {
URect::from_corners(self.min.as_uvec2(), self.max.as_uvec2())
}
}
#[cfg(test)]
mod tests {
use crate::ops;
use super::*;
#[test]
fn well_formed() {
let r = Rect::from_center_size(Vec2::new(3., -5.), Vec2::new(8., 11.));
assert!(r.min.abs_diff_eq(Vec2::new(-1., -10.5), 1e-5));
assert!(r.max.abs_diff_eq(Vec2::new(7., 0.5), 1e-5));
assert!(r.center().abs_diff_eq(Vec2::new(3., -5.), 1e-5));
assert!(ops::abs(r.width() - 8.) <= 1e-5);
assert!(ops::abs(r.height() - 11.) <= 1e-5);
assert!(r.size().abs_diff_eq(Vec2::new(8., 11.), 1e-5));
assert!(r.half_size().abs_diff_eq(Vec2::new(4., 5.5), 1e-5));
assert!(r.contains(Vec2::new(3., -5.)));
assert!(r.contains(Vec2::new(-1., -10.5)));
assert!(r.contains(Vec2::new(-1., 0.5)));
assert!(r.contains(Vec2::new(7., -10.5)));
assert!(r.contains(Vec2::new(7., 0.5)));
assert!(!r.contains(Vec2::new(50., -5.)));
}
#[test]
fn rect_union() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
// overlapping
let r2 = Rect {
min: Vec2::new(-0.8, 0.3),
max: Vec2::new(0.1, 0.7),
};
let u = r.union(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-0.8, -0.5), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.7), 1e-5));
// disjoint
let r2 = Rect {
min: Vec2::new(-1.8, -0.5),
max: Vec2::new(-1.5, 0.3),
};
let u = r.union(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-1.8, -0.5), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.5, 0.5), 1e-5));
// included
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
let u = r.union(r2);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
// including
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
let u = r.union(r2);
assert!(u.min.abs_diff_eq(r2.min, 1e-5));
assert!(u.max.abs_diff_eq(r2.max, 1e-5));
}
#[test]
fn rect_union_pt() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
// inside
let v = Vec2::new(0.3, -0.2);
let u = r.union_point(v);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
// outside
let v = Vec2::new(10., -3.);
let u = r.union_point(v);
assert!(u.min.abs_diff_eq(Vec2::new(-0.5, -3.), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(10., 0.5), 1e-5));
}
#[test]
fn rect_intersect() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
// overlapping
let r2 = Rect {
min: Vec2::new(-0.8, 0.3),
max: Vec2::new(0.1, 0.7),
};
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(Vec2::new(-0.5, 0.3), 1e-5));
assert!(u.max.abs_diff_eq(Vec2::new(0.1, 0.5), 1e-5));
// disjoint
let r2 = Rect {
min: Vec2::new(-1.8, -0.5),
max: Vec2::new(-1.5, 0.3),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert!(u.width() <= 1e-5);
// included
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(0.5));
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(r2.min, 1e-5));
assert!(u.max.abs_diff_eq(r2.max, 1e-5));
// including
let r2 = Rect::from_center_size(Vec2::ZERO, Vec2::splat(1.5));
let u = r.intersect(r2);
assert!(u.min.abs_diff_eq(r.min, 1e-5));
assert!(u.max.abs_diff_eq(r.max, 1e-5));
}
#[test]
fn rect_inflate() {
let r = Rect::from_center_size(Vec2::ZERO, Vec2::ONE); // [-0.5,-0.5] - [0.5,0.5]
let r2 = r.inflate(0.3);
assert!(r2.min.abs_diff_eq(Vec2::new(-0.8, -0.8), 1e-5));
assert!(r2.max.abs_diff_eq(Vec2::new(0.8, 0.8), 1e-5));
}
}

484
vendor/bevy_math/src/rects/urect.rs vendored Normal file
View File

@@ -0,0 +1,484 @@
use crate::{IRect, Rect, UVec2};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A rectangle defined by two opposite corners.
///
/// The rectangle is axis aligned, and defined by its minimum and maximum coordinates,
/// stored in `URect::min` and `URect::max`, respectively. The minimum/maximum invariant
/// must be upheld by the user when directly assigning the fields, otherwise some methods
/// produce invalid results. It is generally recommended to use one of the constructor
/// methods instead, which will ensure this invariant is met, unless you already have
/// the minimum and maximum corners.
#[repr(C)]
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct URect {
/// The minimum corner point of the rect.
pub min: UVec2,
/// The maximum corner point of the rect.
pub max: UVec2,
}
impl URect {
/// An empty `URect`, represented by maximum and minimum corner points
/// with `max == UVec2::MIN` and `min == UVec2::MAX`, so the
/// rect has an extremely large negative size.
/// This is useful, because when taking a union B of a non-empty `URect` A and
/// this empty `URect`, B will simply equal A.
pub const EMPTY: Self = Self {
max: UVec2::MIN,
min: UVec2::MAX,
};
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 4, 10, 6); // w=10 h=2
/// let r = URect::new(2, 4, 5, 0); // w=3 h=4
/// ```
#[inline]
pub fn new(x0: u32, y0: u32, x1: u32, y1: u32) -> Self {
Self::from_corners(UVec2::new(x0, y0), UVec2::new(x1, y1))
}
/// Create a new rectangle from two corner points.
///
/// The two points do not need to be the minimum and/or maximum corners.
/// They only need to be two opposite corners.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// // Unit rect from [0,0] to [1,1]
/// let r = URect::from_corners(UVec2::ZERO, UVec2::ONE); // w=1 h=1
/// // Same; the points do not need to be ordered
/// let r = URect::from_corners(UVec2::ONE, UVec2::ZERO); // w=1 h=1
/// ```
#[inline]
pub fn from_corners(p0: UVec2, p1: UVec2) -> Self {
Self {
min: p0.min(p1),
max: p0.max(p1),
}
}
/// Create a new rectangle from its center and size.
///
/// # Rounding Behavior
///
/// If the size contains odd numbers they will be rounded down to the nearest whole number.
///
/// # Panics
///
/// This method panics if any of the components of the size is negative or if `origin - (size / 2)` results in any negatives.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::from_center_size(UVec2::ONE, UVec2::splat(2)); // w=2 h=2
/// assert_eq!(r.min, UVec2::splat(0));
/// assert_eq!(r.max, UVec2::splat(2));
/// ```
#[inline]
pub fn from_center_size(origin: UVec2, size: UVec2) -> Self {
assert!(origin.cmpge(size / 2).all(), "Origin must always be greater than or equal to (size / 2) otherwise the rectangle is undefined! Origin was {origin} and size was {size}");
let half_size = size / 2;
Self::from_center_half_size(origin, half_size)
}
/// Create a new rectangle from its center and half-size.
///
/// # Panics
///
/// This method panics if any of the components of the half-size is negative or if `origin - half_size` results in any negatives.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::from_center_half_size(UVec2::ONE, UVec2::ONE); // w=2 h=2
/// assert_eq!(r.min, UVec2::splat(0));
/// assert_eq!(r.max, UVec2::splat(2));
/// ```
#[inline]
pub fn from_center_half_size(origin: UVec2, half_size: UVec2) -> Self {
assert!(origin.cmpge(half_size).all(), "Origin must always be greater than or equal to half_size otherwise the rectangle is undefined! Origin was {origin} and half_size was {half_size}");
Self {
min: origin - half_size,
max: origin + half_size,
}
}
/// Check if the rectangle is empty.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::from_corners(UVec2::ZERO, UVec2::new(0, 1)); // w=0 h=1
/// assert!(r.is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.min.cmpge(self.max).any()
}
/// Rectangle width (max.x - min.x).
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.width(), 5);
/// ```
#[inline]
pub const fn width(&self) -> u32 {
self.max.x - self.min.x
}
/// Rectangle height (max.y - min.y).
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.height(), 1);
/// ```
#[inline]
pub const fn height(&self) -> u32 {
self.max.y - self.min.y
}
/// Rectangle size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert_eq!(r.size(), UVec2::new(5, 1));
/// ```
#[inline]
pub fn size(&self) -> UVec2 {
self.max - self.min
}
/// Rectangle half-size.
///
/// # Rounding Behavior
///
/// If the full size contains odd numbers they will be rounded down to the nearest whole number when calculating the half size.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 4, 2); // w=4 h=2
/// assert_eq!(r.half_size(), UVec2::new(2, 1));
/// ```
#[inline]
pub fn half_size(&self) -> UVec2 {
self.size() / 2
}
/// The center point of the rectangle.
///
/// # Rounding Behavior
///
/// If the (min + max) contains odd numbers they will be rounded down to the nearest whole number when calculating the center.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 4, 2); // w=4 h=2
/// assert_eq!(r.center(), UVec2::new(2, 1));
/// ```
#[inline]
pub fn center(&self) -> UVec2 {
(self.min + self.max) / 2
}
/// Check if a point lies within this rectangle, inclusive of its edges.
///
/// # Examples
///
/// ```
/// # use bevy_math::URect;
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// assert!(r.contains(r.center()));
/// assert!(r.contains(r.min));
/// assert!(r.contains(r.max));
/// ```
#[inline]
pub fn contains(&self, point: UVec2) -> bool {
(point.cmpge(self.min) & point.cmple(self.max)).all()
}
/// Build a new rectangle formed of the union of this rectangle and another rectangle.
///
/// The union is the smallest rectangle enclosing both rectangles.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r1 = URect::new(0, 0, 5, 1); // w=5 h=1
/// let r2 = URect::new(1, 0, 3, 8); // w=2 h=4
/// let r = r1.union(r2);
/// assert_eq!(r.min, UVec2::new(0, 0));
/// assert_eq!(r.max, UVec2::new(5, 8));
/// ```
#[inline]
pub fn union(&self, other: Self) -> Self {
Self {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
/// Build a new rectangle formed of the union of this rectangle and a point.
///
/// The union is the smallest rectangle enclosing both the rectangle and the point. If the
/// point is already inside the rectangle, this method returns a copy of the rectangle.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(0, 0, 5, 1); // w=5 h=1
/// let u = r.union_point(UVec2::new(3, 6));
/// assert_eq!(u.min, UVec2::ZERO);
/// assert_eq!(u.max, UVec2::new(5, 6));
/// ```
#[inline]
pub fn union_point(&self, other: UVec2) -> Self {
Self {
min: self.min.min(other),
max: self.max.max(other),
}
}
/// Build a new rectangle formed of the intersection of this rectangle and another rectangle.
///
/// The intersection is the largest rectangle enclosed in both rectangles. If the intersection
/// is empty, this method returns an empty rectangle ([`URect::is_empty()`] returns `true`), but
/// the actual values of [`URect::min`] and [`URect::max`] are implementation-dependent.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r1 = URect::new(0, 0, 2, 2); // w=2 h=2
/// let r2 = URect::new(1, 1, 3, 3); // w=2 h=2
/// let r = r1.intersect(r2);
/// assert_eq!(r.min, UVec2::new(1, 1));
/// assert_eq!(r.max, UVec2::new(2, 2));
/// ```
#[inline]
pub fn intersect(&self, other: Self) -> Self {
let mut r = Self {
min: self.min.max(other.min),
max: self.max.min(other.max),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Create a new rectangle by expanding it evenly on all sides.
///
/// A positive expansion value produces a larger rectangle,
/// while a negative expansion value produces a smaller rectangle.
/// If this would result in zero width or height, [`URect::EMPTY`] is returned instead.
///
/// # Examples
///
/// ```
/// # use bevy_math::{URect, UVec2};
/// let r = URect::new(4, 4, 6, 6); // w=2 h=2
/// let r2 = r.inflate(1); // w=4 h=4
/// assert_eq!(r2.min, UVec2::splat(3));
/// assert_eq!(r2.max, UVec2::splat(7));
///
/// let r = URect::new(4, 4, 8, 8); // w=4 h=4
/// let r2 = r.inflate(-1); // w=2 h=2
/// assert_eq!(r2.min, UVec2::splat(5));
/// assert_eq!(r2.max, UVec2::splat(7));
/// ```
#[inline]
pub fn inflate(&self, expansion: i32) -> Self {
let mut r = Self {
min: UVec2::new(
self.min.x.saturating_add_signed(-expansion),
self.min.y.saturating_add_signed(-expansion),
),
max: UVec2::new(
self.max.x.saturating_add_signed(expansion),
self.max.y.saturating_add_signed(expansion),
),
};
// Collapse min over max to enforce invariants and ensure e.g. width() or
// height() never return a negative value.
r.min = r.min.min(r.max);
r
}
/// Returns self as [`Rect`] (f32)
#[inline]
pub fn as_rect(&self) -> Rect {
Rect::from_corners(self.min.as_vec2(), self.max.as_vec2())
}
/// Returns self as [`IRect`] (i32)
#[inline]
pub fn as_irect(&self) -> IRect {
IRect::from_corners(self.min.as_ivec2(), self.max.as_ivec2())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn well_formed() {
let r = URect::from_center_size(UVec2::new(10, 16), UVec2::new(8, 12));
assert_eq!(r.min, UVec2::new(6, 10));
assert_eq!(r.max, UVec2::new(14, 22));
assert_eq!(r.center(), UVec2::new(10, 16));
assert_eq!(r.width(), 8);
assert_eq!(r.height(), 12);
assert_eq!(r.size(), UVec2::new(8, 12));
assert_eq!(r.half_size(), UVec2::new(4, 6));
assert!(r.contains(UVec2::new(7, 10)));
assert!(r.contains(UVec2::new(14, 10)));
assert!(r.contains(UVec2::new(10, 22)));
assert!(r.contains(UVec2::new(6, 22)));
assert!(r.contains(UVec2::new(14, 22)));
assert!(!r.contains(UVec2::new(50, 5)));
}
#[test]
fn rect_union() {
let r = URect::from_center_size(UVec2::splat(4), UVec2::splat(4)); // [2, 2] - [6, 6]
// overlapping
let r2 = URect {
min: UVec2::new(0, 0),
max: UVec2::new(3, 3),
};
let u = r.union(r2);
assert_eq!(u.min, UVec2::new(0, 0));
assert_eq!(u.max, UVec2::new(6, 6));
// disjoint
let r2 = URect {
min: UVec2::new(4, 7),
max: UVec2::new(8, 8),
};
let u = r.union(r2);
assert_eq!(u.min, UVec2::new(2, 2));
assert_eq!(u.max, UVec2::new(8, 8));
// included
let r2 = URect::from_center_size(UVec2::splat(4), UVec2::splat(2));
let u = r.union(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// including
let r2 = URect::from_center_size(UVec2::splat(4), UVec2::splat(6));
let u = r.union(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.min, r2.min);
}
#[test]
fn rect_union_pt() {
let r = URect::from_center_size(UVec2::splat(4), UVec2::splat(4)); // [2, 2] - [6, 6]
// inside
let v = UVec2::new(2, 5);
let u = r.union_point(v);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
// outside
let v = UVec2::new(10, 5);
let u = r.union_point(v);
assert_eq!(u.min, UVec2::new(2, 2));
assert_eq!(u.max, UVec2::new(10, 6));
}
#[test]
fn rect_intersect() {
let r = URect::from_center_size(UVec2::splat(6), UVec2::splat(8)); // [2, 2] - [10, 10]
// overlapping
let r2 = URect {
min: UVec2::new(8, 8),
max: UVec2::new(12, 12),
};
let u = r.intersect(r2);
assert_eq!(u.min, UVec2::new(8, 8));
assert_eq!(u.max, UVec2::new(10, 10));
// disjoint
let r2 = URect {
min: UVec2::new(12, 12),
max: UVec2::new(14, 18),
};
let u = r.intersect(r2);
assert!(u.is_empty());
assert_eq!(u.width(), 0);
// included
let r2 = URect::from_center_size(UVec2::splat(6), UVec2::splat(2));
let u = r.intersect(r2);
assert_eq!(u.min, r2.min);
assert_eq!(u.max, r2.max);
// including
let r2 = URect::from_center_size(UVec2::splat(6), UVec2::splat(10));
let u = r.intersect(r2);
assert_eq!(u.min, r.min);
assert_eq!(u.max, r.max);
}
#[test]
fn rect_inflate() {
let r = URect::from_center_size(UVec2::splat(6), UVec2::splat(6)); // [3, 3] - [9, 9]
let r2 = r.inflate(2);
assert_eq!(r2.min, UVec2::new(1, 1));
assert_eq!(r2.max, UVec2::new(11, 11));
}
}

724
vendor/bevy_math/src/rotation2d.rs vendored Normal file
View File

@@ -0,0 +1,724 @@
use core::f32::consts::TAU;
use glam::FloatExt;
use crate::{
ops,
prelude::{Mat2, Vec2},
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A counterclockwise 2D rotation.
///
/// # Example
///
/// ```
/// # use approx::assert_relative_eq;
/// # use bevy_math::{Rot2, Vec2};
/// use std::f32::consts::PI;
///
/// // Create rotations from radians or degrees
/// let rotation1 = Rot2::radians(PI / 2.0);
/// let rotation2 = Rot2::degrees(45.0);
///
/// // Get the angle back as radians or degrees
/// assert_eq!(rotation1.as_degrees(), 90.0);
/// assert_eq!(rotation2.as_radians(), PI / 4.0);
///
/// // "Add" rotations together using `*`
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rotation1 * rotation2, Rot2::degrees(135.0));
///
/// // Rotate vectors
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rotation1 * Vec2::X, Vec2::Y);
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "rotation", alias = "rotation2d", alias = "rotation_2d")]
pub struct Rot2 {
/// The cosine of the rotation angle in radians.
///
/// This is the real part of the unit complex number representing the rotation.
pub cos: f32,
/// The sine of the rotation angle in radians.
///
/// This is the imaginary part of the unit complex number representing the rotation.
pub sin: f32,
}
impl Default for Rot2 {
fn default() -> Self {
Self::IDENTITY
}
}
impl Rot2 {
/// No rotation.
pub const IDENTITY: Self = Self { cos: 1.0, sin: 0.0 };
/// A rotation of π radians.
pub const PI: Self = Self {
cos: -1.0,
sin: 0.0,
};
/// A counterclockwise rotation of π/2 radians.
pub const FRAC_PI_2: Self = Self { cos: 0.0, sin: 1.0 };
/// A counterclockwise rotation of π/3 radians.
pub const FRAC_PI_3: Self = Self {
cos: 0.5,
sin: 0.866_025_4,
};
/// A counterclockwise rotation of π/4 radians.
pub const FRAC_PI_4: Self = Self {
cos: core::f32::consts::FRAC_1_SQRT_2,
sin: core::f32::consts::FRAC_1_SQRT_2,
};
/// A counterclockwise rotation of π/6 radians.
pub const FRAC_PI_6: Self = Self {
cos: 0.866_025_4,
sin: 0.5,
};
/// A counterclockwise rotation of π/8 radians.
pub const FRAC_PI_8: Self = Self {
cos: 0.923_879_5,
sin: 0.382_683_43,
};
/// Creates a [`Rot2`] from a counterclockwise angle in radians.
///
/// # Note
///
/// The input rotation will always be clamped to the range `(-π, π]` by design.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// # use approx::assert_relative_eq;
/// # use std::f32::consts::{FRAC_PI_2, PI};
///
/// let rot1 = Rot2::radians(3.0 * FRAC_PI_2);
/// let rot2 = Rot2::radians(-FRAC_PI_2);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1, rot2);
///
/// let rot3 = Rot2::radians(PI);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1 * rot1, rot3);
/// ```
#[inline]
pub fn radians(radians: f32) -> Self {
let (sin, cos) = ops::sin_cos(radians);
Self::from_sin_cos(sin, cos)
}
/// Creates a [`Rot2`] from a counterclockwise angle in degrees.
///
/// # Note
///
/// The input rotation will always be clamped to the range `(-180°, 180°]` by design.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// # use approx::assert_relative_eq;
///
/// let rot1 = Rot2::degrees(270.0);
/// let rot2 = Rot2::degrees(-90.0);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1, rot2);
///
/// let rot3 = Rot2::degrees(180.0);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1 * rot1, rot3);
/// ```
#[inline]
pub fn degrees(degrees: f32) -> Self {
Self::radians(degrees.to_radians())
}
/// Creates a [`Rot2`] from a counterclockwise fraction of a full turn of 360 degrees.
///
/// # Note
///
/// The input rotation will always be clamped to the range `(-50%, 50%]` by design.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// # use approx::assert_relative_eq;
///
/// let rot1 = Rot2::turn_fraction(0.75);
/// let rot2 = Rot2::turn_fraction(-0.25);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1, rot2);
///
/// let rot3 = Rot2::turn_fraction(0.5);
/// #[cfg(feature = "approx")]
/// assert_relative_eq!(rot1 * rot1, rot3);
/// ```
#[inline]
pub fn turn_fraction(fraction: f32) -> Self {
Self::radians(TAU * fraction)
}
/// Creates a [`Rot2`] from the sine and cosine of an angle in radians.
///
/// The rotation is only valid if `sin * sin + cos * cos == 1.0`.
///
/// # Panics
///
/// Panics if `sin * sin + cos * cos != 1.0` when the `glam_assert` feature is enabled.
#[inline]
pub fn from_sin_cos(sin: f32, cos: f32) -> Self {
let rotation = Self { sin, cos };
debug_assert!(
rotation.is_normalized(),
"the given sine and cosine produce an invalid rotation"
);
rotation
}
/// Returns the rotation in radians in the `(-pi, pi]` range.
#[inline]
pub fn as_radians(self) -> f32 {
ops::atan2(self.sin, self.cos)
}
/// Returns the rotation in degrees in the `(-180, 180]` range.
#[inline]
pub fn as_degrees(self) -> f32 {
self.as_radians().to_degrees()
}
/// Returns the rotation as a fraction of a full 360 degree turn.
#[inline]
pub fn as_turn_fraction(self) -> f32 {
self.as_radians() / TAU
}
/// Returns the sine and cosine of the rotation angle in radians.
#[inline]
pub const fn sin_cos(self) -> (f32, f32) {
(self.sin, self.cos)
}
/// Computes the length or norm of the complex number used to represent the rotation.
///
/// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
/// can be a result of incorrect construction or floating point error caused by
/// successive operations.
#[inline]
#[doc(alias = "norm")]
pub fn length(self) -> f32 {
Vec2::new(self.sin, self.cos).length()
}
/// Computes the squared length or norm of the complex number used to represent the rotation.
///
/// This is generally faster than [`Rot2::length()`], as it avoids a square
/// root operation.
///
/// The length is typically expected to be `1.0`. Unexpectedly denormalized rotations
/// can be a result of incorrect construction or floating point error caused by
/// successive operations.
#[inline]
#[doc(alias = "norm2")]
pub fn length_squared(self) -> f32 {
Vec2::new(self.sin, self.cos).length_squared()
}
/// Computes `1.0 / self.length()`.
///
/// For valid results, `self` must _not_ have a length of zero.
#[inline]
pub fn length_recip(self) -> f32 {
Vec2::new(self.sin, self.cos).length_recip()
}
/// Returns `self` with a length of `1.0` if possible, and `None` otherwise.
///
/// `None` will be returned if the sine and cosine of `self` are both zero (or very close to zero),
/// or if either of them is NaN or infinite.
///
/// Note that [`Rot2`] should typically already be normalized by design.
/// Manual normalization is only needed when successive operations result in
/// accumulated floating point error, or if the rotation was constructed
/// with invalid values.
#[inline]
pub fn try_normalize(self) -> Option<Self> {
let recip = self.length_recip();
if recip.is_finite() && recip > 0.0 {
Some(Self::from_sin_cos(self.sin * recip, self.cos * recip))
} else {
None
}
}
/// Returns `self` with a length of `1.0`.
///
/// Note that [`Rot2`] should typically already be normalized by design.
/// Manual normalization is only needed when successive operations result in
/// accumulated floating point error, or if the rotation was constructed
/// with invalid values.
///
/// # Panics
///
/// Panics if `self` has a length of zero, NaN, or infinity when debug assertions are enabled.
#[inline]
pub fn normalize(self) -> Self {
let length_recip = self.length_recip();
Self::from_sin_cos(self.sin * length_recip, self.cos * length_recip)
}
/// Returns `self` after an approximate normalization, assuming the value is already nearly normalized.
/// Useful for preventing numerical error accumulation.
/// See [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for an example of when such error accumulation might occur.
#[inline]
pub fn fast_renormalize(self) -> Self {
let length_squared = self.length_squared();
// Based on a Taylor approximation of the inverse square root, see [`Dir3::fast_renormalize`](crate::Dir3::fast_renormalize) for more details.
let length_recip_approx = 0.5 * (3.0 - length_squared);
Rot2 {
sin: self.sin * length_recip_approx,
cos: self.cos * length_recip_approx,
}
}
/// Returns `true` if the rotation is neither infinite nor NaN.
#[inline]
pub fn is_finite(self) -> bool {
self.sin.is_finite() && self.cos.is_finite()
}
/// Returns `true` if the rotation is NaN.
#[inline]
pub fn is_nan(self) -> bool {
self.sin.is_nan() || self.cos.is_nan()
}
/// Returns whether `self` has a length of `1.0` or not.
///
/// Uses a precision threshold of approximately `1e-4`.
#[inline]
pub fn is_normalized(self) -> bool {
// The allowed length is 1 +/- 1e-4, so the largest allowed
// squared length is (1 + 1e-4)^2 = 1.00020001, which makes
// the threshold for the squared length approximately 2e-4.
ops::abs(self.length_squared() - 1.0) <= 2e-4
}
/// Returns `true` if the rotation is near [`Rot2::IDENTITY`].
#[inline]
pub fn is_near_identity(self) -> bool {
// Same as `Quat::is_near_identity`, but using sine and cosine
let threshold_angle_sin = 0.000_049_692_047; // let threshold_angle = 0.002_847_144_6;
self.cos > 0.0 && ops::abs(self.sin) < threshold_angle_sin
}
/// Returns the angle in radians needed to make `self` and `other` coincide.
#[inline]
pub fn angle_to(self, other: Self) -> f32 {
(other * self.inverse()).as_radians()
}
/// Returns the inverse of the rotation. This is also the conjugate
/// of the unit complex number representing the rotation.
#[inline]
#[must_use]
#[doc(alias = "conjugate")]
pub const fn inverse(self) -> Self {
Self {
cos: self.cos,
sin: -self.sin,
}
}
/// Performs a linear interpolation between `self` and `rhs` based on
/// the value `s`, and normalizes the rotation afterwards.
///
/// When `s == 0.0`, the result will be equal to `self`.
/// When `s == 1.0`, the result will be equal to `rhs`.
///
/// This is slightly more efficient than [`slerp`](Self::slerp), and produces a similar result
/// when the difference between the two rotations is small. At larger differences,
/// the result resembles a kind of ease-in-out effect.
///
/// If you would like the angular velocity to remain constant, consider using [`slerp`](Self::slerp) instead.
///
/// # Details
///
/// `nlerp` corresponds to computing an angle for a point at position `s` on a line drawn
/// between the endpoints of the arc formed by `self` and `rhs` on a unit circle,
/// and normalizing the result afterwards.
///
/// Note that if the angles are opposite like 0 and π, the line will pass through the origin,
/// and the resulting angle will always be either `self` or `rhs` depending on `s`.
/// If `s` happens to be `0.5` in this case, a valid rotation cannot be computed, and `self`
/// will be returned as a fallback.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// #
/// let rot1 = Rot2::IDENTITY;
/// let rot2 = Rot2::degrees(135.0);
///
/// let result1 = rot1.nlerp(rot2, 1.0 / 3.0);
/// assert_eq!(result1.as_degrees(), 28.675055);
///
/// let result2 = rot1.nlerp(rot2, 0.5);
/// assert_eq!(result2.as_degrees(), 67.5);
/// ```
#[inline]
pub fn nlerp(self, end: Self, s: f32) -> Self {
Self {
sin: self.sin.lerp(end.sin, s),
cos: self.cos.lerp(end.cos, s),
}
.try_normalize()
// Fall back to the start rotation.
// This can happen when `self` and `end` are opposite angles and `s == 0.5`,
// because the resulting rotation would be zero, which cannot be normalized.
.unwrap_or(self)
}
/// Performs a spherical linear interpolation between `self` and `end`
/// based on the value `s`.
///
/// This corresponds to interpolating between the two angles at a constant angular velocity.
///
/// When `s == 0.0`, the result will be equal to `self`.
/// When `s == 1.0`, the result will be equal to `rhs`.
///
/// If you would like the rotation to have a kind of ease-in-out effect, consider
/// using the slightly more efficient [`nlerp`](Self::nlerp) instead.
///
/// # Example
///
/// ```
/// # use bevy_math::Rot2;
/// #
/// let rot1 = Rot2::IDENTITY;
/// let rot2 = Rot2::degrees(135.0);
///
/// let result1 = rot1.slerp(rot2, 1.0 / 3.0);
/// assert_eq!(result1.as_degrees(), 45.0);
///
/// let result2 = rot1.slerp(rot2, 0.5);
/// assert_eq!(result2.as_degrees(), 67.5);
/// ```
#[inline]
pub fn slerp(self, end: Self, s: f32) -> Self {
self * Self::radians(self.angle_to(end) * s)
}
}
impl From<f32> for Rot2 {
/// Creates a [`Rot2`] from a counterclockwise angle in radians.
fn from(rotation: f32) -> Self {
Self::radians(rotation)
}
}
impl From<Rot2> for Mat2 {
/// Creates a [`Mat2`] rotation matrix from a [`Rot2`].
fn from(rot: Rot2) -> Self {
Mat2::from_cols_array(&[rot.cos, -rot.sin, rot.sin, rot.cos])
}
}
impl core::ops::Mul for Rot2 {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
cos: self.cos * rhs.cos - self.sin * rhs.sin,
sin: self.sin * rhs.cos + self.cos * rhs.sin,
}
}
}
impl core::ops::MulAssign for Rot2 {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl core::ops::Mul<Vec2> for Rot2 {
type Output = Vec2;
/// Rotates a [`Vec2`] by a [`Rot2`].
fn mul(self, rhs: Vec2) -> Self::Output {
Vec2::new(
rhs.x * self.cos - rhs.y * self.sin,
rhs.x * self.sin + rhs.y * self.cos,
)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::AbsDiffEq for Rot2 {
type Epsilon = f32;
fn default_epsilon() -> f32 {
f32::EPSILON
}
fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool {
self.cos.abs_diff_eq(&other.cos, epsilon) && self.sin.abs_diff_eq(&other.sin, epsilon)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::RelativeEq for Rot2 {
fn default_max_relative() -> f32 {
f32::EPSILON
}
fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool {
self.cos.relative_eq(&other.cos, epsilon, max_relative)
&& self.sin.relative_eq(&other.sin, epsilon, max_relative)
}
}
#[cfg(any(feature = "approx", test))]
impl approx::UlpsEq for Rot2 {
fn default_max_ulps() -> u32 {
4
}
fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool {
self.cos.ulps_eq(&other.cos, epsilon, max_ulps)
&& self.sin.ulps_eq(&other.sin, epsilon, max_ulps)
}
}
#[cfg(test)]
mod tests {
use core::f32::consts::FRAC_PI_2;
use approx::assert_relative_eq;
use crate::{ops, Dir2, Rot2, Vec2};
#[test]
fn creation() {
let rotation1 = Rot2::radians(FRAC_PI_2);
let rotation2 = Rot2::degrees(90.0);
let rotation3 = Rot2::from_sin_cos(1.0, 0.0);
let rotation4 = Rot2::turn_fraction(0.25);
// All three rotations should be equal
assert_relative_eq!(rotation1.sin, rotation2.sin);
assert_relative_eq!(rotation1.cos, rotation2.cos);
assert_relative_eq!(rotation1.sin, rotation3.sin);
assert_relative_eq!(rotation1.cos, rotation3.cos);
assert_relative_eq!(rotation1.sin, rotation4.sin);
assert_relative_eq!(rotation1.cos, rotation4.cos);
// The rotation should be 90 degrees
assert_relative_eq!(rotation1.as_radians(), FRAC_PI_2);
assert_relative_eq!(rotation1.as_degrees(), 90.0);
assert_relative_eq!(rotation1.as_turn_fraction(), 0.25);
}
#[test]
fn rotate() {
let rotation = Rot2::degrees(90.0);
assert_relative_eq!(rotation * Vec2::X, Vec2::Y);
assert_relative_eq!(rotation * Dir2::Y, Dir2::NEG_X);
}
#[test]
fn rotation_range() {
// the rotation range is `(-180, 180]` and the constructors
// normalize the rotations to that range
assert_relative_eq!(Rot2::radians(3.0 * FRAC_PI_2), Rot2::radians(-FRAC_PI_2));
assert_relative_eq!(Rot2::degrees(270.0), Rot2::degrees(-90.0));
assert_relative_eq!(Rot2::turn_fraction(0.75), Rot2::turn_fraction(-0.25));
}
#[test]
fn add() {
let rotation1 = Rot2::degrees(90.0);
let rotation2 = Rot2::degrees(180.0);
// 90 deg + 180 deg becomes -90 deg after it wraps around to be within the `(-180, 180]` range
assert_eq!((rotation1 * rotation2).as_degrees(), -90.0);
}
#[test]
fn subtract() {
let rotation1 = Rot2::degrees(90.0);
let rotation2 = Rot2::degrees(45.0);
assert_relative_eq!((rotation1 * rotation2.inverse()).as_degrees(), 45.0);
// This should be equivalent to the above
assert_relative_eq!(rotation2.angle_to(rotation1), core::f32::consts::FRAC_PI_4);
}
#[test]
fn length() {
let rotation = Rot2 {
sin: 10.0,
cos: 5.0,
};
assert_eq!(rotation.length_squared(), 125.0);
assert_eq!(rotation.length(), 11.18034);
assert!(ops::abs(rotation.normalize().length() - 1.0) < 10e-7);
}
#[test]
fn is_near_identity() {
assert!(!Rot2::radians(0.1).is_near_identity());
assert!(!Rot2::radians(-0.1).is_near_identity());
assert!(Rot2::radians(0.00001).is_near_identity());
assert!(Rot2::radians(-0.00001).is_near_identity());
assert!(Rot2::radians(0.0).is_near_identity());
}
#[test]
fn normalize() {
let rotation = Rot2 {
sin: 10.0,
cos: 5.0,
};
let normalized_rotation = rotation.normalize();
assert_eq!(normalized_rotation.sin, 0.89442724);
assert_eq!(normalized_rotation.cos, 0.44721362);
assert!(!rotation.is_normalized());
assert!(normalized_rotation.is_normalized());
}
#[test]
fn fast_renormalize() {
let rotation = Rot2 { sin: 1.0, cos: 0.5 };
let normalized_rotation = rotation.normalize();
let mut unnormalized_rot = rotation;
let mut renormalized_rot = rotation;
let mut initially_normalized_rot = normalized_rotation;
let mut fully_normalized_rot = normalized_rotation;
// Compute a 64x (=2⁶) multiple of the rotation.
for _ in 0..6 {
unnormalized_rot = unnormalized_rot * unnormalized_rot;
renormalized_rot = renormalized_rot * renormalized_rot;
initially_normalized_rot = initially_normalized_rot * initially_normalized_rot;
fully_normalized_rot = fully_normalized_rot * fully_normalized_rot;
renormalized_rot = renormalized_rot.fast_renormalize();
fully_normalized_rot = fully_normalized_rot.normalize();
}
assert!(!unnormalized_rot.is_normalized());
assert!(renormalized_rot.is_normalized());
assert!(fully_normalized_rot.is_normalized());
assert_relative_eq!(fully_normalized_rot, renormalized_rot, epsilon = 0.000001);
assert_relative_eq!(
fully_normalized_rot,
unnormalized_rot.normalize(),
epsilon = 0.000001
);
assert_relative_eq!(
fully_normalized_rot,
initially_normalized_rot.normalize(),
epsilon = 0.000001
);
}
#[test]
fn try_normalize() {
// Valid
assert!(Rot2 {
sin: 10.0,
cos: 5.0,
}
.try_normalize()
.is_some());
// NaN
assert!(Rot2 {
sin: f32::NAN,
cos: 5.0,
}
.try_normalize()
.is_none());
// Zero
assert!(Rot2 { sin: 0.0, cos: 0.0 }.try_normalize().is_none());
// Non-finite
assert!(Rot2 {
sin: f32::INFINITY,
cos: 5.0,
}
.try_normalize()
.is_none());
}
#[test]
fn nlerp() {
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::degrees(135.0);
assert_eq!(rot1.nlerp(rot2, 1.0 / 3.0).as_degrees(), 28.675055);
assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 67.5);
assert_eq!(rot1.nlerp(rot2, 1.0).as_degrees(), 135.0);
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::from_sin_cos(0.0, -1.0);
assert!(rot1.nlerp(rot2, 1.0 / 3.0).is_near_identity());
assert!(rot1.nlerp(rot2, 0.0).is_near_identity());
// At 0.5, there is no valid rotation, so the fallback is the original angle.
assert_eq!(rot1.nlerp(rot2, 0.5).as_degrees(), 0.0);
assert_eq!(ops::abs(rot1.nlerp(rot2, 1.0).as_degrees()), 180.0);
}
#[test]
fn slerp() {
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::degrees(135.0);
assert_eq!(rot1.slerp(rot2, 1.0 / 3.0).as_degrees(), 45.0);
assert!(rot1.slerp(rot2, 0.0).is_near_identity());
assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 67.5);
assert_eq!(rot1.slerp(rot2, 1.0).as_degrees(), 135.0);
let rot1 = Rot2::IDENTITY;
let rot2 = Rot2::from_sin_cos(0.0, -1.0);
assert!(ops::abs(rot1.slerp(rot2, 1.0 / 3.0).as_degrees() - 60.0) < 10e-6);
assert!(rot1.slerp(rot2, 0.0).is_near_identity());
assert_eq!(rot1.slerp(rot2, 0.5).as_degrees(), 90.0);
assert_eq!(ops::abs(rot1.slerp(rot2, 1.0).as_degrees()), 180.0);
}
}

View File

@@ -0,0 +1,59 @@
//! Functionality related to random sampling from triangle meshes.
use crate::{
primitives::{Measured2d, Triangle3d},
ShapeSample, Vec3,
};
use alloc::vec::Vec;
use rand::Rng;
use rand_distr::{Distribution, WeightedAliasIndex, WeightedError};
/// A [distribution] that caches data to allow fast sampling from a collection of triangles.
/// Generally used through [`sample`] or [`sample_iter`].
///
/// [distribution]: Distribution
/// [`sample`]: Distribution::sample
/// [`sample_iter`]: Distribution::sample_iter
///
/// Example
/// ```
/// # use bevy_math::{Vec3, primitives::*};
/// # use bevy_math::sampling::mesh_sampling::UniformMeshSampler;
/// # use rand::{SeedableRng, rngs::StdRng, distributions::Distribution};
/// let faces = Tetrahedron::default().faces();
/// let sampler = UniformMeshSampler::try_new(faces).unwrap();
/// let rng = StdRng::seed_from_u64(8765309);
/// // 50 random points on the tetrahedron:
/// let samples: Vec<Vec3> = sampler.sample_iter(rng).take(50).collect();
/// ```
pub struct UniformMeshSampler {
triangles: Vec<Triangle3d>,
face_distribution: WeightedAliasIndex<f32>,
}
impl Distribution<Vec3> for UniformMeshSampler {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let face_index = self.face_distribution.sample(rng);
self.triangles[face_index].sample_interior(rng)
}
}
impl UniformMeshSampler {
/// Construct a new [`UniformMeshSampler`] from a list of [triangles].
///
/// Returns an error if the distribution of areas for the collection of triangles could not be formed
/// (most notably if the collection has zero surface area).
///
/// [triangles]: Triangle3d
pub fn try_new<T: IntoIterator<Item = Triangle3d>>(
triangles: T,
) -> Result<Self, WeightedError> {
let triangles: Vec<Triangle3d> = triangles.into_iter().collect();
let areas = triangles.iter().map(Measured2d::area).collect();
WeightedAliasIndex::new(areas).map(|face_distribution| Self {
triangles,
face_distribution,
})
}
}

13
vendor/bevy_math/src/sampling/mod.rs vendored Normal file
View File

@@ -0,0 +1,13 @@
//! This module contains tools related to random sampling.
//!
//! To use this, the "rand" feature must be enabled.
#[cfg(feature = "alloc")]
pub mod mesh_sampling;
pub mod shape_sampling;
pub mod standard;
#[cfg(feature = "alloc")]
pub use mesh_sampling::*;
pub use shape_sampling::*;
pub use standard::*;

View File

@@ -0,0 +1,640 @@
//! The [`ShapeSample`] trait, allowing random sampling from geometric shapes.
//!
//! At the most basic level, this allows sampling random points from the interior and boundary of
//! geometric primitives. For example:
//! ```
//! # use bevy_math::primitives::*;
//! # use bevy_math::ShapeSample;
//! # use rand::SeedableRng;
//! # use rand::rngs::StdRng;
//! // Get some `Rng`:
//! let rng = &mut StdRng::from_entropy();
//! // Make a circle of radius 2:
//! let circle = Circle::new(2.0);
//! // Get a point inside this circle uniformly at random:
//! let interior_pt = circle.sample_interior(rng);
//! // Get a point on the circle's boundary uniformly at random:
//! let boundary_pt = circle.sample_boundary(rng);
//! ```
//!
//! For repeated sampling, `ShapeSample` also includes methods for accessing a [`Distribution`]:
//! ```
//! # use bevy_math::primitives::*;
//! # use bevy_math::{Vec2, ShapeSample};
//! # use rand::SeedableRng;
//! # use rand::rngs::StdRng;
//! # use rand::distributions::Distribution;
//! # let rng1 = StdRng::from_entropy();
//! # let rng2 = StdRng::from_entropy();
//! // Use a rectangle this time:
//! let rectangle = Rectangle::new(1.0, 2.0);
//! // Get an iterator that spits out random interior points:
//! let interior_iter = rectangle.interior_dist().sample_iter(rng1);
//! // Collect random interior points from the iterator:
//! let interior_pts: Vec<Vec2> = interior_iter.take(1000).collect();
//! // Similarly, get an iterator over many random boundary points and collect them:
//! let boundary_pts: Vec<Vec2> = rectangle.boundary_dist().sample_iter(rng2).take(1000).collect();
//! ```
//!
//! In any case, the [`Rng`] used as the source of randomness must be provided explicitly.
use core::f32::consts::{PI, TAU};
use crate::{ops, primitives::*, NormedVectorSpace, Vec2, Vec3};
use rand::{
distributions::{Distribution, WeightedIndex},
Rng,
};
/// Exposes methods to uniformly sample a variety of primitive shapes.
pub trait ShapeSample {
/// The type of vector returned by the sample methods, [`Vec2`] for 2D shapes and [`Vec3`] for 3D shapes.
type Output;
/// Uniformly sample a point from inside the area/volume of this shape, centered on 0.
///
/// Shapes like [`Cylinder`], [`Capsule2d`] and [`Capsule3d`] are oriented along the y-axis.
///
/// # Example
/// ```
/// # use bevy_math::prelude::*;
/// let square = Rectangle::new(2.0, 2.0);
///
/// // Returns a Vec2 with both x and y between -1 and 1.
/// println!("{}", square.sample_interior(&mut rand::thread_rng()));
/// ```
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output;
/// Uniformly sample a point from the surface of this shape, centered on 0.
///
/// Shapes like [`Cylinder`], [`Capsule2d`] and [`Capsule3d`] are oriented along the y-axis.
///
/// # Example
/// ```
/// # use bevy_math::prelude::*;
/// let square = Rectangle::new(2.0, 2.0);
///
/// // Returns a Vec2 where one of the coordinates is at ±1,
/// // and the other is somewhere between -1 and 1.
/// println!("{}", square.sample_boundary(&mut rand::thread_rng()));
/// ```
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output;
/// Extract a [`Distribution`] whose samples are points of this shape's interior, taken uniformly.
///
/// # Example
///
/// ```
/// # use bevy_math::prelude::*;
/// # use rand::distributions::Distribution;
/// let square = Rectangle::new(2.0, 2.0);
/// let rng = rand::thread_rng();
///
/// // Iterate over points randomly drawn from `square`'s interior:
/// for random_val in square.interior_dist().sample_iter(rng).take(5) {
/// println!("{}", random_val);
/// }
/// ```
fn interior_dist(self) -> impl Distribution<Self::Output>
where
Self: Sized,
{
InteriorOf(self)
}
/// Extract a [`Distribution`] whose samples are points of this shape's boundary, taken uniformly.
///
/// # Example
///
/// ```
/// # use bevy_math::prelude::*;
/// # use rand::distributions::Distribution;
/// let square = Rectangle::new(2.0, 2.0);
/// let rng = rand::thread_rng();
///
/// // Iterate over points randomly drawn from `square`'s boundary:
/// for random_val in square.boundary_dist().sample_iter(rng).take(5) {
/// println!("{}", random_val);
/// }
/// ```
fn boundary_dist(self) -> impl Distribution<Self::Output>
where
Self: Sized,
{
BoundaryOf(self)
}
}
#[derive(Clone, Copy)]
/// A wrapper struct that allows interior sampling from a [`ShapeSample`] type directly as
/// a [`Distribution`].
pub struct InteriorOf<T: ShapeSample>(pub T);
#[derive(Clone, Copy)]
/// A wrapper struct that allows boundary sampling from a [`ShapeSample`] type directly as
/// a [`Distribution`].
pub struct BoundaryOf<T: ShapeSample>(pub T);
impl<T: ShapeSample> Distribution<<T as ShapeSample>::Output> for InteriorOf<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> <T as ShapeSample>::Output {
self.0.sample_interior(rng)
}
}
impl<T: ShapeSample> Distribution<<T as ShapeSample>::Output> for BoundaryOf<T> {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> <T as ShapeSample>::Output {
self.0.sample_boundary(rng)
}
}
impl ShapeSample for Circle {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
// https://mathworld.wolfram.com/DiskPointPicking.html
let theta = rng.gen_range(0.0..TAU);
let r_squared = rng.gen_range(0.0..=(self.radius * self.radius));
let r = ops::sqrt(r_squared);
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(r * cos, r * sin)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let theta = rng.gen_range(0.0..TAU);
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(self.radius * cos, self.radius * sin)
}
}
/// Boundary sampling for unit-spheres
#[inline]
fn sample_unit_sphere_boundary<R: Rng + ?Sized>(rng: &mut R) -> Vec3 {
let z = rng.gen_range(-1f32..=1f32);
let (a_sin, a_cos) = ops::sin_cos(rng.gen_range(-PI..=PI));
let c = ops::sqrt(1f32 - z * z);
let x = a_sin * c;
let y = a_cos * c;
Vec3::new(x, y, z)
}
impl ShapeSample for Sphere {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let r_cubed = rng.gen_range(0.0..=(self.radius * self.radius * self.radius));
let r = ops::cbrt(r_cubed);
r * sample_unit_sphere_boundary(rng)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
self.radius * sample_unit_sphere_boundary(rng)
}
}
impl ShapeSample for Annulus {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let inner_radius = self.inner_circle.radius;
let outer_radius = self.outer_circle.radius;
// Like random sampling for a circle, radius is weighted by the square.
let r_squared = rng.gen_range((inner_radius * inner_radius)..(outer_radius * outer_radius));
let r = ops::sqrt(r_squared);
let theta = rng.gen_range(0.0..TAU);
let (sin, cos) = ops::sin_cos(theta);
Vec2::new(r * cos, r * sin)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let total_perimeter = self.inner_circle.perimeter() + self.outer_circle.perimeter();
let inner_prob = (self.inner_circle.perimeter() / total_perimeter) as f64;
// Sample from boundary circles, choosing which one by weighting by perimeter:
let inner = rng.gen_bool(inner_prob);
if inner {
self.inner_circle.sample_boundary(rng)
} else {
self.outer_circle.sample_boundary(rng)
}
}
}
impl ShapeSample for Rectangle {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let x = rng.gen_range(-self.half_size.x..=self.half_size.x);
let y = rng.gen_range(-self.half_size.y..=self.half_size.y);
Vec2::new(x, y)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let primary_side = rng.gen_range(-1.0..1.0);
let other_side = if rng.r#gen() { -1.0 } else { 1.0 };
if self.half_size.x + self.half_size.y > 0.0 {
if rng.gen_bool((self.half_size.x / (self.half_size.x + self.half_size.y)) as f64) {
Vec2::new(primary_side, other_side) * self.half_size
} else {
Vec2::new(other_side, primary_side) * self.half_size
}
} else {
Vec2::ZERO
}
}
}
impl ShapeSample for Cuboid {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let x = rng.gen_range(-self.half_size.x..=self.half_size.x);
let y = rng.gen_range(-self.half_size.y..=self.half_size.y);
let z = rng.gen_range(-self.half_size.z..=self.half_size.z);
Vec3::new(x, y, z)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let primary_side1 = rng.gen_range(-1.0..1.0);
let primary_side2 = rng.gen_range(-1.0..1.0);
let other_side = if rng.r#gen() { -1.0 } else { 1.0 };
if let Ok(dist) = WeightedIndex::new([
self.half_size.y * self.half_size.z,
self.half_size.x * self.half_size.z,
self.half_size.x * self.half_size.y,
]) {
match dist.sample(rng) {
0 => Vec3::new(other_side, primary_side1, primary_side2) * self.half_size,
1 => Vec3::new(primary_side1, other_side, primary_side2) * self.half_size,
2 => Vec3::new(primary_side1, primary_side2, other_side) * self.half_size,
_ => unreachable!(),
}
} else {
Vec3::ZERO
}
}
}
/// Interior sampling for triangles which doesn't depend on the ambient dimension.
fn sample_triangle_interior<P: NormedVectorSpace, R: Rng + ?Sized>(
vertices: [P; 3],
rng: &mut R,
) -> P {
let [a, b, c] = vertices;
let ab = b - a;
let ac = c - a;
// Generate random points on a parallelepiped and reflect so that
// we can use the points that lie outside the triangle
let u = rng.gen_range(0.0..=1.0);
let v = rng.gen_range(0.0..=1.0);
if u + v > 1. {
let u1 = 1. - v;
let v1 = 1. - u;
a + (ab * u1 + ac * v1)
} else {
a + (ab * u + ac * v)
}
}
/// Boundary sampling for triangles which doesn't depend on the ambient dimension.
fn sample_triangle_boundary<P: NormedVectorSpace, R: Rng + ?Sized>(
vertices: [P; 3],
rng: &mut R,
) -> P {
let [a, b, c] = vertices;
let ab = b - a;
let ac = c - a;
let bc = c - b;
let t = rng.gen_range(0.0..=1.0);
if let Ok(dist) = WeightedIndex::new([ab.norm(), ac.norm(), bc.norm()]) {
match dist.sample(rng) {
0 => a.lerp(b, t),
1 => a.lerp(c, t),
2 => b.lerp(c, t),
_ => unreachable!(),
}
} else {
// This should only occur when the triangle is 0-dimensional degenerate
// so this is actually the correct result.
a
}
}
impl ShapeSample for Triangle2d {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_interior(self.vertices, rng)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_boundary(self.vertices, rng)
}
}
impl ShapeSample for Triangle3d {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_interior(self.vertices, rng)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
sample_triangle_boundary(self.vertices, rng)
}
}
impl ShapeSample for Tetrahedron {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let [v0, v1, v2, v3] = self.vertices;
// Generate a random point in a cube:
let mut coords: [f32; 3] = [
rng.gen_range(0.0..1.0),
rng.gen_range(0.0..1.0),
rng.gen_range(0.0..1.0),
];
// The cube is broken into six tetrahedra of the form 0 <= c_0 <= c_1 <= c_2 <= 1,
// where c_i are the three euclidean coordinates in some permutation. (Since 3! = 6,
// there are six of them). Sorting the coordinates folds these six tetrahedra into the
// tetrahedron 0 <= x <= y <= z <= 1 (i.e. a fundamental domain of the permutation action).
coords.sort_by(|x, y| x.partial_cmp(y).unwrap());
// Now, convert a point from the fundamental tetrahedron into barycentric coordinates by
// taking the four successive differences of coordinates; note that these telescope to sum
// to 1, and this transformation is linear, hence preserves the probability density, since
// the latter comes from the Lebesgue measure.
//
// (See https://en.wikipedia.org/wiki/Lebesgue_measure#Properties — specifically, that
// Lebesgue measure of a linearly transformed set is its original measure times the
// determinant.)
let (a, b, c, d) = (
coords[0],
coords[1] - coords[0],
coords[2] - coords[1],
1. - coords[2],
);
// This is also a linear mapping, so probability density is still preserved.
v0 * a + v1 * b + v2 * c + v3 * d
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let triangles = self.faces();
let areas = triangles.iter().map(Measured2d::area);
if areas.clone().sum::<f32>() > 0.0 {
// There is at least one triangle with nonzero area, so this unwrap succeeds.
let dist = WeightedIndex::new(areas).unwrap();
// Get a random index, then sample the interior of the associated triangle.
let idx = dist.sample(rng);
triangles[idx].sample_interior(rng)
} else {
// In this branch the tetrahedron has zero surface area; just return a point that's on
// the tetrahedron.
self.vertices[0]
}
}
}
impl ShapeSample for Cylinder {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let Vec2 { x, y: z } = self.base().sample_interior(rng);
let y = rng.gen_range(-self.half_height..=self.half_height);
Vec3::new(x, y, z)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
// This uses the area of the ends divided by the overall surface area (optimized)
// [2 (\pi r^2)]/[2 (\pi r^2) + 2 \pi r h] = r/(r + h)
if self.radius + 2.0 * self.half_height > 0.0 {
if rng.gen_bool((self.radius / (self.radius + 2.0 * self.half_height)) as f64) {
let Vec2 { x, y: z } = self.base().sample_interior(rng);
if rng.r#gen() {
Vec3::new(x, self.half_height, z)
} else {
Vec3::new(x, -self.half_height, z)
}
} else {
let Vec2 { x, y: z } = self.base().sample_boundary(rng);
let y = rng.gen_range(-self.half_height..=self.half_height);
Vec3::new(x, y, z)
}
} else {
Vec3::ZERO
}
}
}
impl ShapeSample for Capsule2d {
type Output = Vec2;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let rectangle_area = self.half_length * self.radius * 4.0;
let capsule_area = rectangle_area + PI * self.radius * self.radius;
if capsule_area > 0.0 {
// Check if the random point should be inside the rectangle
if rng.gen_bool((rectangle_area / capsule_area) as f64) {
self.to_inner_rectangle().sample_interior(rng)
} else {
let circle = Circle::new(self.radius);
let point = circle.sample_interior(rng);
// Add half length if it is the top semi-circle, otherwise subtract half
if point.y > 0.0 {
point + Vec2::Y * self.half_length
} else {
point - Vec2::Y * self.half_length
}
}
} else {
Vec2::ZERO
}
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec2 {
let rectangle_surface = 4.0 * self.half_length;
let capsule_surface = rectangle_surface + TAU * self.radius;
if capsule_surface > 0.0 {
if rng.gen_bool((rectangle_surface / capsule_surface) as f64) {
let side_distance =
rng.gen_range((-2.0 * self.half_length)..=(2.0 * self.half_length));
if side_distance < 0.0 {
Vec2::new(self.radius, side_distance + self.half_length)
} else {
Vec2::new(-self.radius, side_distance - self.half_length)
}
} else {
let circle = Circle::new(self.radius);
let point = circle.sample_boundary(rng);
// Add half length if it is the top semi-circle, otherwise subtract half
if point.y > 0.0 {
point + Vec2::Y * self.half_length
} else {
point - Vec2::Y * self.half_length
}
}
} else {
Vec2::ZERO
}
}
}
impl ShapeSample for Capsule3d {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let cylinder_vol = PI * self.radius * self.radius * 2.0 * self.half_length;
// Add 4/3 pi r^3
let capsule_vol = cylinder_vol + 4.0 / 3.0 * PI * self.radius * self.radius * self.radius;
if capsule_vol > 0.0 {
// Check if the random point should be inside the cylinder
if rng.gen_bool((cylinder_vol / capsule_vol) as f64) {
self.to_cylinder().sample_interior(rng)
} else {
let sphere = Sphere::new(self.radius);
let point = sphere.sample_interior(rng);
// Add half length if it is the top semi-sphere, otherwise subtract half
if point.y > 0.0 {
point + Vec3::Y * self.half_length
} else {
point - Vec3::Y * self.half_length
}
}
} else {
Vec3::ZERO
}
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Vec3 {
let cylinder_surface = TAU * self.radius * 2.0 * self.half_length;
let capsule_surface = cylinder_surface + 4.0 * PI * self.radius * self.radius;
if capsule_surface > 0.0 {
if rng.gen_bool((cylinder_surface / capsule_surface) as f64) {
let Vec2 { x, y: z } = Circle::new(self.radius).sample_boundary(rng);
let y = rng.gen_range(-self.half_length..=self.half_length);
Vec3::new(x, y, z)
} else {
let sphere = Sphere::new(self.radius);
let point = sphere.sample_boundary(rng);
// Add half length if it is the top semi-sphere, otherwise subtract half
if point.y > 0.0 {
point + Vec3::Y * self.half_length
} else {
point - Vec3::Y * self.half_length
}
}
} else {
Vec3::ZERO
}
}
}
impl<P: Primitive2d + Measured2d + ShapeSample<Output = Vec2>> ShapeSample for Extrusion<P> {
type Output = Vec3;
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let base_point = self.base_shape.sample_interior(rng);
let depth = rng.gen_range(-self.half_depth..self.half_depth);
base_point.extend(depth)
}
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
let base_area = self.base_shape.area();
let total_area = self.area();
let random = rng.gen_range(0.0..total_area);
match random {
x if x < base_area => self.base_shape.sample_interior(rng).extend(self.half_depth),
x if x < 2. * base_area => self
.base_shape
.sample_interior(rng)
.extend(-self.half_depth),
_ => self
.base_shape
.sample_boundary(rng)
.extend(rng.gen_range(-self.half_depth..self.half_depth)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
#[test]
fn circle_interior_sampling() {
let mut rng = ChaCha8Rng::from_seed(Default::default());
let circle = Circle::new(8.0);
let boxes = [
(-3.0, 3.0),
(1.0, 2.0),
(-1.0, -2.0),
(3.0, -2.0),
(1.0, -6.0),
(-3.0, -7.0),
(-7.0, -3.0),
(-6.0, 1.0),
];
let mut box_hits = [0; 8];
// Checks which boxes (if any) the sampled points are in
for _ in 0..5000 {
let point = circle.sample_interior(&mut rng);
for (i, box_) in boxes.iter().enumerate() {
if (point.x > box_.0 && point.x < box_.0 + 4.0)
&& (point.y > box_.1 && point.y < box_.1 + 4.0)
{
box_hits[i] += 1;
}
}
}
assert_eq!(
box_hits,
[396, 377, 415, 404, 366, 408, 408, 430],
"samples will occur across all array items at statistically equal chance"
);
}
#[test]
fn circle_boundary_sampling() {
let mut rng = ChaCha8Rng::from_seed(Default::default());
let circle = Circle::new(1.0);
let mut wedge_hits = [0; 8];
// Checks in which eighth of the circle each sampled point is in
for _ in 0..5000 {
let point = circle.sample_boundary(&mut rng);
let angle = ops::atan(point.y / point.x) + PI / 2.0;
let wedge = ops::floor(angle * 8.0 / PI) as usize;
wedge_hits[wedge] += 1;
}
assert_eq!(
wedge_hits,
[636, 608, 639, 603, 614, 650, 640, 610],
"samples will occur across all array items at statistically equal chance"
);
}
}

View File

@@ -0,0 +1,99 @@
//! This module holds local implementations of the [`Distribution`] trait for [`Standard`], which
//! allow certain Bevy math types (those whose values can be randomly generated without additional
//! input other than an [`Rng`]) to be produced using [`rand`]'s APIs. It also holds [`FromRng`],
//! an ergonomic extension to that functionality which permits the omission of type annotations.
//!
//! For instance:
//! ```
//! # use rand::{random, Rng, SeedableRng, rngs::StdRng, distributions::Standard};
//! # use bevy_math::{Dir3, sampling::FromRng};
//! let mut rng = StdRng::seed_from_u64(7313429298);
//! // Random direction using thread-local rng
//! let random_direction1: Dir3 = random();
//!
//! // Random direction using the rng constructed above
//! let random_direction2: Dir3 = rng.r#gen();
//!
//! // The same as the previous but with different syntax
//! let random_direction3 = Dir3::from_rng(&mut rng);
//!
//! // Five random directions, using Standard explicitly
//! let many_random_directions: Vec<Dir3> = rng.sample_iter(Standard).take(5).collect();
//! ```
use core::f32::consts::TAU;
use crate::{
primitives::{Circle, Sphere},
Dir2, Dir3, Dir3A, Quat, Rot2, ShapeSample, Vec3A,
};
use rand::{
distributions::{Distribution, Standard},
Rng,
};
/// Ergonomics trait for a type with a [`Standard`] distribution, allowing values to be generated
/// uniformly from an [`Rng`] by a method in its own namespace.
///
/// Example
/// ```
/// # use rand::{Rng, SeedableRng, rngs::StdRng};
/// # use bevy_math::{Dir3, sampling::FromRng};
/// let mut rng = StdRng::seed_from_u64(451);
/// let random_dir = Dir3::from_rng(&mut rng);
/// ```
pub trait FromRng
where
Self: Sized,
Standard: Distribution<Self>,
{
/// Construct a value of this type uniformly at random using `rng` as the source of randomness.
fn from_rng<R: Rng + ?Sized>(rng: &mut R) -> Self {
rng.r#gen()
}
}
impl Distribution<Dir2> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Dir2 {
let circle = Circle::new(1.0);
let vector = circle.sample_boundary(rng);
Dir2::new_unchecked(vector)
}
}
impl FromRng for Dir2 {}
impl Distribution<Dir3> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Dir3 {
let sphere = Sphere::new(1.0);
let vector = sphere.sample_boundary(rng);
Dir3::new_unchecked(vector)
}
}
impl FromRng for Dir3 {}
impl Distribution<Dir3A> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Dir3A {
let sphere = Sphere::new(1.0);
let vector: Vec3A = sphere.sample_boundary(rng).into();
Dir3A::new_unchecked(vector)
}
}
impl FromRng for Dir3A {}
impl Distribution<Rot2> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Rot2 {
let angle = rng.gen_range(0.0..TAU);
Rot2::radians(angle)
}
}
impl FromRng for Rot2 {}
impl FromRng for Quat {}