├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── bevy-natura ├── Cargo.toml └── src │ └── lib.rs ├── examples ├── bevy-simple │ ├── .cargo │ │ └── config.toml │ ├── Cargo.toml │ ├── assets │ │ └── fonts │ │ │ ├── PixelSmall.ttf │ │ │ └── PixelSmall.txt │ └── src │ │ └── main.rs ├── coffee-2d │ ├── Cargo.toml │ └── src │ │ └── main.rs └── simple │ ├── Cargo.toml │ └── src │ └── main.rs ├── misc ├── demo.gif └── natura-vegeta.png └── natura ├── Cargo.toml └── src ├── lib.rs ├── projectile.rs ├── spring.rs └── sprite.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .idea 17 | 18 | # find . -name .DS_Store -print0 | xargs -0 git rm -f --ignore-unmatch 19 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "natura", 4 | "bevy-natura", 5 | "examples/simple", 6 | "examples/coffee-2d", 7 | "examples/bevy-simple", 8 | ] 9 | 10 | # Err: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should. 11 | # https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions 12 | resolver = "2" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 O(ʒ) 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 | # Natura ![](misc/natura-vegeta.png) 2 | An opinionated, simple and efficient spring animation library for smooth, natural motion in Rust 3 | 4 | ![](misc/demo.gif) 5 | 6 | ## Usage 7 | 8 | Natura is framework-agnostic and works well in 2D and 3D contexts. Simply call [`Spring::new`](https://github.com/ziyasal/natura/blob/main/natura/src/spring.rs#L138) with your settings to initialize and [`update`](https://github.com/ziyasal/natura/blob/main/natura/src/spring.rs#L171) on each frame to animate. 9 | 10 | For details, see the [examples](/examples) 11 | 12 | ### Examples 13 | 14 | #### Example with [2D engine `coffee`](https://github.com/hecrj/coffee) 15 | `cargo run -p coffee-2d` 16 | 17 | #### Example with [`Bevy Engine`](https://github.com/bevyengine/bevy) 18 | `cargo run -p bevy-simple` 19 | 20 | **Enable Plugin:** 21 | 22 | ```rust 23 | use bevy_natura::{NaturaAnimationBundle, NaturaAnimationPlugin}; 24 | 25 | // omitted for brevity 26 | .add_plugin(NaturaAnimationPlugin::new( 27 | DeltaTime(60.0), 28 | AngularFrequency(6.0), 29 | DampingRatio(0.7), 30 | )) 31 | ``` 32 | 33 | Please see usage [here](https://github.com/ziyasal/natura/blob/main/examples/bevy-simple/src/main.rs#L44) 34 | 35 | #### Simple example 36 | 37 | `cargo run -p simple` 38 | 39 | **Code:** 40 | 41 | ```rust 42 | 43 | // Where we want to animate it. 44 | const TARGET_X: f64 = 50.0; 45 | const TARGET_Y: f64 = 100.0; 46 | 47 | fn main() { 48 | let mut sprite = Sprite::default(); 49 | 50 | // initialize a spring with frame-rate, angular frequency, and damping values. 51 | let mut spring = Spring::new(DeltaTime(natura::fps(60)), AngularFrequency(6.0), 0.5); 52 | 53 | loop { 54 | let (sprite_x, sprite_x_velocity) = spring.update(sprite.x, sprite.x_velocity, TARGET_X); 55 | sprite.x = sprite_x; 56 | sprite.x_velocity = sprite_x_velocity; 57 | 58 | let (sprite_y, sprite_y_velocity) = spring.update(sprite.y, sprite.y_velocity, TARGET_Y); 59 | sprite.y = sprite_y; 60 | sprite.y_velocity = sprite_y_velocity; 61 | 62 | sleep(Duration::from_millis(10000)); 63 | 64 | // use new position here on every frame 65 | println!( 66 | "Sprite x:{}, y:{}, x_vel:{}, y_vel:{}", 67 | sprite.x, sprite.y, sprite.x_velocity, sprite.y_velocity 68 | ) 69 | } 70 | } 71 | ``` 72 | 73 | 74 | ## Settings 75 | 76 | `Spring::new` takes three values: 77 | 78 | * **Time Delta:** the time step to operate on. Game engines typically provide 79 | a way to determine the time delta, however if that's not available you can 80 | simply set the framerate with the included [`fps(u64)`](https://github.com/ziyasal/natura/blob/main/natura/src/spring.rs#L105) utility function. Make 81 | the framerate you set here matches your actual framerate. 82 | * **Angular Velocity:** this translates roughly to the speed. Higher values are 83 | faster. 84 | * **Damping Ratio:** the springiness of the animation, generally between `0` 85 | and `1`, though it can go higher. Lower values are springier. For details, 86 | see below. 87 | 88 | ## Damping Ratios 89 | 90 | The damping ratio affects the motion in one of three different ways depending 91 | on how it's set. 92 | 93 | ### Under-Damping 94 | 95 | A spring is under-damped when its damping ratio is less than `1`. An 96 | under-damped spring reaches equilibrium the fastest, but overshoots and will 97 | continue to oscillate as its amplitude decays over time. 98 | 99 | ### Critical Damping 100 | 101 | A spring is critically-damped the damping ratio is exactly `1`. A critically 102 | damped spring will reach equilibrium as fast as possible without oscillating. 103 | 104 | ### Over-Damping 105 | 106 | A spring is over-damped the damping ratio is greater than `1`. An over-damped 107 | spring will never oscillate, but reaches equilibrium at a slower rate than 108 | a critically damped spring. 109 | 110 | ## Acknowledgements 111 | 112 | This library is a fairly straightforward port of [Ryan Juckett][juckett]’s 113 | excellent damped simple harmonic oscillator originally writen in C++ in 2008 114 | and published in 2012. [Ryan’s writeup][writeup] on the subject is fantastic. 115 | 116 | [juckett]: https://www.ryanjuckett.com/ 117 | [writeup]: https://www.ryanjuckett.com/damped-springs/ 118 | 119 | ## License 120 | 121 | [UNLICENSE](https://github.com/ziyasal/pmecs/blob/main/LICENSE) 122 | 123 | > _This crate is developed to be part of Λ.R.Ξ.N.Λ 2D game engine._ 124 | -------------------------------------------------------------------------------- /bevy-natura/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy-natura" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | bevy = { version = "0.6.1", default-features = false } 10 | natura = { path = "../natura", version = "0.1.0" } 11 | -------------------------------------------------------------------------------- /bevy-natura/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use natura::{ 3 | Spring, 4 | DeltaTime, 5 | AngularFrequency, 6 | DampingRatio, 7 | Sprite as NaturaSprite, 8 | }; 9 | 10 | pub struct NaturaAnimationPlugin { 11 | delta_time: DeltaTime, 12 | angular_frequency: AngularFrequency, 13 | damping_ratio: DampingRatio, 14 | } 15 | 16 | #[derive(Default)] 17 | pub struct NaturaAnimationBundle { 18 | pub sprite: NaturaSprite, 19 | pub spring: Spring, 20 | } 21 | 22 | impl NaturaAnimationPlugin { 23 | #[must_use] 24 | pub fn new( 25 | delta_time: DeltaTime, 26 | angular_frequency: AngularFrequency, 27 | damping_ratio: DampingRatio, 28 | ) -> NaturaAnimationPlugin { 29 | NaturaAnimationPlugin { 30 | delta_time, 31 | angular_frequency, 32 | damping_ratio, 33 | } 34 | } 35 | } 36 | 37 | impl Plugin for NaturaAnimationPlugin { 38 | fn build(&self, app: &mut App) { 39 | let sprite = NaturaSprite::default(); 40 | 41 | // Initialize a spring with frame-rate, angular frequency, and damping values. 42 | let spring = Spring::new( 43 | DeltaTime(natura::fps(self.delta_time.0 as u64)), 44 | self.angular_frequency.clone(), 45 | self.damping_ratio.clone(), 46 | ); 47 | // add things to your app here 48 | app.insert_resource(NaturaAnimationBundle { sprite, spring }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/bevy-simple/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # Rename this file to `config.toml` to enable "fast build" configuration. Please read the notes below. 2 | 3 | # NOTE: For maximum performance, build using a nightly compiler 4 | # If you are using rust stable, remove the "-Zshare-generics=y" below. 5 | 6 | [target.x86_64-unknown-linux-gnu] 7 | linker = "/usr/bin/clang" 8 | rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] 9 | 10 | # NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager: 11 | # `brew install michaeleisel/zld/zld` 12 | [target.x86_64-apple-darwin] 13 | rustflags = ["-Clink-arg=-fuse-ld=/usr/local/bin/zld", "-Csplit-debuginfo=unpacked", "-Zshare-generics=y"] 14 | 15 | [target.aarch64-apple-darwin] 16 | rustflags = ["-Clink-arg=-fuse-ld=/usr/local/bin/zld", "-Csplit-debuginfo=unpacked", "-Zshare-generics=y"] 17 | 18 | [target.x86_64-pc-windows-msvc] 19 | linker = "rust-lld.exe" 20 | rustflags = ["-Zshare-generics=y"] 21 | 22 | # Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only' 23 | # In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains. 24 | #[profile.dev] 25 | #debug = 1 26 | -------------------------------------------------------------------------------- /examples/bevy-simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy-simple" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | # Compile all the *dependencies* in optimized release mode even if `--release` is not passed in 9 | [profile.dev] 10 | opt-level = 3 11 | debug = false 12 | debug-assertions = false 13 | overflow-checks = false 14 | incremental = false 15 | codegen-units = 16 16 | 17 | # But compile the project itself in debug mode if `--release` is not passed in 18 | [profile.dev.package.bevy-simple] 19 | opt-level = 0 20 | debug = true 21 | debug-assertions = true 22 | overflow-checks = true 23 | incremental = true 24 | codegen-units = 256 25 | 26 | [dependencies] 27 | bevy = "0.6.1" 28 | natura = { path = "../../natura", version = "0.1.0" } 29 | bevy-natura = { path = "../../bevy-natura", version = "0.1.0" } 30 | -------------------------------------------------------------------------------- /examples/bevy-simple/assets/fonts/PixelSmall.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugthesystem/natura/4779f22716c8c52050d7241dc4273baaf488c5d1/examples/bevy-simple/assets/fonts/PixelSmall.ttf -------------------------------------------------------------------------------- /examples/bevy-simple/assets/fonts/PixelSmall.txt: -------------------------------------------------------------------------------- 1 | PixelSmall.ttf is free for personal and commercial use as per the license on the download page 2 | accessed on 21 Feb 2022 https://lana-ro.itch.io/sra-free-pixel-font-pack 3 | -------------------------------------------------------------------------------- /examples/bevy-simple/src/main.rs: -------------------------------------------------------------------------------- 1 | use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}; 2 | use bevy::prelude::*; 3 | use bevy_natura::{NaturaAnimationBundle, NaturaAnimationPlugin}; 4 | use natura::{AngularFrequency, DampingRatio, DeltaTime}; 5 | 6 | // Where we want to animate it. 7 | const TARGET_X: f64 = 40.0; 8 | const TARGET_Y: f64 = 200.0; 9 | 10 | fn main() { 11 | App::new() 12 | .add_plugins(DefaultPlugins) 13 | .add_plugin(NaturaAnimationPlugin::new( 14 | DeltaTime(60.0), 15 | AngularFrequency(6.0), 16 | DampingRatio(0.7), 17 | )) 18 | .add_plugin(LogDiagnosticsPlugin::default()) 19 | .add_plugin(FrameTimeDiagnosticsPlugin::default()) 20 | .add_startup_system(template_setup) 21 | .add_system(template_animation) 22 | .run(); 23 | } 24 | 25 | fn template_setup(mut commands: Commands, asset_server: Res) { 26 | commands.spawn_bundle(OrthographicCameraBundle::new_2d()); 27 | commands.spawn_bundle(Text2dBundle { 28 | text: Text::with_section( 29 | "Natura: Shall we play a game Bevy?", 30 | TextStyle { 31 | font: asset_server.load("fonts/PixelSmall.ttf"), 32 | font_size: 58.0, 33 | color: Color::WHITE, 34 | }, 35 | TextAlignment { 36 | vertical: VerticalAlign::Center, 37 | horizontal: HorizontalAlign::Center, 38 | }, 39 | ), 40 | ..Default::default() 41 | }); 42 | } 43 | 44 | fn template_animation( 45 | _: Res