├── .github ├── FUNDING.yml ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── clippy.toml ├── examples ├── extrapolation.rs ├── hermite_interpolation.rs └── interpolation.rs └── src ├── extrapolation.rs ├── hermite.rs ├── interpolation.rs └── lib.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Jondolf] 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Objective 2 | 3 | - Describe the objective or issue this PR addresses. 4 | - If you're fixing a specific issue, say "Fixes #X". 5 | 6 | ## Solution 7 | 8 | - Describe the solution used to achieve the objective above. 9 | 10 | --- 11 | 12 | ## Changelog 13 | 14 | > This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section. 15 | 16 | - What changed as a result of this PR? 17 | - If applicable, organize changes under "Added", "Changed", or "Fixed" sub-headings 18 | - Stick to one or two sentences. If more detail is needed for a particular change, consider adding it to the "Solution" section 19 | - If you can't summarize the work, your change may be unreasonably large / unrelated. Consider splitting your PR to make it easier to review and merge! 20 | 21 | ## Migration Guide 22 | 23 | > This section is optional. If there are no breaking changes, you can delete this section. 24 | 25 | - If this PR is a breaking change (relative to the last release), describe how a user might need to migrate their code to support these changes. 26 | - Simply adding new functionality is not a breaking change. 27 | - Fixing behavior that was definitely a bug, rather than a questionable design choice is not a breaking change. 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check: 14 | name: Check 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: dtolnay/rust-toolchain@stable 19 | - name: Run cargo check 20 | run: cargo check 21 | 22 | check-compiles-no-std: 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | target: 28 | - "x86_64-unknown-none" 29 | - "wasm32v1-none" 30 | - "thumbv6m-none-eabi" 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: dtolnay/rust-toolchain@stable 34 | with: 35 | targets: ${{ matrix.target }} 36 | - name: Run cargo check 37 | run: cargo check --no-default-features --features libm,critical-section --target ${{ matrix.target }} 38 | 39 | test: 40 | name: Test Suite 41 | strategy: 42 | matrix: 43 | os: [windows-latest, ubuntu-latest, macos-latest] 44 | runs-on: ${{ matrix.os }} 45 | timeout-minutes: 60 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: dtolnay/rust-toolchain@stable 49 | - name: Run cargo test 50 | run: cargo test --all-features 51 | 52 | lints: 53 | name: Lints 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v4 57 | - uses: dtolnay/rust-toolchain@stable 58 | - name: Run cargo fmt 59 | run: cargo fmt --all -- --check 60 | - name: Run cargo clippy 61 | run: cargo clippy -- -D warnings 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_transform_interpolation" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Joona Aalto "] 7 | description = "Transform interpolation for fixed timesteps for the Bevy game engine" 8 | documentation = "https://docs.rs/bevy_transform_interpolation" 9 | repository = "https://github.com/Jondolf/bevy_transform_interpolation" 10 | readme = "README.md" 11 | keywords = ["gamedev", "interpolation", "easing", "bevy"] 12 | categories = ["game-development"] 13 | 14 | [features] 15 | default = ["std"] 16 | 17 | # Enable data serialization/deserialization using `serde`. 18 | serialize = ["dep:serde", "bevy/serialize"] 19 | 20 | # Enable the Rust standard library. 21 | std = ["bevy/std"] 22 | 23 | # Enable `libm` math operations for `no_std` environments and cross-platform determinism. 24 | libm = ["bevy/libm"] 25 | 26 | # Rely on `critical-section` for synchronization primitives. 27 | critical-section = ["bevy/critical-section"] 28 | 29 | [dependencies] 30 | bevy = { version = "0.16", default-features = false } 31 | 32 | # Serialization 33 | serde = { version = "1.0", default-features = false, optional = true } 34 | 35 | [dev-dependencies] 36 | bevy = { version = "0.16", default-features = false, features = [ 37 | "bevy_core_pipeline", 38 | "bevy_text", 39 | "bevy_ui", 40 | "bevy_asset", 41 | "bevy_render", 42 | "bevy_sprite", 43 | "default_font", 44 | "bevy_winit", 45 | "bevy_window", 46 | "x11", 47 | ] } 48 | 49 | [lints.clippy] 50 | std_instead_of_core = "warn" 51 | std_instead_of_alloc = "warn" 52 | alloc_instead_of_core = "warn" 53 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jondolf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `bevy_transform_interpolation` 2 | 3 | [![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/Jondolf/bevy_transform_interpolation#license) 4 | [![ci](https://github.com/Jondolf/bevy_transform_interpolation/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Jondolf/bevy_transform_interpolation/actions/workflows/ci.yml) 5 | [![crates.io](https://img.shields.io/crates/v/bevy_transform_interpolation?label=crates.io)](https://crates.io/crates/bevy_transform_interpolation) 6 | [![docs.rs](https://img.shields.io/docsrs/bevy_transform_interpolation?label=docs.rs)](https://docs.rs/bevy_transform_interpolation) 7 | 8 | A drop-in `Transform` interpolation solution for fixed timesteps for the [Bevy game engine](https://bevyengine.org). 9 | 10 | ## What Is This For? 11 | 12 | A lot of gameplay logic and movement systems typically use a fixed timestep to produce consistent and stable behavior 13 | regardless of the frame rate. Notable examples include physics simulation and character movement. 14 | 15 | However, this can make movement appear choppy, especially on displays with a high refresh rate. 16 | To achieve visually smooth movement while using a fixed timestep, the visual transform must be smoothed 17 | independently of the "true" gameplay transform. 18 | 19 | The most common way to do this is to use **transform interpolation**, which interpolates movement from the previous 20 | state to the current state. This could be done by storing the current and old gameplay positions in their own components 21 | and interpolating `Transform` using them: 22 | 23 | ```rust 24 | use bevy::prelude::*; 25 | 26 | #[derive(Component, Deref, DerefMut)] 27 | struct Position(Vec3); 28 | 29 | #[derive(Component, Deref, DerefMut)] 30 | struct OldPosition(Vec3); 31 | 32 | fn interpolate_transforms( 33 | mut query: Query<(&mut Transform, &Position, &OldPosition)>, 34 | fixed_time: Res> 35 | ) { 36 | // How much of a "partial timestep" has accumulated since the last fixed timestep run. 37 | // Between `0.0` and `1.0`. 38 | let overstep = fixed_time.overstep_fraction(); 39 | 40 | for (mut transform, position, old_position) in &mut query { 41 | // Linearly interpolate the translation from the old position to the current one. 42 | transform.translation = old_position.lerp(position.0, overstep_fraction); 43 | } 44 | } 45 | ``` 46 | 47 | In fact, you could simply plug the above implementation into your own application if you wanted to! 48 | 49 | However, it requires you to use `Position` for gameplay logic, and to manage `OldPosition` somewhere. 50 | This can be annoying, and is incompatible with third party libraries that expect to be able to modify 51 | the transform directly. 52 | 53 | `bevy_transform_interpolation` aims to be a drop-in solution that allows easy and efficient transform interpolation, 54 | while still allowing the usage of `Transform` for gameplay logic. It should be automatically compatible with physics engines 55 | such as [Avian](https://github.com/Jondolf/avian) and [`bevy_rapier`](https://github.com/dimforge/bevy_rapier), as long as 56 | the simulation is run in `FixedUpdate` or `FixedPostUpdate`. 57 | 58 | ## Getting Started 59 | 60 | First, add `bevy_transform_interpolation` to your dependencies in `Cargo.toml`: 61 | 62 | ```toml 63 | [dependencies] 64 | bevy_transform_interpolation = "0.1" 65 | ``` 66 | 67 | To enable `Transform` interpolation, add the `TransformInterpolationPlugin` to your app: 68 | 69 | ```rust 70 | use bevy::prelude::*; 71 | use bevy_transform_interpolation::prelude::*; 72 | 73 | fn main() { 74 | App::new() 75 | .add_plugins((DefaultPlugins, TransformInterpolationPlugin::default())) 76 | // ...other plugins, resources, and systems 77 | .run(); 78 | } 79 | ``` 80 | 81 | By default, interpolation is only performed for entities with the `TransformInterpolation` component: 82 | 83 | ```rust 84 | fn setup(mut commands: Commands) { 85 | // Interpolate the entire transform: translation, rotation, and scale. 86 | commands.spawn(( 87 | Transform::default(), 88 | TransformInterpolation, 89 | )); 90 | } 91 | ``` 92 | 93 | Now, any changes made to the `Transform` of the entity in `FixedPreUpdate`, `FixedUpdate`, or `FixedPostUpdate` 94 | will automatically be interpolated in between fixed timesteps. 95 | 96 | If you want *all* entities with a `Transform` to be interpolated by default, you can use 97 | `TransformInterpolationPlugin::interpolate_all()`: 98 | 99 | ```rust 100 | fn main() { 101 | App::new() 102 | .add_plugins(TransformInterpolationPlugin::interpolate_all()) 103 | // ... 104 | .run(); 105 | } 106 | ``` 107 | 108 | ## Advanced Usage 109 | 110 | For a lot of applications, the functionality shown in the Getting Started guide might be all you need! 111 | However, `bevy_transform_interpolation` has a lot more to offer: 112 | 113 | - Granularly ease individual properties of the transform with `TranslationInterpolation`, `RotationInterpolation`, and `ScaleInterpolation`. 114 | - Opt out of transform easing for individual entities with `NoTranslationEasing`, `NoRotationEasing`, and `NoScaleEasing`. 115 | - Use extrapolation instead of interpolation with the `TransformExtrapolationPlugin` and its related components. 116 | - Use Hermite interpolation for more natural and accurate movement with the `TransformHermiteEasingPlugin`. 117 | - Implement custom easing backends for your specific needs. 118 | 119 | ## How Does It Work? 120 | 121 | Internally, `bevy_transform_interpolation` simply maintains components that store the `start` and `end` of the interpolation. 122 | For example, translation uses the following component for easing the movement: 123 | 124 | ```rust 125 | pub struct TranslationEasingState { 126 | pub start: Option, 127 | pub end: Option, 128 | } 129 | ``` 130 | 131 | The states are updated by the `TransformInterpolationPlugin` or `TransformExtrapolationPlugin` 132 | depending on whether the entity has `TransformInterpolation` or `TransformExtrapolation` components. 133 | 134 | If interpolation is used: 135 | 136 | - In `FixedFirst`, `start` is set to the current `Transform`. 137 | - In `FixedLast`, `end` is set to the current `Transform`. 138 | 139 | If extrapolation is used: 140 | 141 | - In `FixedLast`, `start` is set to the current `Transform`, and `end` is set to the `Transform` predicted based on velocity. 142 | 143 | At the start of the `FixedFirst` schedule, the states are reset to `None`. If the `Transform` is detected to have changed 144 | since the last easing run but *outside* of the fixed timestep schedules, the easing is also reset to `None` to prevent overwriting the change. 145 | 146 | The actual easing is performed in `RunFixedMainLoop`, right after `FixedMain`, before `Update`. 147 | By default, linear interpolation (`lerp`) is used for translation and scale, and spherical linear interpolation (`slerp`) 148 | is used for rotation. 149 | 150 | However, thanks to the modular and flexible architecture, other easing methods can also be used. 151 | The `TransformHermiteEasingPlugin` provides an easing backend using Hermite interpolation, 152 | overwriting the linear interpolation for specific entities with the `NonlinearTranslationEasing` 153 | and `NonlinearRotationEasing` marker components. Custom easing solutions can be implemented using the same pattern. 154 | 155 | ## Supported Bevy Versions 156 | 157 | | `bevy` | `bevy_transform_interpolation` | 158 | | ------- | ------------------------------ | 159 | | 0.16 | 0.2 | 160 | | 0.15 | 0.1 | 161 | 162 | ## License 163 | 164 | `bevy_transform_interpolation` is free and open source. All code in this repository is dual-licensed under either: 165 | 166 | - MIT License ([LICENSE-MIT](/LICENSE-MIT) or ) 167 | - Apache License, Version 2.0 ([LICENSE-APACHE](/LICENSE-APACHE) or ) 168 | 169 | at your option. 170 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-methods = [ 2 | { path = "f32::powi", reason = "use ops::FloatPow::squared, ops::FloatPow::cubed, or ops::powf instead for libm determinism" }, 3 | { path = "f32::log", reason = "use ops::ln, ops::log2, or ops::log10 instead for libm determinism" }, 4 | { path = "f32::abs_sub", reason = "deprecated and deeply confusing method" }, 5 | { path = "f32::powf", reason = "use ops::powf instead for libm determinism" }, 6 | { path = "f32::exp", reason = "use ops::exp instead for libm determinism" }, 7 | { path = "f32::exp2", reason = "use ops::exp2 instead for libm determinism" }, 8 | { path = "f32::ln", reason = "use ops::ln instead for libm determinism" }, 9 | { path = "f32::log2", reason = "use ops::log2 instead for libm determinism" }, 10 | { path = "f32::log10", reason = "use ops::log10 instead for libm determinism" }, 11 | { path = "f32::cbrt", reason = "use ops::cbrt instead for libm determinism" }, 12 | { path = "f32::hypot", reason = "use ops::hypot instead for libm determinism" }, 13 | { path = "f32::sin", reason = "use ops::sin instead for libm determinism" }, 14 | { path = "f32::cos", reason = "use ops::cos instead for libm determinism" }, 15 | { path = "f32::tan", reason = "use ops::tan instead for libm determinism" }, 16 | { path = "f32::asin", reason = "use ops::asin instead for libm determinism" }, 17 | { path = "f32::acos", reason = "use ops::acos instead for libm determinism" }, 18 | { path = "f32::atan", reason = "use ops::atan instead for libm determinism" }, 19 | { path = "f32::atan2", reason = "use ops::atan2 instead for libm determinism" }, 20 | { path = "f32::sin_cos", reason = "use ops::sin_cos instead for libm determinism" }, 21 | { path = "f32::exp_m1", reason = "use ops::exp_m1 instead for libm determinism" }, 22 | { path = "f32::ln_1p", reason = "use ops::ln_1p instead for libm determinism" }, 23 | { path = "f32::sinh", reason = "use ops::sinh instead for libm determinism" }, 24 | { path = "f32::cosh", reason = "use ops::cosh instead for libm determinism" }, 25 | { path = "f32::tanh", reason = "use ops::tanh instead for libm determinism" }, 26 | { path = "f32::asinh", reason = "use ops::asinh instead for libm determinism" }, 27 | { path = "f32::acosh", reason = "use ops::acosh instead for libm determinism" }, 28 | { path = "f32::atanh", reason = "use ops::atanh instead for libm determinism" }, 29 | # These methods have defined precision, but are only available from the standard library, 30 | # not in core. Using these substitutes allows for no_std compatibility. 31 | { path = "f32::rem_euclid", reason = "use ops::rem_euclid instead for no_std compatibility" }, 32 | { path = "f32::abs", reason = "use ops::abs instead for no_std compatibility" }, 33 | { path = "f32::sqrt", reason = "use ops::sqrt instead for no_std compatibility" }, 34 | { path = "f32::copysign", reason = "use ops::copysign instead for no_std compatibility" }, 35 | { path = "f32::round", reason = "use ops::round instead for no_std compatibility" }, 36 | { path = "f32::floor", reason = "use ops::floor instead for no_std compatibility" }, 37 | { path = "f32::fract", reason = "use ops::fract instead for no_std compatibility" }, 38 | ] 39 | -------------------------------------------------------------------------------- /examples/extrapolation.rs: -------------------------------------------------------------------------------- 1 | //! This example showcases how `Transform` extrapolation can be used to make movement 2 | //! appear smooth at fixed timesteps. 3 | //! 4 | //! Unlike `Transform` interpolation, which eases between the previous and current positions, 5 | //! `Transform` extrapolation predicts future positions based on velocity. This makes movement 6 | //! feel more responsive than interpolation, but it also produces jumpy results when the prediction is wrong, 7 | //! such as when the velocity of an object suddenly changes. 8 | //! 9 | //! For an example of how transform interpolation could be used instead, 10 | //! see `examples/interpolation.rs`. 11 | 12 | use bevy::{ 13 | color::palettes::{ 14 | css::WHITE, 15 | tailwind::{CYAN_400, LIME_400, RED_400}, 16 | }, 17 | ecs::query::QueryData, 18 | prelude::*, 19 | }; 20 | use bevy_transform_interpolation::{ 21 | extrapolation::{TransformExtrapolation, TransformExtrapolationPlugin}, 22 | prelude::*, 23 | VelocitySource, 24 | }; 25 | 26 | const MOVEMENT_SPEED: f32 = 250.0; 27 | const ROTATION_SPEED: f32 = 2.0; 28 | 29 | fn main() { 30 | let mut app = App::new(); 31 | 32 | // Add the `TransformInterpolationPlugin` and `TransformExtrapolationPlugin` to the app to enable 33 | // transform interpolation and extrapolation. 34 | app.add_plugins(( 35 | DefaultPlugins, 36 | TransformInterpolationPlugin::default(), 37 | // We must specify "velocity sources" to tell the plugin how to extract velocity information. 38 | // These are implemented below this function. 39 | TransformExtrapolationPlugin::::default(), 40 | )); 41 | 42 | // Set the fixed timestep to just 5 Hz for demonstration purposes. 43 | app.insert_resource(Time::::from_hz(5.0)); 44 | 45 | // Setup the scene and UI, and update text in `Update`. 46 | app.add_systems(Startup, (setup, setup_text)) 47 | .add_systems(Update, (change_timestep, update_timestep_text)); 48 | 49 | // Move entities in `FixedUpdate`. The movement should appear smooth for interpolated/extrapolated entities. 50 | app.add_systems( 51 | FixedUpdate, 52 | (flip_movement_direction.before(movement), movement, rotate), 53 | ); 54 | 55 | // Run the app. 56 | app.run(); 57 | } 58 | 59 | /// The linear velocity of an entity indicating its movement speed and direction. 60 | #[derive(Component, Deref, DerefMut)] 61 | struct LinearVelocity(Vec2); 62 | 63 | /// The angular velocity of an entity indicating its rotation speed. 64 | #[derive(Component, Deref, DerefMut)] 65 | struct AngularVelocity(f32); 66 | 67 | #[derive(QueryData)] 68 | struct LinVelSource; 69 | 70 | impl VelocitySource for LinVelSource { 71 | // Components storing the previous and current velocities. 72 | // Note: For extrapolation, the `Previous` component is not used, so we can make it the same as `Current`. 73 | type Previous = LinearVelocity; 74 | type Current = LinearVelocity; 75 | 76 | fn previous(start: &Self::Previous) -> Vec3 { 77 | start.0.extend(0.0) 78 | } 79 | 80 | fn current(end: &Self::Current) -> Vec3 { 81 | end.0.extend(0.0) 82 | } 83 | } 84 | 85 | #[derive(QueryData)] 86 | struct AngVelSource; 87 | 88 | impl VelocitySource for AngVelSource { 89 | type Previous = AngularVelocity; 90 | type Current = AngularVelocity; 91 | 92 | fn previous(start: &Self::Previous) -> Vec3 { 93 | Vec3::Z * start.0 94 | } 95 | 96 | fn current(end: &Self::Current) -> Vec3 { 97 | Vec3::Z * end.0 98 | } 99 | } 100 | 101 | fn setup( 102 | mut commands: Commands, 103 | mut materials: ResMut>, 104 | mut meshes: ResMut>, 105 | ) { 106 | // Spawn a camera. 107 | commands.spawn(Camera2d); 108 | 109 | let mesh = meshes.add(Rectangle::from_length(60.0)); 110 | 111 | // This entity uses transform interpolation. 112 | commands.spawn(( 113 | Name::new("Interpolation"), 114 | Mesh2d(mesh.clone()), 115 | MeshMaterial2d(materials.add(Color::from(CYAN_400)).clone()), 116 | Transform::from_xyz(-500.0, 120.0, 0.0), 117 | TransformInterpolation, 118 | LinearVelocity(Vec2::new(MOVEMENT_SPEED, 0.0)), 119 | AngularVelocity(ROTATION_SPEED), 120 | )); 121 | 122 | // This entity uses transform extrapolation. 123 | commands.spawn(( 124 | Name::new("Extrapolation"), 125 | Mesh2d(mesh.clone()), 126 | MeshMaterial2d(materials.add(Color::from(LIME_400)).clone()), 127 | Transform::from_xyz(-500.0, 00.0, 0.0), 128 | TransformExtrapolation, 129 | LinearVelocity(Vec2::new(MOVEMENT_SPEED, 0.0)), 130 | AngularVelocity(ROTATION_SPEED), 131 | )); 132 | 133 | // This entity is simulated in `FixedUpdate` without any smoothing. 134 | commands.spawn(( 135 | Name::new("No Interpolation"), 136 | Mesh2d(mesh.clone()), 137 | MeshMaterial2d(materials.add(Color::from(RED_400)).clone()), 138 | Transform::from_xyz(-500.0, -120.0, 0.0), 139 | LinearVelocity(Vec2::new(MOVEMENT_SPEED, 0.0)), 140 | AngularVelocity(ROTATION_SPEED), 141 | )); 142 | } 143 | 144 | /// Flips the movement directions of objects when they reach the left or right side of the screen. 145 | fn flip_movement_direction(mut query: Query<(&Transform, &mut LinearVelocity)>) { 146 | for (transform, mut lin_vel) in &mut query { 147 | if transform.translation.x > 500.0 && lin_vel.0.x > 0.0 { 148 | lin_vel.0 = Vec2::new(-MOVEMENT_SPEED, 0.0); 149 | } else if transform.translation.x < -500.0 && lin_vel.0.x < 0.0 { 150 | lin_vel.0 = Vec2::new(MOVEMENT_SPEED, 0.0); 151 | } 152 | } 153 | } 154 | 155 | /// Changes the timestep of the simulation when the up or down arrow keys are pressed. 156 | fn change_timestep(mut time: ResMut>, keyboard_input: Res>) { 157 | if keyboard_input.pressed(KeyCode::ArrowUp) { 158 | let new_timestep = (time.delta_secs_f64() * 0.9).max(1.0 / 255.0); 159 | time.set_timestep_seconds(new_timestep); 160 | } 161 | if keyboard_input.pressed(KeyCode::ArrowDown) { 162 | let new_timestep = (time.delta_secs_f64() * 1.1).min(1.0); 163 | time.set_timestep_seconds(new_timestep); 164 | } 165 | } 166 | 167 | /// Moves entities based on their `LinearVelocity`. 168 | fn movement(mut query: Query<(&mut Transform, &LinearVelocity)>, time: Res