diff --git a/Cargo.lock b/Cargo.lock index 1bf7c0c..4180317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,7 +85,7 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy 0.7.35", @@ -718,7 +718,7 @@ dependencies = [ "derive_more", "glam", "itertools", - "rand", + "rand 0.8.5", "rand_distr", "serde", "smallvec", @@ -1051,7 +1051,7 @@ checksum = "63c2174d43a0de99f863c98a472370047a2bfa7d1e5cec8d9d647fb500905d9d" dependencies = [ "ahash", "bevy_utils_proc_macros", - "getrandom", + "getrandom 0.2.15", "hashbrown 0.14.5", "thread_local", "tracing", @@ -1409,7 +1409,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] @@ -1606,6 +1606,7 @@ dependencies = [ "avian2d", "bevy", "log", + "rand 0.9.0", ] [[package]] @@ -1882,10 +1883,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -1904,7 +1917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" dependencies = [ "bytemuck", - "rand", + "rand 0.8.5", "serde", ] @@ -3036,6 +3049,12 @@ 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 = "radsort" version = "0.1.1" @@ -3049,8 +3068,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", ] [[package]] @@ -3060,7 +3090,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -3069,7 +3109,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", ] [[package]] @@ -3079,7 +3128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -3833,7 +3882,7 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ - "getrandom", + "getrandom 0.2.15", "serde", ] @@ -3865,6 +3914,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.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" @@ -4480,6 +4538,15 @@ 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 2.9.0", +] + [[package]] name = "xcursor" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 4a3b943..68b934e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,4 @@ log = { version = "*", features = [ "max_level_debug", "release_max_level_warn", ] } +rand = "0.9.0" diff --git a/src/main.rs b/src/main.rs index 2f15d49..7878fc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,152 +1,145 @@ -//! This example showcases how `Transform` interpolation or extrapolation can be used -//! to make movement appear smooth at fixed timesteps. -//! -//! To produce consistent, frame rate independent behavior, physics by default runs -//! in the `FixedPostUpdate` schedule with a fixed timestep, meaning that the time between -//! physics ticks remains constant. On some frames, physics can either not run at all or run -//! more than once to catch up to real time. This can lead to visible stutter for movement. -//! -//! `Transform` interpolation resolves this issue by updating `Transform` at every frame in between -//! physics ticks to smooth out the visual result. The interpolation is done from the previous position -//! to the current physics position, which keeps movement smooth, but has the downside of making movement -//! feel slightly delayed as the rendered result lags slightly behind the true positions. -//! -//! `Transform` extrapolation works similarly, but instead of using the previous positions, it predicts -//! the next positions based on velocity. This makes movement feel much more responsive, but can cause -//! jumpy results when the prediction is wrong, such as when the velocity of an object is suddenly altered. +use std::f32::consts::PI; -use avian2d::{math::*, prelude::*}; +use avian2d::{math::Vector, prelude::*}; use bevy::{ color::palettes::{ css::WHITE, - tailwind::{CYAN_400, LIME_400, RED_400}, + tailwind::{LIME_400, RED_400}, }, input::common_conditions::input_pressed, prelude::*, }; +use rand::Rng; + +const AREA_WIDTH: f32 = 750.; +const PLAYER_SIZE: f32 = 30.; fn main() { - let mut app = App::new(); - - // Interpolation and extrapolation functionality is enabled by the `PhysicsInterpolationPlugin`. - // It is included in the `PhysicsPlugins` by default. - app.add_plugins(( - DefaultPlugins, - PhysicsPlugins::default().with_length_unit(50.0), - )); - - // By default, interpolation must be enabled for each entity manually - // by adding the `TransformInterpolation` component. - // - // It can also be enabled for all rigid bodies with `PhysicsInterpolationPlugin::interpolate_all()`: - // - // app.add_plugins(PhysicsPlugins::default().set(PhysicsInterpolationPlugin::interpolate_all())); - - // Set gravity. - app.insert_resource(Gravity(Vector::NEG_Y * 900.0)); - - // Set the fixed timestep to just 10 Hz for demonstration purposes. - app.insert_resource(Time::from_hz(10.0)); - - // Setup the scene and UI, and update text in `Update`. - app.add_systems(Startup, (setup_scene, setup_balls)) + App::new() + .add_plugins(( + DefaultPlugins, + PhysicsPlugins::default() + .with_length_unit(50.0) + .set(PhysicsInterpolationPlugin::interpolate_all()), + )) + .add_systems( + Startup, + (setup_scene, setup_player, setup_balls, setup_walls), + ) .add_systems( Update, - ( - change_timestep, - // Reset the scene when the 'R' key is pressed. - reset_balls.run_if(input_pressed(KeyCode::KeyR)), - ), - ); - - // Run the app. - app.run(); + (move_player, quit.run_if(input_pressed(KeyCode::KeyQ))), + ) + .insert_resource(Gravity(Vector::ZERO)) + .run(); } -#[derive(Component)] -struct Ball; - -fn setup_scene( - mut commands: Commands, - mut materials: ResMut>, - mut meshes: ResMut>, -) { - // Spawn a camera. +fn setup_scene(mut commands: Commands) { commands.spawn(Camera2d); - - // Spawn the ground. - commands.spawn(( - Name::new("Ground"), - RigidBody::Static, - Collider::rectangle(500.0, 20.0), - Restitution::new(0.99).with_combine_rule(CoefficientCombine::Max), - Transform::from_xyz(0.0, -300.0, 0.0), - Mesh2d(meshes.add(Rectangle::new(500.0, 20.0))), - MeshMaterial2d(materials.add(Color::from(WHITE))), - )); } +#[derive(Component, Default)] +#[require(Collider, Mesh2d, MeshMaterial2d, Restitution(|| Restitution::new(1.0)), RigidBody, TransformInterpolation, Transform)] +struct GameObject; + +#[derive(Component, Default)] +#[require(GameObject, RigidBody(|| RigidBody::Dynamic))] +struct Ball; + fn setup_balls( mut commands: Commands, mut materials: ResMut>, mut meshes: ResMut>, ) { - let circle = Circle::new(30.0); - let mesh = meshes.add(circle); + let mut rng = rand::rng(); + for _ in 0..20 { + let circle = Circle::new(rng.random_range(10.0..(PLAYER_SIZE - 5.))); + let mut transform = Transform::from_xyz( + 0., + rng.random_range((PLAYER_SIZE + 5.)..(AREA_WIDTH / 2. - PLAYER_SIZE)), + 0., + ); + transform.rotate_around( + Vec3::ZERO, + Quat::from_rotation_z(rng.random_range(0.0..(PI * 2.))), + ); + commands.spawn(( + Ball, + Collider::from(circle), + Mesh2d(meshes.add(circle)), + MeshMaterial2d(materials.add(Color::from(RED_400))), + transform, + )); + } +} - // This entity uses transform interpolation. - commands.spawn(( - Name::new("Interpolation"), - Ball, - RigidBody::Dynamic, - Collider::from(circle), - TransformInterpolation, - Transform::from_xyz(-100.0, 300.0, 0.0), - Mesh2d(mesh.clone()), - MeshMaterial2d(materials.add(Color::from(CYAN_400)).clone()), - )); +#[derive(Component, Default)] +#[require(Ball, RigidBody(|| RigidBody::Dynamic))] +struct Player; - // This entity uses transform extrapolation. +fn setup_player( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, +) { + let circle = Circle::new(PLAYER_SIZE); commands.spawn(( - Name::new("Extrapolation"), - Ball, - RigidBody::Dynamic, + Player, Collider::from(circle), - TransformExtrapolation, - Transform::from_xyz(0.0, 300.0, 0.0), - Mesh2d(mesh.clone()), - MeshMaterial2d(materials.add(Color::from(LIME_400)).clone()), - )); - - // This entity is simulated in `FixedUpdate` without any smoothing. - commands.spawn(( - Name::new("No Interpolation"), - Ball, - RigidBody::Dynamic, - Collider::from(circle), - Transform::from_xyz(100.0, 300.0, 0.0), - Mesh2d(mesh.clone()), - MeshMaterial2d(materials.add(Color::from(RED_400)).clone()), + Mesh2d(meshes.add(circle)), + MeshMaterial2d(materials.add(Color::from(LIME_400))), )); } -/// Despawns all balls and respawns them. -fn reset_balls(mut commands: Commands, query: Query>) { - for entity in &query { - commands.entity(entity).despawn(); - } +#[derive(Component, Default)] +#[require(GameObject, RigidBody(|| RigidBody::Static))] +struct Wall; - commands.run_system_cached(setup_balls); +fn setup_walls( + mut commands: Commands, + mut materials: ResMut>, + mut meshes: ResMut>, +) { + let thickness = 20.; + let width = AREA_WIDTH + thickness; + + for i in 0..4 { + let mut transform = Transform::from_xyz(0., AREA_WIDTH / 2., 0.); + transform.rotate_around(Vec3::ZERO, Quat::from_rotation_z((i as f32) * PI / 2.)); + commands.spawn(( + Wall, + Collider::rectangle(width, thickness), + Mesh2d(meshes.add(Rectangle::new(width, thickness))), + MeshMaterial2d(materials.add(Color::from(WHITE))), + transform, + )); + } } -/// Changes the timestep of the simulation when the up or down arrow keys are pressed. -fn change_timestep(mut time: ResMut>, keyboard_input: Res>) { - if keyboard_input.pressed(KeyCode::ArrowUp) { - let new_timestep = (time.delta_secs_f64() * 0.975).max(1.0 / 255.0); - time.set_timestep_seconds(new_timestep); +fn move_player( + time: Res