├── benches ├── .gitignore ├── src │ ├── dim2 │ │ ├── large_pyramid.rs │ │ ├── mod.rs │ │ └── many_pyramids.rs │ ├── dim3 │ │ ├── large_pyramid.rs │ │ ├── mod.rs │ │ └── many_pyramids.rs │ └── main.rs ├── Cargo.toml └── README.md ├── assets └── branding │ └── icon.png ├── crates ├── avian3d │ ├── assets │ │ ├── ferris.glb │ │ └── character_controller_demo.glb │ └── examples │ │ ├── debugdump_3d.rs │ │ ├── fixed_joint_3d.rs │ │ ├── distance_joint_3d.rs │ │ ├── revolute_joint_3d.rs │ │ ├── prismatic_joint_3d.rs │ │ ├── collider_constructors.rs │ │ ├── dynamic_character_3d │ │ └── main.rs │ │ ├── kinematic_character_3d │ │ └── main.rs │ │ ├── cubes.rs │ │ ├── trimesh_shapes_3d.rs │ │ ├── custom_broad_phase.rs │ │ ├── diagnostics.rs │ │ └── voxels_3d.rs ├── avian_derive │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── avian2d │ └── examples │ │ ├── debugdump_2d.rs │ │ ├── fixed_joint_2d.rs │ │ ├── revolute_joint_2d.rs │ │ ├── distance_joint_2d.rs │ │ ├── prismatic_joint_2d.rs │ │ ├── pyramid_2d.rs │ │ ├── ray_caster.rs │ │ ├── chain_2d.rs │ │ ├── many_pyramids_2d.rs │ │ ├── tumbler.rs │ │ ├── collision_layers.rs │ │ ├── move_marbles.rs │ │ └── many_shapes.rs ├── examples_common_2d │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── examples_common_3d │ ├── Cargo.toml │ └── src │ └── lib.rs ├── .gitignore ├── .github ├── FUNDING.yml └── pull_request_template.md ├── src ├── dynamics │ ├── solver │ │ ├── xpbd │ │ │ ├── joints │ │ │ │ ├── shared │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── fixed_angle_constraint.rs │ │ │ │ │ └── point_constraint.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── fixed.rs │ │ │ │ └── distance.rs │ │ │ └── positional_constraint.rs │ │ ├── diagnostics.rs │ │ ├── softness_parameters │ │ │ └── mod.rs │ │ └── mod.rs │ ├── joints │ │ └── images │ │ │ ├── point_constraint.svg │ │ │ ├── revolute_joint.svg │ │ │ ├── 2d_dofs.svg │ │ │ ├── distance_joint.svg │ │ │ ├── prismatic_joint.svg │ │ │ ├── joint_frame.svg │ │ │ └── 3d_dofs.svg │ └── rigid_body │ │ └── mass_properties │ │ └── components │ │ └── collider.rs ├── data_structures │ ├── mod.rs │ ├── pair_key.rs │ └── id_pool.rs ├── character_controller │ └── mod.rs ├── diagnostics │ ├── path_macro.rs │ └── entity_counters.rs ├── collision │ ├── diagnostics.rs │ ├── collider │ │ ├── cache.rs │ │ ├── collider_transform │ │ │ └── mod.rs │ │ └── parry │ │ │ └── primitives3d.rs │ └── contact_types │ │ ├── feature_id.rs │ │ └── system_param.rs ├── spatial_query │ ├── diagnostics.rs │ └── query_filter.rs ├── utils.rs ├── math │ ├── single.rs │ └── double.rs ├── physics_transform │ └── helper.rs └── tests │ └── determinism_2d.rs ├── Cargo.toml ├── .cargo └── config_ci.toml ├── publish.sh ├── migration-guides └── 0.4-to-main.md └── LICENSE-MIT /benches/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | output -------------------------------------------------------------------------------- /assets/branding/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avianphysics/avian/HEAD/assets/branding/icon.png -------------------------------------------------------------------------------- /crates/avian3d/assets/ferris.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avianphysics/avian/HEAD/crates/avian3d/assets/ferris.glb -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | crates/*/target 3 | **/*.rs.bk 4 | .cargo/config 5 | .cargo/config.toml 6 | /.idea 7 | /.vscode 8 | /benches/target 9 | -------------------------------------------------------------------------------- /crates/avian3d/assets/character_controller_demo.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avianphysics/avian/HEAD/crates/avian3d/assets/character_controller_demo.glb -------------------------------------------------------------------------------- /crates/avian_derive/README.md: -------------------------------------------------------------------------------- 1 | # `avian_derive` 2 | 3 | This crate provides some derive implementation for [Avian Physics](https://github.com/avianphysics/avian). 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Jondolf] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | -------------------------------------------------------------------------------- /src/dynamics/solver/xpbd/joints/shared/mod.rs: -------------------------------------------------------------------------------- 1 | mod fixed_angle_constraint; 2 | mod point_constraint; 3 | 4 | pub use fixed_angle_constraint::FixedAngleConstraintShared; 5 | pub use point_constraint::PointConstraintShared; 6 | -------------------------------------------------------------------------------- /src/data_structures/mod.rs: -------------------------------------------------------------------------------- 1 | //! Specialized data structures used by Avian. 2 | 3 | pub mod bit_vec; 4 | pub mod graph; 5 | pub mod id_pool; 6 | pub mod pair_key; 7 | pub mod sparse_secondary_map; 8 | pub mod stable_graph; 9 | 10 | #[cfg(feature = "2d")] 11 | pub use arrayvec::ArrayVec; 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/avian2d", "crates/avian3d"] 3 | exclude = ["benches"] 4 | resolver = "2" 5 | 6 | [workspace.lints.clippy] 7 | alloc_instead_of_core = "warn" 8 | std_instead_of_alloc = "warn" 9 | std_instead_of_core = "warn" 10 | 11 | [profile.dev] 12 | opt-level = 1 # Use slightly better optimization, so examples work 13 | -------------------------------------------------------------------------------- /src/character_controller/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for implementing character controllers. 2 | 3 | pub mod move_and_slide; 4 | 5 | /// Re-exports common types related to character controller functionality. 6 | pub mod prelude { 7 | pub use super::move_and_slide::{ 8 | MoveAndSlide, MoveAndSlideConfig, MoveAndSlideHitData, MoveAndSlideOutput, 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /.cargo/config_ci.toml: -------------------------------------------------------------------------------- 1 | # This config is used for the CI workflow. 2 | 3 | # Enable a small amount of optimization in the dev profile. 4 | [profile.dev] 5 | opt-level = 1 6 | 7 | # Enable a large amount of optimization in the dev profile for dependencies. 8 | [profile.dev.package."*"] 9 | opt-level = 3 10 | 11 | [target.x86_64-pc-windows-msvc] 12 | linker = "rust-lld.exe" 13 | -------------------------------------------------------------------------------- /crates/avian_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avian_derive" 3 | version = "0.2.2" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Joona Aalto "] 7 | description = "Provides derive implementations for Avian Physics" 8 | repository = "https://github.com/avianphysics/avian" 9 | readme = "README.md" 10 | 11 | [lib] 12 | proc-macro = true 13 | bench = false 14 | 15 | [dependencies] 16 | proc-macro2 = "1.0.78" 17 | proc-macro-error2 = "2.0" 18 | quote = "1.0" 19 | syn = "2.0" 20 | -------------------------------------------------------------------------------- /src/dynamics/solver/xpbd/joints/mod.rs: -------------------------------------------------------------------------------- 1 | //! XPBD joint constraints. 2 | 3 | mod shared; 4 | pub use shared::{FixedAngleConstraintShared, PointConstraintShared}; 5 | 6 | mod distance; 7 | mod fixed; 8 | mod prismatic; 9 | mod revolute; 10 | #[cfg(feature = "3d")] 11 | mod spherical; 12 | 13 | pub use distance::DistanceJointSolverData; 14 | pub use fixed::FixedJointSolverData; 15 | pub use prismatic::PrismaticJointSolverData; 16 | pub use revolute::RevoluteJointSolverData; 17 | #[cfg(feature = "3d")] 18 | pub use spherical::SphericalJointSolverData; 19 | -------------------------------------------------------------------------------- /crates/avian2d/examples/debugdump_2d.rs: -------------------------------------------------------------------------------- 1 | //! Run with: 2 | //! `cargo run --example debugdump_2d > dump.dot && dot -Tsvg dump.dot > dump.svg` 3 | 4 | use avian2d::prelude::*; 5 | use bevy::prelude::*; 6 | 7 | fn main() { 8 | let mut app = App::new(); 9 | 10 | app.add_plugins((PhysicsPlugins::default(), PhysicsDebugPlugin)); 11 | 12 | // Schedules of interest: 13 | // - PhysicsSchedule 14 | // - SubstepSchedule 15 | // - FixedPostUpdate 16 | // - Update 17 | bevy_mod_debugdump::print_schedule_graph(&mut app, PhysicsSchedule); 18 | } 19 | -------------------------------------------------------------------------------- /crates/avian3d/examples/debugdump_3d.rs: -------------------------------------------------------------------------------- 1 | //! Run with: 2 | //! `cargo run --example debugdump_3d > dump.dot && dot -Tsvg dump.dot > dump.svg` 3 | 4 | use avian3d::prelude::*; 5 | use bevy::prelude::*; 6 | 7 | fn main() { 8 | let mut app = App::new(); 9 | 10 | app.add_plugins((PhysicsPlugins::default(), PhysicsDebugPlugin)); 11 | 12 | // Schedules of interest: 13 | // - PhysicsSchedule 14 | // - SubstepSchedule 15 | // - FixedPostUpdate 16 | // - Update 17 | bevy_mod_debugdump::print_schedule_graph(&mut app, PhysicsSchedule); 18 | } 19 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | tmp=$(mktemp -d) 4 | 5 | echo "$tmp" 6 | currdir=$(pwd) 7 | 8 | cp -r src "$tmp"/. 9 | cp -r LICENSE-MIT LICENSE-APACHE README.md "$tmp"/. 10 | 11 | ### Publish avian2d 12 | sed 's#\.\./\.\./src#src#g' crates/avian2d/Cargo.toml > "$tmp"/Cargo.toml 13 | cp -r crates/avian2d/examples "$tmp"/. 14 | cp -r crates/avian2d/benches "$tmp"/. 15 | cd "$tmp" && cargo publish 16 | 17 | ### Remove the 2D examples and return to previous directory 18 | rm -rf examples 19 | cd "$currdir" || exit 20 | 21 | ### Publish avian3d 22 | sed 's#\.\./\.\./src#src#g' crates/avian3d/Cargo.toml > "$tmp"/Cargo.toml 23 | cp -r crates/avian3d/examples "$tmp"/. 24 | cp -r crates/avian3d/benches "$tmp"/. 25 | cd "$tmp" && cargo publish 26 | 27 | rm -rf "$tmp" -------------------------------------------------------------------------------- /crates/examples_common_2d/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples_common_2d" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [features] 7 | use-debug-plugin = [] 8 | 9 | [dependencies] 10 | bevy = { version = "0.17", default-features = false, features = [ 11 | "bevy_core_pipeline", 12 | "bevy_state", 13 | "bevy_text", 14 | "bevy_ui", 15 | "bevy_ui_render", 16 | "bevy_asset", 17 | "bevy_render", 18 | "bevy_sprite", 19 | "bevy_sprite_render", 20 | "bevy_pbr", 21 | "bevy_gizmos", 22 | "default_font", 23 | "tonemapping_luts", 24 | "ktx2", 25 | "zstd_rust", 26 | "bevy_winit", 27 | "bevy_window", 28 | "x11", # github actions runners don't have libxkbcommon installed, so can't use wayland 29 | ] } 30 | avian2d = { path = "../avian2d", default-features = false, features = [ 31 | "diagnostic_ui", 32 | ] } 33 | 34 | [lints] 35 | workspace = true 36 | -------------------------------------------------------------------------------- /crates/examples_common_3d/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples_common_3d" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [features] 7 | use-debug-plugin = [] 8 | 9 | [dependencies] 10 | bevy = { version = "0.17", default-features = false, features = [ 11 | "bevy_core_pipeline", 12 | "bevy_state", 13 | "bevy_text", 14 | "bevy_ui", 15 | "bevy_ui_render", 16 | "bevy_asset", 17 | "bevy_render", 18 | "bevy_sprite", 19 | "bevy_sprite_render", 20 | "bevy_pbr", 21 | "bevy_gizmos", 22 | "bevy_gltf", 23 | "animation", 24 | "default_font", 25 | "tonemapping_luts", 26 | "ktx2", 27 | "png", 28 | "zstd_rust", 29 | "bevy_winit", 30 | "bevy_window", 31 | "x11", # github actions runners don't have libxkbcommon installed, so can't use wayland 32 | ] } 33 | avian3d = { path = "../avian3d", default-features = false, features = [ 34 | "diagnostic_ui", 35 | ] } 36 | 37 | [lints] 38 | workspace = true 39 | -------------------------------------------------------------------------------- /migration-guides/0.4-to-main.md: -------------------------------------------------------------------------------- 1 | # Migrating From v0.4 to `main` 2 | 3 | This file contains migration guides for breaking changes that have happened on the `main` branch 4 | since the latest release. These guides are evolving and may not be polished yet. 5 | 6 | See [migration-guides/README.md](./README.md) and existing entries for information about Avian's 7 | migration guide process and what to put here. 8 | 9 | ## `ReadRigidBody` and `WriteRigidBody` 10 | 11 | PR [#908](https://github.com/avianphysics/avian/pull/908) introduced two new traits: `ReadRigidBody` and `WriteRigidBody`, and `RigidyBodyForces` is now defined as: 12 | 13 | ```rust 14 | pub trait RigidBodyForces: ReadRigidBodyForces + WriteRigidBodyForces {} 15 | ``` 16 | 17 | In most cases this should just work, but if it doesn't, you can replace your implementation for `RigidBodyForces` with both `ReadRigidBodyForces` and `WriteRigidBodyForces` where it is used / needed. Both traits are required to implement `RigidBodyForces`, but you can implement them separately. 18 | -------------------------------------------------------------------------------- /src/data_structures/pair_key.rs: -------------------------------------------------------------------------------- 1 | //! A unique key for a pair of identifiers. 2 | 3 | use bevy::prelude::*; 4 | 5 | /// A unique key for a pair of identifiers. 6 | /// 7 | /// This can be used for efficient storage and lookup of pairs of entities or other objects. 8 | #[derive(Clone, Copy, Debug, Deref, DerefMut, PartialEq, Eq, Hash)] 9 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 10 | pub struct PairKey(pub u64); 11 | 12 | impl PairKey { 13 | /// Creates a new pair key from two IDs. 14 | #[inline] 15 | pub const fn new(id1: u32, id2: u32) -> Self { 16 | if id1 < id2 { 17 | Self(((id1 as u64) << 32) | id2 as u64) 18 | } else { 19 | Self(((id2 as u64) << 32) | id1 as u64) 20 | } 21 | } 22 | 23 | /// Gets the two IDs stored in the pair key in ascending order. 24 | #[inline] 25 | pub fn get(&self) -> (u32, u32) { 26 | ( 27 | ((self.0 >> 32) & 0xFFFF_FFFF) as u32, 28 | (self.0 & 0xFFFF_FFFF) as u32, 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 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. -------------------------------------------------------------------------------- /src/dynamics/joints/images/point_constraint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | body1 5 | 6 | 7 | 8 | body2 9 | 10 | 11 | 12 | anchor 13 | 14 | -------------------------------------------------------------------------------- /benches/src/dim2/large_pyramid.rs: -------------------------------------------------------------------------------- 1 | use avian2d::{math::Scalar, prelude::*}; 2 | use bevy::prelude::*; 3 | 4 | use super::Benchmark2dPlugins; 5 | 6 | pub fn create_bench(base_count: usize) -> App { 7 | let mut app = App::new(); 8 | app.add_plugins((Benchmark2dPlugins, PhysicsPlugins::default())); 9 | app.add_systems(Startup, move |commands: Commands| { 10 | setup(commands, base_count) 11 | }); 12 | app 13 | } 14 | 15 | fn setup(mut commands: Commands, base_count: usize) { 16 | // Ground 17 | commands.spawn(( 18 | RigidBody::Static, 19 | Collider::rectangle(800.0, 40.0), 20 | Transform::from_xyz(0.0, -20.0, 0.0), 21 | )); 22 | 23 | let h = 0.5; 24 | let box_size = 2.0 * h; 25 | let collider = Collider::rectangle(box_size as Scalar, box_size as Scalar); 26 | let shift = h; 27 | for i in 0..base_count { 28 | let y = (2.0 * i as f32 + 1.0) * shift * 0.99; 29 | 30 | for j in i..base_count { 31 | let x = (i as f32 + 1.0) * shift + 2.0 * (j - i) as f32 * shift - h * base_count as f32; 32 | 33 | commands.spawn(( 34 | RigidBody::Dynamic, 35 | collider.clone(), 36 | Transform::from_xyz(x, y, 0.0), 37 | )); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /benches/src/dim3/large_pyramid.rs: -------------------------------------------------------------------------------- 1 | use avian3d::{math::Scalar, prelude::*}; 2 | use bevy::prelude::*; 3 | 4 | use super::Benchmark3dPlugins; 5 | 6 | pub fn create_bench(base_count: usize) -> App { 7 | let mut app = App::new(); 8 | app.add_plugins((Benchmark3dPlugins, PhysicsPlugins::default())); 9 | app.add_systems(Startup, move |commands: Commands| { 10 | setup(commands, base_count) 11 | }); 12 | app 13 | } 14 | 15 | fn setup(mut commands: Commands, base_count: usize) { 16 | // Ground 17 | commands.spawn(( 18 | RigidBody::Static, 19 | Collider::cuboid(800.0, 40.0, 800.0), 20 | Transform::from_xyz(0.0, -20.0, 0.0), 21 | )); 22 | 23 | let h = 0.5; 24 | let box_size = 2.0 * h; 25 | let collider = Collider::cuboid(box_size as Scalar, box_size as Scalar, box_size as Scalar); 26 | let shift = h; 27 | for i in 0..base_count { 28 | let y = (2.0 * i as f32 + 1.0) * shift * 0.99; 29 | 30 | for j in i..base_count { 31 | let x = (i as f32 + 1.0) * shift + 2.0 * (j - i) as f32 * shift - h * base_count as f32; 32 | 33 | commands.spawn(( 34 | RigidBody::Dynamic, 35 | collider.clone(), 36 | Transform::from_xyz(x, y, 0.0), 37 | )); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/diagnostics/path_macro.rs: -------------------------------------------------------------------------------- 1 | /// A procedural macro to generate diagnostic paths. 2 | /// 3 | /// # Example 4 | /// 5 | /// The macro can be used like this: 6 | /// 7 | /// ```ignore 8 | /// impl_diagnostic_paths! { 9 | /// pub struct SolverDiagnostics { 10 | /// PREPARE_CONSTRAINTS: "avian/solver/prepare_constraints", 11 | /// INTEGRATE_VELOCITIES: "avian/solver/integrate_velocities", 12 | /// // ... 13 | /// } 14 | /// } 15 | /// ``` 16 | /// 17 | /// It expands to: 18 | /// 19 | /// ``` 20 | /// pub struct SolverDiagnostics; 21 | /// 22 | /// impl SolverDiagnostics { 23 | /// pub const PREPARE_CONSTRAINTS: &'static bevy::diagnostic::DiagnosticPath = &bevy::diagnostic::DiagnosticPath::const_new("avian/solver/prepare_constraints"); 24 | /// pub const INTEGRATE_VELOCITIES: &'static bevy::diagnostic::DiagnosticPath = &bevy::diagnostic::DiagnosticPath::const_new("avian/solver/integrate_velocities"); 25 | /// // ... 26 | /// } 27 | /// ``` 28 | macro_rules! impl_diagnostic_paths { 29 | ($(#[$meta:meta])* impl $name:ident { $($path:ident: $path_str:expr,)* }) => { 30 | #[allow(missing_docs)] 31 | impl $name { 32 | $( 33 | pub const $path: &'static bevy::diagnostic::DiagnosticPath = &bevy::diagnostic::DiagnosticPath::const_new($path_str); 34 | )* 35 | } 36 | }; 37 | } 38 | 39 | pub(crate) use impl_diagnostic_paths; 40 | -------------------------------------------------------------------------------- /src/collision/diagnostics.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | diagnostic::DiagnosticPath, 3 | prelude::{ReflectResource, Resource}, 4 | reflect::Reflect, 5 | }; 6 | use core::time::Duration; 7 | 8 | use crate::diagnostics::{PhysicsDiagnostics, impl_diagnostic_paths}; 9 | 10 | /// Diagnostics for collision detection. 11 | #[derive(Resource, Debug, Default, Reflect)] 12 | #[reflect(Resource, Debug)] 13 | pub struct CollisionDiagnostics { 14 | /// Time spent finding potential collision pairs in the broad phase. 15 | pub broad_phase: Duration, 16 | /// Time spent updating contacts in the narrow phase. 17 | pub narrow_phase: Duration, 18 | /// The number of contacts. 19 | pub contact_count: u32, 20 | } 21 | 22 | impl PhysicsDiagnostics for CollisionDiagnostics { 23 | fn timer_paths(&self) -> Vec<(&'static DiagnosticPath, Duration)> { 24 | vec![ 25 | (Self::BROAD_PHASE, self.broad_phase), 26 | (Self::NARROW_PHASE, self.narrow_phase), 27 | ] 28 | } 29 | 30 | fn counter_paths(&self) -> Vec<(&'static DiagnosticPath, u32)> { 31 | vec![(Self::CONTACT_COUNT, self.contact_count)] 32 | } 33 | } 34 | 35 | impl_diagnostic_paths! { 36 | impl CollisionDiagnostics { 37 | BROAD_PHASE: "avian/collision/broad_phase", 38 | NARROW_PHASE: "avian/collision/update_contacts", 39 | CONTACT_COUNT: "avian/collision/contact_count", 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /crates/avian2d/examples/fixed_joint_2d.rs: -------------------------------------------------------------------------------- 1 | use avian2d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_2d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .insert_resource(Gravity(Vector::NEG_Y * 1000.0)) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup(mut commands: Commands) { 20 | commands.spawn(Camera2d); 21 | 22 | let square_sprite = Sprite { 23 | color: Color::srgb(0.2, 0.7, 0.9), 24 | custom_size: Some(Vec2::splat(50.0)), 25 | ..default() 26 | }; 27 | 28 | let anchor = commands 29 | .spawn(( 30 | square_sprite.clone(), 31 | RigidBody::Kinematic, 32 | AngularVelocity(1.5), 33 | )) 34 | .id(); 35 | 36 | let object = commands 37 | .spawn(( 38 | square_sprite, 39 | Transform::from_xyz(100.0, 0.0, 0.0), 40 | RigidBody::Dynamic, 41 | MassPropertiesBundle::from_shape(&Rectangle::from_length(50.0), 1.0), 42 | )) 43 | .id(); 44 | 45 | commands.spawn(FixedJoint::new(anchor, object).with_local_anchor1(Vector::X * 100.0)); 46 | } 47 | -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benches" 3 | version = "0.1.0" 4 | edition = "2024" 5 | license = "MIT OR Apache-2.0" 6 | authors = ["Joona Aalto "] 7 | description = "Benchmarks for Avian Physics" 8 | repository = "https://github.com/avianphysics/avian" 9 | readme = "README.md" 10 | 11 | [features] 12 | default = ["2d", "3d"] 13 | 2d = ["dep:avian2d"] 14 | 3d = ["dep:avian3d"] 15 | 16 | [dependencies] 17 | # Physics 18 | avian2d = { path = "../crates/avian2d", default-features = false, features = [ 19 | "2d", 20 | "f32", 21 | "parry-f32", 22 | "parallel", 23 | "simd", 24 | ], optional = true } 25 | avian3d = { path = "../crates/avian3d", default-features = false, features = [ 26 | "3d", 27 | "f32", 28 | "parry-f32", 29 | "parallel", 30 | "simd", 31 | ], optional = true } 32 | 33 | # Bevy 34 | bevy = { version = "0.17", default-features = false } 35 | 36 | # CLI 37 | clap = { version = "4.5", features = ["derive"] } 38 | 39 | # Miscellaneous 40 | rayon = { version = "1.10" } 41 | 42 | [profile.dev] 43 | # Make the `dev` profile equivalent to the `release` profile 44 | # so that benchmarks are more realistic even without `--release`. 45 | # Reference: https://doc.rust-lang.org/cargo/reference/profiles.html 46 | opt-level = 3 47 | debug = false 48 | strip = "none" 49 | debug-assertions = false 50 | overflow-checks = false 51 | lto = false 52 | panic = 'unwind' 53 | incremental = false 54 | codegen-units = 16 55 | rpath = false 56 | -------------------------------------------------------------------------------- /src/spatial_query/diagnostics.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use bevy::{ 4 | diagnostic::DiagnosticPath, 5 | prelude::{ReflectResource, Resource}, 6 | reflect::Reflect, 7 | }; 8 | 9 | use crate::diagnostics::{PhysicsDiagnostics, impl_diagnostic_paths}; 10 | 11 | /// Diagnostics for spatial queries. 12 | #[derive(Resource, Debug, Default, Reflect)] 13 | #[reflect(Resource, Debug)] 14 | pub struct SpatialQueryDiagnostics { 15 | /// Time spent updating the [`SpatialQueryPipeline`](super::SpatialQueryPipeline). 16 | pub update_pipeline: Duration, 17 | /// Time spent updating [`RayCaster`](super::RayCaster) hits. 18 | pub update_ray_casters: Duration, 19 | /// Time spent updating [`ShapeCaster`](super::ShapeCaster) hits. 20 | pub update_shape_casters: Duration, 21 | } 22 | 23 | impl PhysicsDiagnostics for SpatialQueryDiagnostics { 24 | fn timer_paths(&self) -> Vec<(&'static DiagnosticPath, Duration)> { 25 | vec![ 26 | (Self::UPDATE_PIPELINE, self.update_pipeline), 27 | (Self::UPDATE_RAY_CASTERS, self.update_ray_casters), 28 | (Self::UPDATE_SHAPE_CASTERS, self.update_shape_casters), 29 | ] 30 | } 31 | } 32 | 33 | impl_diagnostic_paths! { 34 | impl SpatialQueryDiagnostics { 35 | UPDATE_PIPELINE: "avian/spatial_query/update_pipeline", 36 | UPDATE_RAY_CASTERS: "avian/spatial_query/update_ray_casters", 37 | UPDATE_SHAPE_CASTERS: "avian/spatial_query/update_shape_casters", 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/avian2d/examples/revolute_joint_2d.rs: -------------------------------------------------------------------------------- 1 | use avian2d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_2d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .insert_resource(Gravity(Vector::NEG_Y * 1000.0)) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup(mut commands: Commands) { 20 | commands.spawn(Camera2d); 21 | 22 | let square_sprite = Sprite { 23 | color: Color::srgb(0.2, 0.7, 0.9), 24 | custom_size: Some(Vec2::splat(50.0)), 25 | ..default() 26 | }; 27 | 28 | let anchor = commands 29 | .spawn(( 30 | square_sprite.clone(), 31 | RigidBody::Kinematic, 32 | AngularVelocity(1.5), 33 | )) 34 | .id(); 35 | 36 | let object = commands 37 | .spawn(( 38 | square_sprite, 39 | Transform::from_xyz(0.0, -100.0, 0.0), 40 | RigidBody::Dynamic, 41 | MassPropertiesBundle::from_shape(&Rectangle::from_length(50.0), 1.0), 42 | )) 43 | .id(); 44 | 45 | commands.spawn( 46 | RevoluteJoint::new(anchor, object) 47 | .with_local_anchor2(Vector::Y * 100.0) 48 | .with_angle_limits(-1.0, 1.0), 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /crates/avian2d/examples/distance_joint_2d.rs: -------------------------------------------------------------------------------- 1 | use avian2d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_2d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .insert_resource(Gravity(Vector::NEG_Y * 1000.0)) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup(mut commands: Commands) { 20 | commands.spawn(Camera2d); 21 | 22 | let square_sprite = Sprite { 23 | color: Color::srgb(0.2, 0.7, 0.9), 24 | custom_size: Some(Vec2::splat(50.0)), 25 | ..default() 26 | }; 27 | 28 | let anchor = commands 29 | .spawn((square_sprite.clone(), RigidBody::Kinematic)) 30 | .id(); 31 | 32 | let object = commands 33 | .spawn(( 34 | square_sprite, 35 | Transform::from_xyz(100.0, 0.0, 0.0), 36 | RigidBody::Dynamic, 37 | MassPropertiesBundle::from_shape(&Rectangle::from_length(50.0), 1.0), 38 | )) 39 | .id(); 40 | 41 | commands.spawn(( 42 | DistanceJoint::new(anchor, object) 43 | .with_limits(100.0, 100.0) 44 | .with_compliance(0.00000001), 45 | JointDamping { 46 | linear: 0.1, 47 | angular: 1.0, 48 | }, 49 | )); 50 | } 51 | -------------------------------------------------------------------------------- /crates/avian2d/examples/prismatic_joint_2d.rs: -------------------------------------------------------------------------------- 1 | use avian2d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_2d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .insert_resource(Gravity(Vector::NEG_Y * 1000.0)) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup(mut commands: Commands) { 20 | commands.spawn(Camera2d); 21 | 22 | let square_sprite = Sprite { 23 | color: Color::srgb(0.2, 0.7, 0.9), 24 | custom_size: Some(Vec2::splat(50.0)), 25 | ..default() 26 | }; 27 | 28 | let anchor = commands 29 | .spawn(( 30 | square_sprite.clone(), 31 | RigidBody::Kinematic, 32 | AngularVelocity(1.5), 33 | )) 34 | .id(); 35 | 36 | let object = commands 37 | .spawn(( 38 | square_sprite, 39 | Transform::from_xyz(100.0, 0.0, 0.0), 40 | RigidBody::Dynamic, 41 | MassPropertiesBundle::from_shape(&Rectangle::from_length(50.0), 1.0), 42 | )) 43 | .id(); 44 | 45 | commands.spawn( 46 | PrismaticJoint::new(anchor, object) 47 | .with_local_anchor1(Vector::X * 50.0) 48 | .with_slider_axis(Vector::X) 49 | .with_limits(25.0, 100.0), 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /.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 | 14 | 15 | ## Testing 16 | 17 | - Did you test these changes? If so, how? 18 | - Are there any parts that need more testing? 19 | - How can other people (reviewers) test your changes? Is there anything specific they need to know? 20 | - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? 21 | 22 | --- 23 | 24 | ## Showcase 25 | 26 | > This section is optional. If this PR does not include a visual change or does not add a new feature, you can delete this section. 27 | 28 | - Help others understand the result of this PR by showcasing your awesome work! 29 | - If this PR adds a new feature or public API, consider adding a brief pseudo-code snippet of it in action 30 | - If this PR includes a visual change, consider adding a screenshot, GIF, or video 31 | - If you want, you could even include a before/after comparison! 32 | - If the Migration Guide adequately covers the changes, you can delete this section 33 | 34 | While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases: 35 | 36 |
37 | Click to view showcase 38 | 39 | ```rust 40 | println!("My super cool code."); 41 | ``` 42 | 43 |
44 | -------------------------------------------------------------------------------- /benches/src/dim2/mod.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use avian2d::prelude::SubstepCount; 4 | use bevy::{ 5 | MinimalPlugins, 6 | app::{App, Plugin, PluginGroup, PluginGroupBuilder}, 7 | time::{Time, TimeUpdateStrategy}, 8 | transform::TransformPlugin, 9 | }; 10 | 11 | use crate::Benchmark; 12 | 13 | mod large_pyramid; 14 | mod many_pyramids; 15 | 16 | /// All benchmarks for `avian2d`. 17 | pub const BENCHMARKS: &[Benchmark] = &[ 18 | Benchmark::new("Large Pyramid 2D", "pyramid", || { 19 | large_pyramid::create_bench(100) 20 | }), 21 | Benchmark::new("Many Pyramids 2D", "many_pyramids", || { 22 | many_pyramids::create_bench(10, 10, 10) 23 | }), 24 | ]; 25 | 26 | /// A plugin group that includes the minimal set of plugins used for benchmarking `avian2d`. 27 | pub struct Benchmark2dPlugins; 28 | 29 | impl PluginGroup for Benchmark2dPlugins { 30 | fn build(self) -> bevy::app::PluginGroupBuilder { 31 | PluginGroupBuilder::start::() 32 | .add_group(MinimalPlugins) 33 | .add(Benchmark2dCorePlugin) 34 | .add(TransformPlugin) 35 | } 36 | } 37 | 38 | /// A plugin that sets up the core resources and configuration for benchmarking. 39 | struct Benchmark2dCorePlugin; 40 | 41 | impl Plugin for Benchmark2dCorePlugin { 42 | fn build(&self, app: &mut App) { 43 | // All benchmarks use a fixed time step of 60 FPS with 4 substeps. 44 | app.insert_resource(Time::from_hz(60.0)); 45 | app.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f64( 46 | 1.0 / 60.0, 47 | ))); 48 | app.insert_resource(SubstepCount(4)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /benches/src/dim3/mod.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use avian3d::prelude::SubstepCount; 4 | use bevy::{ 5 | MinimalPlugins, 6 | app::{App, Plugin, PluginGroup, PluginGroupBuilder}, 7 | time::{Time, TimeUpdateStrategy}, 8 | transform::TransformPlugin, 9 | }; 10 | 11 | use crate::Benchmark; 12 | 13 | mod large_pyramid; 14 | mod many_pyramids; 15 | 16 | /// All benchmarks for `avian3d`. 17 | pub const BENCHMARKS: &[Benchmark] = &[ 18 | Benchmark::new("Large Pyramid 3D", "pyramid", || { 19 | large_pyramid::create_bench(100) 20 | }), 21 | Benchmark::new("Many Pyramids 3D", "many_pyramids", || { 22 | many_pyramids::create_bench(10, 10, 10) 23 | }), 24 | ]; 25 | 26 | /// A plugin group that includes the minimal set of plugins used for benchmarking `avian3d`. 27 | pub struct Benchmark3dPlugins; 28 | 29 | impl PluginGroup for Benchmark3dPlugins { 30 | fn build(self) -> bevy::app::PluginGroupBuilder { 31 | PluginGroupBuilder::start::() 32 | .add_group(MinimalPlugins) 33 | .add(Benchmark3dCorePlugin) 34 | .add(TransformPlugin) 35 | } 36 | } 37 | 38 | /// A plugin that sets up the core resources and configuration for benchmarking. 39 | struct Benchmark3dCorePlugin; 40 | 41 | impl Plugin for Benchmark3dCorePlugin { 42 | fn build(&self, app: &mut App) { 43 | // All benchmarks use a fixed time step of 60 FPS with 4 substeps. 44 | app.insert_resource(Time::from_hz(60.0)); 45 | app.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f64( 46 | 1.0 / 60.0, 47 | ))); 48 | app.insert_resource(SubstepCount(4)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/dynamics/joints/images/revolute_joint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | body1 5 | 6 | 7 | 8 | body2 9 | 10 | 11 | 12 | 13 | 14 | anchor 15 | 16 | 17 | angle 18 | 19 | -------------------------------------------------------------------------------- /benches/src/main.rs: -------------------------------------------------------------------------------- 1 | use bevy::app::App; 2 | 3 | mod cli; 4 | #[cfg(feature = "2d")] 5 | mod dim2; 6 | #[cfg(feature = "3d")] 7 | mod dim3; 8 | 9 | /// A benchmark that can be run with the CLI. 10 | #[derive(Clone, Copy, Debug)] 11 | pub struct Benchmark { 12 | /// The name of the benchmark. 13 | pub name: &'static str, 14 | /// The name of the module where the benchmark is defined. 15 | pub module: &'static str, 16 | /// A function that constructs the benchmark application. 17 | pub constructor: fn() -> App, 18 | } 19 | 20 | impl Benchmark { 21 | /// Creates a new benchmark with the given name, module, and constructor function. 22 | pub const fn new(name: &'static str, module: &'static str, constructor: fn() -> App) -> Self { 23 | Self { 24 | name, 25 | module, 26 | constructor, 27 | } 28 | } 29 | } 30 | 31 | fn main() { 32 | let benchmarks = [ 33 | #[cfg(feature = "2d")] 34 | dim2::BENCHMARKS, 35 | #[cfg(feature = "3d")] 36 | dim3::BENCHMARKS, 37 | ] 38 | .concat(); 39 | 40 | if benchmarks.is_empty() { 41 | eprintln!("No benchmarks available. Please enable either the `2d` or `3d` feature."); 42 | return; 43 | } 44 | 45 | let args = cli::Args::parse(); 46 | 47 | if args.list { 48 | cli::list(&benchmarks); 49 | return; 50 | } 51 | 52 | if let Some(name) = &args.name { 53 | // Run the given benchmark with the specified number of threads. 54 | if let Some(benchmark) = benchmarks.iter().find(|b| b.name == name) { 55 | cli::run(benchmark, &args); 56 | } else if let Ok(Some(benchmark)) = name.parse::().map(|i| benchmarks.get(i - 1)) { 57 | cli::run(benchmark, &args); 58 | } else { 59 | eprintln!("Benchmark '{name}' not found. Use --list to see available benchmarks.",); 60 | } 61 | } else { 62 | // If no specific benchmark is requested, run all benchmarks. 63 | cli::run_all(&benchmarks, &args); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/avian2d/examples/pyramid_2d.rs: -------------------------------------------------------------------------------- 1 | use avian2d::{math::Scalar, prelude::*}; 2 | use bevy::{camera::ScalingMode, prelude::*}; 3 | use examples_common_2d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | let mut app = App::new(); 7 | 8 | app.add_plugins(( 9 | DefaultPlugins, 10 | PhysicsPlugins::default(), 11 | ExampleCommonPlugin, 12 | )); 13 | 14 | app.add_systems(Startup, setup); 15 | 16 | app.run(); 17 | } 18 | 19 | fn setup( 20 | mut commands: Commands, 21 | mut meshes: ResMut>, 22 | mut materials: ResMut>, 23 | ) { 24 | commands.spawn(( 25 | Camera2d, 26 | Projection::Orthographic(OrthographicProjection { 27 | scaling_mode: ScalingMode::FixedHorizontal { 28 | viewport_width: 150.0, 29 | }, 30 | ..OrthographicProjection::default_2d() 31 | }), 32 | Transform::from_xyz(0.0, 30.0, 0.0), 33 | )); 34 | 35 | // Ground 36 | commands.spawn(( 37 | RigidBody::Static, 38 | Collider::rectangle(800.0, 40.0), 39 | Transform::from_xyz(0.0, -20.0, 0.0), 40 | Mesh2d(meshes.add(Rectangle::new(800.0, 40.0))), 41 | MeshMaterial2d(materials.add(Color::srgb(0.3, 0.5, 0.3))), 42 | )); 43 | 44 | let base_count = 50; 45 | let h = 0.5; 46 | let box_size = 2.0 * h; 47 | let collider = Collider::rectangle(box_size as Scalar, box_size as Scalar); 48 | let shift = h; 49 | for i in 0..base_count { 50 | let y = (2.0 * i as f32 + 1.0) * shift * 0.99; 51 | 52 | for j in i..base_count { 53 | let x = (i as f32 + 1.0) * shift + 2.0 * (j - i) as f32 * shift - h * base_count as f32; 54 | 55 | commands.spawn(( 56 | RigidBody::Dynamic, 57 | collider.clone(), 58 | Transform::from_xyz(x, y, 0.0), 59 | Mesh2d(meshes.add(Rectangle::new(box_size, box_size))), 60 | MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 0.9))), 61 | )); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/avian3d/examples/fixed_joint_3d.rs: -------------------------------------------------------------------------------- 1 | use avian3d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_3d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .add_systems(Startup, setup) 15 | .run(); 16 | } 17 | 18 | fn setup( 19 | mut commands: Commands, 20 | mut materials: ResMut>, 21 | mut meshes: ResMut>, 22 | ) { 23 | let cube_mesh = meshes.add(Cuboid::default()); 24 | let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6)); 25 | 26 | // Kinematic rotating "anchor" object 27 | let anchor = commands 28 | .spawn(( 29 | Mesh3d(cube_mesh.clone()), 30 | MeshMaterial3d(cube_material.clone()), 31 | RigidBody::Kinematic, 32 | AngularVelocity(Vector::Z * 1.5), 33 | )) 34 | .id(); 35 | 36 | // Dynamic object rotating around anchor 37 | let object = commands 38 | .spawn(( 39 | Mesh3d(cube_mesh), 40 | MeshMaterial3d(cube_material), 41 | Transform::from_xyz(1.5, 0.0, 0.0), 42 | RigidBody::Dynamic, 43 | MassPropertiesBundle::from_shape(&Cuboid::from_length(1.0), 1.0), 44 | )) 45 | .id(); 46 | 47 | // Connect anchor and dynamic object 48 | commands.spawn(FixedJoint::new(anchor, object).with_local_anchor1(Vector::X * 1.5)); 49 | 50 | // Directional light 51 | commands.spawn(( 52 | DirectionalLight { 53 | illuminance: 2000.0, 54 | shadows_enabled: true, 55 | ..default() 56 | }, 57 | Transform::default().looking_at(Vec3::new(-1.0, -2.5, -1.5), Vec3::Y), 58 | )); 59 | 60 | // Camera 61 | commands.spawn(( 62 | Camera3d::default(), 63 | Transform::from_translation(Vec3::Z * 10.0).looking_at(Vec3::ZERO, Vec3::Y), 64 | )); 65 | } 66 | -------------------------------------------------------------------------------- /crates/avian3d/examples/distance_joint_3d.rs: -------------------------------------------------------------------------------- 1 | use avian3d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_3d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | PhysicsDebugPlugin, 12 | )) 13 | .add_systems(Startup, setup) 14 | .run(); 15 | } 16 | 17 | fn setup( 18 | mut commands: Commands, 19 | mut meshes: ResMut>, 20 | mut materials: ResMut>, 21 | ) { 22 | let cube_mesh = meshes.add(Cuboid::default()); 23 | let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6)); 24 | 25 | // Spawn a static cube and a dynamic cube that is connected to it by a distance joint. 26 | let static_cube = commands 27 | .spawn(( 28 | Mesh3d(cube_mesh.clone()), 29 | MeshMaterial3d(cube_material.clone()), 30 | RigidBody::Static, 31 | Collider::cuboid(1., 1., 1.), 32 | )) 33 | .id(); 34 | let dynamic_cube = commands 35 | .spawn(( 36 | Mesh3d(cube_mesh), 37 | MeshMaterial3d(cube_material), 38 | Transform::from_xyz(-2.0, -0.5, 0.0), 39 | RigidBody::Dynamic, 40 | Collider::cuboid(1., 1., 1.), 41 | )) 42 | .id(); 43 | 44 | // Add a distance joint to keep the cubes at a certain distance from each other. 45 | commands.spawn( 46 | DistanceJoint::new(static_cube, dynamic_cube) 47 | .with_local_anchor2(Vector::splat(0.5)) 48 | .with_limits(1.5, 1.5) 49 | .with_compliance(1.0 / 400.0), 50 | ); 51 | 52 | // Light 53 | commands.spawn(( 54 | PointLight { 55 | intensity: 2_000_000.0, 56 | shadows_enabled: true, 57 | ..default() 58 | }, 59 | Transform::from_xyz(4.0, 8.0, 4.0), 60 | )); 61 | 62 | // Camera 63 | commands.spawn(( 64 | Camera3d::default(), 65 | Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), 66 | )); 67 | } 68 | -------------------------------------------------------------------------------- /benches/src/dim2/many_pyramids.rs: -------------------------------------------------------------------------------- 1 | use avian2d::prelude::*; 2 | use bevy::prelude::*; 3 | 4 | use super::Benchmark2dPlugins; 5 | 6 | pub fn create_bench(base_count: usize, row_count: usize, column_count: usize) -> App { 7 | let mut app = App::new(); 8 | app.add_plugins((Benchmark2dPlugins, PhysicsPlugins::default())); 9 | app.add_systems(Startup, move |commands: Commands| { 10 | setup(commands, base_count, row_count, column_count); 11 | }); 12 | app 13 | } 14 | 15 | fn setup(mut commands: Commands, base_count: usize, row_count: usize, column_count: usize) { 16 | let h = 0.5; 17 | let ground_delta_y = 2.0 * h * (base_count + 1) as f32; 18 | let ground_width = 2.0 * h * column_count as f32 * (base_count + 1) as f32; 19 | 20 | // Ground 21 | for i in 0..row_count { 22 | commands.spawn(( 23 | RigidBody::Static, 24 | Collider::rectangle(ground_width, 0.01), 25 | Transform::from_xyz(0.0, i as f32 * ground_delta_y, 0.0), 26 | )); 27 | } 28 | 29 | let base_width = 2.0 * h * base_count as f32; 30 | 31 | for i in 0..row_count { 32 | let base_y = i as f32 * ground_delta_y; 33 | for j in 0..column_count { 34 | let center_x = -ground_width / 2.0 + j as f32 * (base_width + 2.0 * h) + h; 35 | spawn_small_pyramid(&mut commands, base_count, h, center_x, base_y); 36 | } 37 | } 38 | } 39 | 40 | /// Spawns a small pyramid structure at the specified position. 41 | fn spawn_small_pyramid( 42 | commands: &mut Commands, 43 | base_count: usize, 44 | h: f32, 45 | center_x: f32, 46 | base_y: f32, 47 | ) { 48 | let box_size = 2.0 * h; 49 | let collider = Collider::rectangle(box_size, box_size); 50 | 51 | for i in 0..base_count { 52 | let y = (2 * i + 1) as f32 * h + base_y; 53 | 54 | for j in i..base_count { 55 | let x = (i + 1) as f32 * h + 2.0 * (j - i) as f32 * h + center_x - 0.5; 56 | 57 | commands.spawn(( 58 | RigidBody::Dynamic, 59 | collider.clone(), 60 | Transform::from_xyz(x, y, 0.0), 61 | )); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /benches/src/dim3/many_pyramids.rs: -------------------------------------------------------------------------------- 1 | use avian3d::prelude::*; 2 | use bevy::prelude::*; 3 | 4 | use super::Benchmark3dPlugins; 5 | 6 | pub fn create_bench(base_count: usize, row_count: usize, column_count: usize) -> App { 7 | let mut app = App::new(); 8 | app.add_plugins((Benchmark3dPlugins, PhysicsPlugins::default())); 9 | app.add_systems(Startup, move |commands: Commands| { 10 | setup(commands, base_count, row_count, column_count); 11 | }); 12 | app 13 | } 14 | 15 | fn setup(mut commands: Commands, base_count: usize, row_count: usize, column_count: usize) { 16 | let h = 0.5; 17 | let ground_delta_y = 2.0 * h * (base_count + 1) as f32; 18 | let ground_width = 2.0 * h * column_count as f32 * (base_count + 1) as f32; 19 | 20 | // Ground 21 | for i in 0..row_count { 22 | commands.spawn(( 23 | RigidBody::Static, 24 | Collider::cuboid(ground_width, 0.01, ground_width), 25 | Transform::from_xyz(0.0, i as f32 * ground_delta_y, 0.0), 26 | )); 27 | } 28 | 29 | let base_width = 2.0 * h * base_count as f32; 30 | 31 | for i in 0..row_count { 32 | let base_y = i as f32 * ground_delta_y; 33 | for j in 0..column_count { 34 | let center_x = -ground_width / 2.0 + j as f32 * (base_width + 2.0 * h) + h; 35 | spawn_small_pyramid(&mut commands, base_count, h, center_x, base_y); 36 | } 37 | } 38 | } 39 | 40 | /// Spawns a small pyramid structure at the specified position. 41 | fn spawn_small_pyramid( 42 | commands: &mut Commands, 43 | base_count: usize, 44 | h: f32, 45 | center_x: f32, 46 | base_y: f32, 47 | ) { 48 | let box_size = 2.0 * h; 49 | let collider = Collider::cuboid(box_size, box_size, box_size); 50 | 51 | for i in 0..base_count { 52 | let y = (2 * i + 1) as f32 * h + base_y; 53 | 54 | for j in i..base_count { 55 | let x = (i + 1) as f32 * h + 2.0 * (j - i) as f32 * h + center_x - 0.5; 56 | 57 | commands.spawn(( 58 | RigidBody::Dynamic, 59 | collider.clone(), 60 | Transform::from_xyz(x, y, 0.0), 61 | )); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/avian3d/examples/revolute_joint_3d.rs: -------------------------------------------------------------------------------- 1 | use avian3d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_3d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .add_systems(Startup, setup) 15 | .run(); 16 | } 17 | 18 | fn setup( 19 | mut commands: Commands, 20 | mut materials: ResMut>, 21 | mut meshes: ResMut>, 22 | ) { 23 | let cube_mesh = meshes.add(Cuboid::default()); 24 | let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6)); 25 | 26 | // Kinematic rotating "anchor" object 27 | let anchor = commands 28 | .spawn(( 29 | Mesh3d(cube_mesh.clone()), 30 | MeshMaterial3d(cube_material.clone()), 31 | RigidBody::Kinematic, 32 | AngularVelocity(Vector::Z * 1.5), 33 | )) 34 | .id(); 35 | 36 | // Dynamic object rotating around anchor 37 | let object = commands 38 | .spawn(( 39 | Mesh3d(cube_mesh), 40 | MeshMaterial3d(cube_material), 41 | Transform::from_xyz(0.0, -2.0, 0.0), 42 | RigidBody::Dynamic, 43 | MassPropertiesBundle::from_shape(&Cuboid::from_length(1.0), 1.0), 44 | )) 45 | .id(); 46 | 47 | // Connect anchor and dynamic object 48 | commands.spawn( 49 | RevoluteJoint::new(anchor, object) 50 | .with_local_anchor2(Vector::Y * 2.0) 51 | .with_angle_limits(-1.0, 1.0), 52 | ); 53 | 54 | // Directional light 55 | commands.spawn(( 56 | DirectionalLight { 57 | illuminance: 2000.0, 58 | shadows_enabled: true, 59 | ..default() 60 | }, 61 | Transform::default().looking_at(Vec3::new(-1.0, -2.5, -1.5), Vec3::Y), 62 | )); 63 | 64 | // Camera 65 | commands.spawn(( 66 | Camera3d::default(), 67 | Transform::from_translation(Vec3::Z * 10.0).looking_at(Vec3::ZERO, Vec3::Y), 68 | )); 69 | } 70 | -------------------------------------------------------------------------------- /crates/avian3d/examples/prismatic_joint_3d.rs: -------------------------------------------------------------------------------- 1 | use avian3d::{math::*, prelude::*}; 2 | use bevy::prelude::*; 3 | use examples_common_3d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins(( 8 | DefaultPlugins, 9 | ExampleCommonPlugin, 10 | PhysicsPlugins::default(), 11 | )) 12 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 13 | .insert_resource(SubstepCount(50)) 14 | .add_systems(Startup, setup) 15 | .run(); 16 | } 17 | 18 | fn setup( 19 | mut commands: Commands, 20 | mut materials: ResMut>, 21 | mut meshes: ResMut>, 22 | ) { 23 | let cube_mesh = meshes.add(Cuboid::default()); 24 | let cube_material = materials.add(Color::srgb(0.8, 0.7, 0.6)); 25 | 26 | // Kinematic rotating "anchor" object 27 | let anchor = commands 28 | .spawn(( 29 | Mesh3d(cube_mesh.clone()), 30 | MeshMaterial3d(cube_material.clone()), 31 | RigidBody::Kinematic, 32 | AngularVelocity(Vector::Z * 1.5), 33 | )) 34 | .id(); 35 | 36 | // Dynamic object rotating around anchor 37 | let object = commands 38 | .spawn(( 39 | Mesh3d(cube_mesh), 40 | MeshMaterial3d(cube_material), 41 | Transform::from_xyz(1.5, 0.0, 0.0), 42 | RigidBody::Dynamic, 43 | MassPropertiesBundle::from_shape(&Cuboid::from_length(1.0), 1.0), 44 | )) 45 | .id(); 46 | 47 | // Connect anchor and dynamic object 48 | commands.spawn( 49 | PrismaticJoint::new(anchor, object) 50 | .with_local_anchor1(Vector::X) 51 | .with_slider_axis(Vector::X) 52 | .with_limits(0.5, 2.0), 53 | ); 54 | 55 | // Directional light 56 | commands.spawn(( 57 | DirectionalLight { 58 | illuminance: 2000.0, 59 | shadows_enabled: true, 60 | ..default() 61 | }, 62 | Transform::default().looking_at(Vec3::new(-1.0, -2.5, -1.5), Vec3::Y), 63 | )); 64 | 65 | // Camera 66 | commands.spawn(( 67 | Camera3d::default(), 68 | Transform::from_translation(Vec3::Z * 10.0).looking_at(Vec3::ZERO, Vec3::Y), 69 | )); 70 | } 71 | -------------------------------------------------------------------------------- /src/collision/collider/cache.rs: -------------------------------------------------------------------------------- 1 | use bevy::{platform::collections::HashMap, prelude::*}; 2 | 3 | use super::{Collider, ColliderConstructor}; 4 | 5 | /// A plugin for caching colliders created from meshes via [`ColliderConstructor`] or [`ColliderConstructorHierarchy`](super::ColliderConstructorHierarchy). 6 | /// With this plugin enabled, colliders created from meshes through such constructors will be created only once and reused. 7 | /// This is especially useful when performing convex decomposition, as this is a very expensive operation. 8 | pub struct ColliderCachePlugin; 9 | 10 | impl Plugin for ColliderCachePlugin { 11 | fn build(&self, app: &mut App) { 12 | app.init_resource::(); 13 | app.add_systems(PreUpdate, clear_unused_colliders); 14 | } 15 | } 16 | 17 | #[derive(Debug, Resource, Default)] 18 | pub(crate) struct ColliderCache(HashMap, Vec<(ColliderConstructor, Collider)>>); 19 | 20 | impl ColliderCache { 21 | pub(crate) fn get_or_insert( 22 | &mut self, 23 | mesh_handle: &Handle, 24 | mesh: &Mesh, 25 | constructor: ColliderConstructor, 26 | ) -> Option { 27 | let id = mesh_handle.id(); 28 | let Some(entries) = self.0.get_mut(&id) else { 29 | let collider = Collider::try_from_constructor(constructor.clone(), Some(mesh))?; 30 | self.0 31 | .insert(id, vec![(constructor.clone(), collider.clone())]); 32 | return Some(collider); 33 | }; 34 | if let Some((_ctor, collider)) = entries.iter().find(|(c, _)| c == &constructor) { 35 | Some(collider.clone()) 36 | } else { 37 | let collider = Collider::try_from_constructor(constructor.clone(), Some(mesh))?; 38 | entries.push((constructor.clone(), collider.clone())); 39 | Some(collider) 40 | } 41 | } 42 | } 43 | 44 | fn clear_unused_colliders( 45 | mut asset_events: MessageReader>, 46 | mut collider_cache: ResMut, 47 | ) { 48 | for event in asset_events.read() { 49 | if let AssetEvent::Removed { id } | AssetEvent::Unused { id } = event 50 | && collider_cache.0.contains_key(id) 51 | { 52 | collider_cache.0.remove(id); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/dynamics/joints/images/2d_dofs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | x-axis 7 | translation 8 | 9 | 10 | z-axis 11 | rotation 12 | 13 | 14 | y-axis 15 | translation 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/dynamics/joints/images/distance_joint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | body1 7 | 8 | 9 | 10 | body2 11 | 12 | 13 | 14 | 15 | anchor2 16 | 17 | 18 | 19 | anchor1 20 | 21 | 22 | max 23 | 24 | 25 | min 26 | 27 | 28 | distance 29 | 30 | -------------------------------------------------------------------------------- /src/collision/collider/collider_transform/mod.rs: -------------------------------------------------------------------------------- 1 | //! Transform management and types for colliders. 2 | 3 | mod plugin; 4 | 5 | pub use plugin::ColliderTransformPlugin; 6 | 7 | use crate::prelude::*; 8 | use bevy::prelude::*; 9 | 10 | /// The transform of a collider relative to the rigid body it's attached to. 11 | /// This is in the local space of the body, not the collider itself. 12 | /// 13 | /// This is used for computing things like contact positions and a body's center of mass 14 | /// without having to traverse deeply nested hierarchies. It's updated automatically, 15 | /// so you shouldn't modify it manually. 16 | #[derive(Reflect, Clone, Copy, Component, Debug, PartialEq)] 17 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 18 | #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] 19 | #[reflect(Debug, Component, PartialEq)] 20 | pub struct ColliderTransform { 21 | /// The translation of a collider in a rigid body's frame of reference. 22 | pub translation: Vector, 23 | /// The rotation of a collider in a rigid body's frame of reference. 24 | pub rotation: Rotation, 25 | /// The global scale of a collider. Equivalent to the `GlobalTransform` scale. 26 | pub scale: Vector, 27 | } 28 | 29 | impl ColliderTransform { 30 | /// Transforms a given point by applying the translation, rotation and scale of 31 | /// this [`ColliderTransform`]. 32 | pub fn transform_point(&self, mut point: Vector) -> Vector { 33 | point *= self.scale; 34 | point = self.rotation * point; 35 | point += self.translation; 36 | point 37 | } 38 | } 39 | 40 | impl Default for ColliderTransform { 41 | fn default() -> Self { 42 | Self { 43 | translation: Vector::ZERO, 44 | rotation: Rotation::default(), 45 | scale: Vector::ONE, 46 | } 47 | } 48 | } 49 | 50 | impl From for ColliderTransform { 51 | fn from(value: Transform) -> Self { 52 | Self { 53 | #[cfg(feature = "2d")] 54 | translation: value.translation.truncate().adjust_precision(), 55 | #[cfg(feature = "3d")] 56 | translation: value.translation.adjust_precision(), 57 | rotation: Rotation::from(value.rotation.adjust_precision()), 58 | #[cfg(feature = "2d")] 59 | scale: value.scale.truncate().adjust_precision(), 60 | #[cfg(feature = "3d")] 61 | scale: value.scale.adjust_precision(), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /benches/README.md: -------------------------------------------------------------------------------- 1 | # Avian Benchmarks 2 | 3 | Benchmarks for `avian2d` and `avian3d` for measuring performance and scaling in various test scenes. 4 | 5 | ## Running the Benchmarks 6 | 7 | The `benches` CLI is provided for running the benchmarks with various configurations. 8 | 9 | ```text 10 | Options: 11 | -n, --name The name or number of the benchmark to run. Leave empty to run all benchmarks 12 | -t, --threads A range for which thread counts to run the benchmarks with. Can be specified as `start..end` (exclusive), `start..=end` (inclusive), or `start` [default: 1..13] 13 | -s, --steps The number of steps to run for each benchmark [default: 300] 14 | -r, --repeat The number of times to repeat each benchmark. The results will be averaged over these repetitions [default: 5] 15 | -l, --list List all available benchmarks in a numbered list 16 | -o, --output The output directory where results are written in CSV format. Leave empty to disable output 17 | -h, --help Print help 18 | -V, --version Print version 19 | ``` 20 | 21 | An example of running the "Large Pyramid 2D" benchmark with 1-6 threads and 500 steps, repeated 5 times: 22 | 23 | ```shell 24 | # Note: Make sure `benches` is the active working directory 25 | cargo run -- --name "Large Pyramid 2D" --threads 1..=6 --steps 500 --repeat 5 26 | ``` 27 | 28 | The output might look something like this: 29 | 30 | ```text 31 | Running benchmark 'Large Pyramid 2D' with threads ranging from 1 to 6: 32 | | threads | avg time / step | min time / step | 33 | | ------- | --------------- | --------------- | 34 | | 1 | 12.29045 ms | 11.22260 ms | 35 | | 2 | 10.40321 ms | 9.27592 ms | 36 | | 3 | 9.65242 ms | 8.53754 ms | 37 | | 4 | 9.19108 ms | 8.15204 ms | 38 | | 5 | 9.03052 ms | 8.08754 ms | 39 | | 6 | 8.91510 ms | 7.87406 ms | 40 | ``` 41 | 42 | To only run benchmarks for a specific dimension, disable default features and enable the `2d` or `3d` feature: 43 | 44 | ```shell 45 | # List all 2D benchmarks 46 | cargo run --no-default-features --features 2d -- --list 47 | 48 | # Run all 3D benchmarks with default options 49 | cargo run --no-default-features --features 3d 50 | ``` 51 | 52 | Note that the `dev` profile has been configured to be equivalent to the `release` profile 53 | in the `Cargo.toml`, so the `--release` flag is optional. 54 | -------------------------------------------------------------------------------- /src/dynamics/joints/images/prismatic_joint.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | body2 9 | 10 | 11 | 12 | 13 | 14 | body1 15 | 16 | 17 | 18 | 19 | anchor1 20 | 21 | 22 | anchor2 23 | 24 | 25 | min 26 | 27 | 28 | max 29 | 30 | 31 | slider_axis 32 | 33 | -------------------------------------------------------------------------------- /crates/avian2d/examples/ray_caster.rs: -------------------------------------------------------------------------------- 1 | //! A simple raycasting example that uses the [`RayCaster`] component. 2 | //! 3 | //! An alternative, more controlled approach is to use the methods of 4 | //! the [`SpatialQuery`] system parameter. 5 | 6 | #![allow(clippy::unnecessary_cast)] 7 | 8 | use avian2d::{math::*, prelude::*}; 9 | use bevy::{ 10 | color::palettes::css::{GREEN, ORANGE_RED}, 11 | prelude::*, 12 | }; 13 | use examples_common_2d::*; 14 | 15 | fn main() { 16 | App::new() 17 | .add_plugins(( 18 | DefaultPlugins, 19 | ExampleCommonPlugin, 20 | PhysicsPlugins::default(), 21 | )) 22 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 23 | .add_systems(Update, render_rays) 24 | .add_systems(Startup, setup) 25 | .run(); 26 | } 27 | 28 | fn setup( 29 | mut commands: Commands, 30 | mut meshes: ResMut>, 31 | mut materials: ResMut>, 32 | ) { 33 | commands.spawn(Camera2d); 34 | 35 | // Spawn a perimeter of circles that the ray will be cast against 36 | let radius = 16.0; 37 | for x in -4..=4 { 38 | for y in -4..=4 { 39 | if (-3..4).contains(&x) && (-3..4).contains(&y) { 40 | continue; 41 | } 42 | 43 | commands.spawn(( 44 | Mesh2d(meshes.add(Circle::new(radius))), 45 | MeshMaterial2d(materials.add(Color::srgb(0.2, 0.7, 0.9))), 46 | Transform::from_xyz(x as f32 * radius * 3.0, y as f32 * radius * 3.0, 0.0), 47 | Collider::circle(radius as Scalar), 48 | )); 49 | } 50 | } 51 | 52 | // Spawn a rotating kinematic body with a ray caster 53 | commands.spawn(( 54 | RigidBody::Kinematic, 55 | AngularVelocity(0.2), 56 | RayCaster::new(Vector::ZERO, Dir2::X), 57 | )); 58 | } 59 | 60 | // Note: The `PhysicsDebugPlugin` can also render rays, hit points, and normals. 61 | // This system is primarily for demonstration purposes. 62 | fn render_rays(mut rays: Query<(&mut RayCaster, &mut RayHits)>, mut gizmos: Gizmos) { 63 | for (ray, hits) in &mut rays { 64 | // Convert to Vec3 for lines 65 | let origin = ray.global_origin().f32(); 66 | let direction = ray.global_direction().f32(); 67 | 68 | for hit in hits.iter() { 69 | gizmos.line_2d(origin, origin + direction * hit.distance as f32, GREEN); 70 | } 71 | if hits.is_empty() { 72 | gizmos.line_2d(origin, origin + direction * 1_000_000.0, ORANGE_RED); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/avian3d/examples/collider_constructors.rs: -------------------------------------------------------------------------------- 1 | //! An example showcasing how to create colliders for meshes and scenes 2 | //! using `ColliderConstructor` and `ColliderConstructorHierarchy` respectively. 3 | 4 | use avian3d::prelude::*; 5 | use bevy::prelude::*; 6 | use examples_common_3d::ExampleCommonPlugin; 7 | 8 | fn main() { 9 | App::new() 10 | .add_plugins(( 11 | DefaultPlugins, 12 | ExampleCommonPlugin, 13 | PhysicsPlugins::default(), 14 | )) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup( 20 | mut commands: Commands, 21 | mut materials: ResMut>, 22 | mut meshes: ResMut>, 23 | assets: ResMut, 24 | ) { 25 | // Spawn ground and generate a collider for the mesh using ColliderConstructor 26 | commands.spawn(( 27 | Mesh3d(meshes.add(Plane3d::default().mesh().size(8.0, 8.0))), 28 | MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), 29 | ColliderConstructor::TrimeshFromMesh, 30 | RigidBody::Static, 31 | )); 32 | 33 | // Spawn Ferris the crab and generate colliders for the scene using ColliderConstructorHierarchy 34 | commands.spawn(( 35 | // The model was made by RayMarch, licenced under CC0-1.0, and can be found here: 36 | // https://github.com/RayMarch/ferris3d 37 | SceneRoot(assets.load("ferris.glb#Scene0")), 38 | Transform::from_xyz(0.0, 1.0, 0.0).with_scale(Vec3::splat(2.0)), 39 | // Create colliders using convex decomposition. 40 | // This takes longer than creating a trimesh or convex hull collider, 41 | // but is more performant for collision detection. 42 | ColliderConstructorHierarchy::new(ColliderConstructor::ConvexDecompositionFromMesh) 43 | // Make the arms heavier to make it easier to stand upright 44 | // Note: Bevy uses the format `MeshName.MaterialName` for the `Name` of glTF mesh primitives. 45 | .with_density_for_name("armL_mesh.ferris_material", 3.0) 46 | .with_density_for_name("armR_mesh.ferris_material", 3.0), 47 | RigidBody::Dynamic, 48 | )); 49 | 50 | // Light 51 | commands.spawn(( 52 | PointLight { 53 | intensity: 1_000_000.0, 54 | shadows_enabled: true, 55 | ..default() 56 | }, 57 | Transform::from_xyz(2.0, 8.0, 2.0), 58 | )); 59 | 60 | // Camera 61 | commands.spawn(( 62 | Camera3d::default(), 63 | Transform::from_xyz(-5.0, 3.5, 5.5).looking_at(Vec3::ZERO, Vec3::Y), 64 | )); 65 | } 66 | -------------------------------------------------------------------------------- /src/collision/contact_types/feature_id.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | /// A feature ID indicating the type of a geometric feature: a vertex, an edge, or (in 3D) a face. 4 | /// 5 | /// This type packs the feature type into the same value as the feature index, 6 | /// which indicates the specific vertex/edge/face that this ID belongs to. 7 | #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Reflect)] 8 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 9 | #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] 10 | #[reflect(Debug, Hash, PartialEq)] 11 | pub struct PackedFeatureId(pub u32); 12 | 13 | impl PackedFeatureId { 14 | /// Packed feature id identifying an unknown feature. 15 | pub const UNKNOWN: Self = Self(0); 16 | 17 | const CODE_MASK: u32 = 0x3fff_ffff; 18 | const HEADER_MASK: u32 = !Self::CODE_MASK; 19 | const HEADER_VERTEX: u32 = 0b01 << 30; 20 | #[cfg(feature = "3d")] 21 | const HEADER_EDGE: u32 = 0b10 << 30; 22 | const HEADER_FACE: u32 = 0b11 << 30; 23 | 24 | /// Converts a vertex feature id into a packed feature id. 25 | pub fn vertex(code: u32) -> Self { 26 | assert_eq!(code & Self::HEADER_MASK, 0); 27 | Self(Self::HEADER_VERTEX | code) 28 | } 29 | 30 | /// Converts a edge feature id into a packed feature id. 31 | #[cfg(feature = "3d")] 32 | pub fn edge(code: u32) -> Self { 33 | assert_eq!(code & Self::HEADER_MASK, 0); 34 | Self(Self::HEADER_EDGE | code) 35 | } 36 | 37 | /// Converts a face feature id into a packed feature id. 38 | pub fn face(code: u32) -> Self { 39 | assert_eq!(code & Self::HEADER_MASK, 0); 40 | Self(Self::HEADER_FACE | code) 41 | } 42 | 43 | /// Is the identified feature a face? 44 | pub fn is_face(self) -> bool { 45 | self.0 & Self::HEADER_MASK == Self::HEADER_FACE 46 | } 47 | 48 | /// Is the identified feature a vertex? 49 | pub fn is_vertex(self) -> bool { 50 | self.0 & Self::HEADER_MASK == Self::HEADER_VERTEX 51 | } 52 | 53 | /// Is the identified feature an edge? 54 | #[cfg(feature = "3d")] 55 | pub fn is_edge(self) -> bool { 56 | self.0 & Self::HEADER_MASK == Self::HEADER_EDGE 57 | } 58 | 59 | /// Is the identified feature unknown? 60 | pub fn is_unknown(self) -> bool { 61 | self == Self::UNKNOWN 62 | } 63 | } 64 | 65 | impl From for PackedFeatureId { 66 | fn from(code: u32) -> Self { 67 | Self(code) 68 | } 69 | } 70 | 71 | #[cfg(any(feature = "parry-f32", feature = "parry-f64"))] 72 | impl From for PackedFeatureId { 73 | fn from(id: crate::parry::shape::PackedFeatureId) -> Self { 74 | Self(id.0) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Miscallaneous utility functions. 2 | 3 | pub(crate) use bevy::platform::time::Instant; 4 | 5 | // TODO: The single-threaded and multi-threaded versions are duplicated here because 6 | // of the different trait bounds on `F`. Unify them somehow? 7 | 8 | /// A helper function for iterating over a slice in parallel or serially 9 | /// based on the `parallel` feature. 10 | /// 11 | /// If `slice.len() < min_len`, serial iteration will be used. 12 | /// 13 | /// The `ComputeTaskPool` is used if parallelism is enabled. 14 | /// 15 | /// # Example 16 | /// 17 | /// ```ignore 18 | /// let mut slice = vec![1, 2, 3, 4]; 19 | /// 20 | /// par_for_each(&mut slice, |index, item| { 21 | /// *item += index; 22 | /// }); 23 | /// 24 | /// assert_eq!(slice, vec![1, 3, 5, 7]); 25 | /// ``` 26 | #[inline(always)] 27 | #[allow(unused_variables, unused_mut)] 28 | #[cfg(not(feature = "parallel"))] 29 | pub fn par_for_each(mut slice: &mut [T], min_len: usize, mut f: F) 30 | where 31 | T: Send + Sync, 32 | F: FnMut(usize, &mut T) + Send + Sync, 33 | { 34 | slice.iter_mut().enumerate().for_each(|(index, item)| { 35 | f(index, item); 36 | }); 37 | } 38 | 39 | /// A helper function for iterating over a slice in parallel or serially 40 | /// based on the `parallel` feature. 41 | /// 42 | /// If `slice.len() < min_len`, serial iteration will be used. 43 | /// 44 | /// The `ComputeTaskPool` is used if parallelism is enabled. 45 | /// 46 | /// # Example 47 | /// 48 | /// ```ignore 49 | /// let mut slice = vec![1, 2, 3, 4]; 50 | /// 51 | /// par_for_each(&mut slice, |index, item| { 52 | /// *item += index; 53 | /// }); 54 | /// 55 | /// assert_eq!(slice, vec![1, 3, 5, 7]); 56 | /// ``` 57 | #[inline(always)] 58 | #[allow(unused_variables, unused_mut)] 59 | #[cfg(feature = "parallel")] 60 | pub fn par_for_each(mut slice: &mut [T], min_len: usize, mut f: F) 61 | where 62 | T: Send + Sync, 63 | F: Fn(usize, &mut T) + Send + Sync, 64 | { 65 | let task_pool_ = bevy::tasks::ComputeTaskPool::get(); 66 | 67 | if task_pool_.thread_num() == 1 || slice.len() < min_len { 68 | slice.iter_mut().enumerate().for_each(|(index, item)| { 69 | f(index, item); 70 | }); 71 | } else { 72 | // TODO: Is there a better approach than `par_chunk_map_mut`? 73 | let chunk_size_ = (slice.len() / task_pool_.thread_num()).max(1); 74 | bevy::tasks::ParallelSliceMut::par_chunk_map_mut( 75 | &mut slice, 76 | task_pool_, 77 | chunk_size_, 78 | |chunk_index_, chunk_| { 79 | let index_offset_ = chunk_index_ * chunk_size_; 80 | chunk_.iter_mut().enumerate().for_each(|(i, item)| { 81 | let index = index_offset_ + i; 82 | f(index, item); 83 | }); 84 | }, 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/examples_common_2d/src/lib.rs: -------------------------------------------------------------------------------- 1 | use avian2d::prelude::*; 2 | use bevy::{ 3 | diagnostic::FrameTimeDiagnosticsPlugin, input::common_conditions::input_just_pressed, 4 | prelude::*, 5 | }; 6 | 7 | /// A plugin that adds common functionality used by examples, 8 | /// such as physics diagnostics UI and the ability to pause and step the simulation. 9 | pub struct ExampleCommonPlugin; 10 | 11 | impl Plugin for ExampleCommonPlugin { 12 | fn build(&self, app: &mut App) { 13 | // Add diagnostics. 14 | app.add_plugins(( 15 | PhysicsDiagnosticsPlugin, 16 | PhysicsDiagnosticsUiPlugin, 17 | FrameTimeDiagnosticsPlugin::default(), 18 | )); 19 | 20 | // Configure the default physics diagnostics UI. 21 | app.insert_resource(PhysicsDiagnosticsUiSettings { 22 | enabled: false, 23 | ..default() 24 | }); 25 | 26 | // Spawn text instructions for keybinds. 27 | app.add_systems(Startup, setup_key_instructions); 28 | 29 | // Add systems for toggling the diagnostics UI and pausing and stepping the simulation. 30 | app.add_systems( 31 | Update, 32 | ( 33 | toggle_diagnostics_ui.run_if(input_just_pressed(KeyCode::KeyU)), 34 | toggle_paused.run_if(input_just_pressed(KeyCode::KeyP)), 35 | step.run_if(physics_paused.and(input_just_pressed(KeyCode::Enter))), 36 | ), 37 | ); 38 | } 39 | 40 | #[cfg(feature = "use-debug-plugin")] 41 | fn finish(&self, app: &mut App) { 42 | // Add the physics debug plugin automatically if the `use-debug-plugin` feature is enabled 43 | // and the plugin is not already added. 44 | if !app.is_plugin_added::() { 45 | app.add_plugins(PhysicsDebugPlugin); 46 | } 47 | } 48 | } 49 | 50 | fn toggle_diagnostics_ui(mut settings: ResMut) { 51 | settings.enabled = !settings.enabled; 52 | } 53 | 54 | fn physics_paused(time: Res>) -> bool { 55 | time.is_paused() 56 | } 57 | 58 | fn toggle_paused(mut time: ResMut>) { 59 | if time.is_paused() { 60 | time.unpause(); 61 | } else { 62 | time.pause(); 63 | } 64 | } 65 | 66 | /// Advances the physics simulation by one `Time` time step. 67 | fn step(mut physics_time: ResMut>, fixed_time: Res>) { 68 | physics_time.advance_by(fixed_time.delta()); 69 | } 70 | 71 | fn setup_key_instructions(mut commands: Commands) { 72 | commands.spawn(( 73 | Text::new("U: Diagnostics UI | P: Pause/Unpause | Enter: Step"), 74 | TextFont { 75 | font_size: 10.0, 76 | ..default() 77 | }, 78 | Node { 79 | position_type: PositionType::Absolute, 80 | top: Val::Px(5.0), 81 | right: Val::Px(5.0), 82 | ..default() 83 | }, 84 | )); 85 | } 86 | -------------------------------------------------------------------------------- /crates/examples_common_3d/src/lib.rs: -------------------------------------------------------------------------------- 1 | use avian3d::prelude::*; 2 | use bevy::{ 3 | diagnostic::FrameTimeDiagnosticsPlugin, input::common_conditions::input_just_pressed, 4 | prelude::*, 5 | }; 6 | 7 | /// A plugin that adds common functionality used by examples, 8 | /// such as physics diagnostics UI and the ability to pause and step the simulation. 9 | pub struct ExampleCommonPlugin; 10 | 11 | impl Plugin for ExampleCommonPlugin { 12 | fn build(&self, app: &mut App) { 13 | // Add diagnostics. 14 | app.add_plugins(( 15 | PhysicsDiagnosticsPlugin, 16 | PhysicsDiagnosticsUiPlugin, 17 | FrameTimeDiagnosticsPlugin::default(), 18 | )); 19 | 20 | // Configure the default physics diagnostics UI. 21 | app.insert_resource(PhysicsDiagnosticsUiSettings { 22 | enabled: false, 23 | ..default() 24 | }); 25 | 26 | // Spawn text instructions for keybinds. 27 | app.add_systems(Startup, setup_key_instructions); 28 | 29 | // Add systems for toggling the diagnostics UI and pausing and stepping the simulation. 30 | app.add_systems( 31 | Update, 32 | ( 33 | toggle_diagnostics_ui.run_if(input_just_pressed(KeyCode::KeyU)), 34 | toggle_paused.run_if(input_just_pressed(KeyCode::KeyP)), 35 | step.run_if(physics_paused.and(input_just_pressed(KeyCode::Enter))), 36 | ), 37 | ); 38 | } 39 | 40 | #[cfg(feature = "use-debug-plugin")] 41 | fn finish(&self, app: &mut App) { 42 | // Add the physics debug plugin automatically if the `use-debug-plugin` feature is enabled 43 | // and the plugin is not already added. 44 | if !app.is_plugin_added::() { 45 | app.add_plugins(PhysicsDebugPlugin); 46 | } 47 | } 48 | } 49 | 50 | fn toggle_diagnostics_ui(mut settings: ResMut) { 51 | settings.enabled = !settings.enabled; 52 | } 53 | 54 | fn physics_paused(time: Res>) -> bool { 55 | time.is_paused() 56 | } 57 | 58 | fn toggle_paused(mut time: ResMut>) { 59 | if time.is_paused() { 60 | time.unpause(); 61 | } else { 62 | time.pause(); 63 | } 64 | } 65 | 66 | /// Advances the physics simulation by one `Time` time step. 67 | fn step(mut physics_time: ResMut>, fixed_time: Res>) { 68 | physics_time.advance_by(fixed_time.delta()); 69 | } 70 | 71 | fn setup_key_instructions(mut commands: Commands) { 72 | commands.spawn(( 73 | Text::new("U: Diagnostics UI | P: Pause/Unpause | Enter: Step"), 74 | TextFont { 75 | font_size: 10.0, 76 | ..default() 77 | }, 78 | Node { 79 | position_type: PositionType::Absolute, 80 | top: Val::Px(5.0), 81 | right: Val::Px(5.0), 82 | ..default() 83 | }, 84 | )); 85 | } 86 | -------------------------------------------------------------------------------- /src/data_structures/id_pool.rs: -------------------------------------------------------------------------------- 1 | //! An [`IdPool`] for managing and reusing identifiers. 2 | 3 | use alloc::collections::BinaryHeap; 4 | use core::cmp::Reverse; 5 | 6 | /// A pool for efficient allocation and reuse of `u32` identifiers. 7 | /// 8 | /// Freed IDs are stored in a min-heap, and reused such that the lowest available IDs are allocated first. 9 | #[derive(Clone, Debug, Default)] 10 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 11 | pub struct IdPool { 12 | /// A min-heap of free IDs. The lowest free IDs are allocated first. 13 | free_ids: BinaryHeap>, 14 | /// The next ID to be allocated. Only incremented when no free IDs are available. 15 | next_index: u32, 16 | } 17 | 18 | impl IdPool { 19 | /// Creates a new empty [`IdPool`]. 20 | #[inline(always)] 21 | pub const fn new() -> Self { 22 | Self { 23 | free_ids: BinaryHeap::new(), 24 | next_index: 0, 25 | } 26 | } 27 | 28 | /// Creates a new [`IdPool`] with the given initial capacity. 29 | /// 30 | /// This is useful for preallocating space for IDs to avoid reallocations. 31 | #[inline(always)] 32 | pub fn with_capacity(capacity: usize) -> Self { 33 | Self { 34 | free_ids: BinaryHeap::with_capacity(capacity), 35 | next_index: 0, 36 | } 37 | } 38 | 39 | /// Allocates a new ID. 40 | /// 41 | /// If there are free IDs available, the lowest free ID is reused. 42 | #[inline(always)] 43 | pub fn alloc(&mut self) -> u32 { 44 | if let Some(id) = self.free_ids.pop() { 45 | id.0 46 | } else { 47 | let id = self.next_index; 48 | self.next_index += 1; 49 | id 50 | } 51 | } 52 | 53 | /// Frees an ID, making it available for reuse. 54 | /// 55 | /// The ID is assumed to not already be freed. 56 | #[inline(always)] 57 | pub fn free(&mut self, id: u32) { 58 | debug_assert!(id < self.next_index); 59 | self.free_ids.push(Reverse(id)); 60 | } 61 | 62 | /// Clears the pool, removing all free IDs and resetting the next index. 63 | #[inline(always)] 64 | pub fn clear(&mut self) { 65 | self.free_ids.clear(); 66 | self.next_index = 0; 67 | } 68 | 69 | /// Returns the number of allocated IDs. 70 | #[inline(always)] 71 | pub fn len(&self) -> usize { 72 | self.next_index as usize - self.free_ids.len() 73 | } 74 | 75 | /// Returns the number of free IDs. 76 | #[inline(always)] 77 | pub fn free_len(&self) -> usize { 78 | self.free_ids.len() 79 | } 80 | 81 | /// Returns the total number of IDs (allocated + free). 82 | #[inline(always)] 83 | pub fn total_len(&self) -> usize { 84 | self.next_index as usize 85 | } 86 | 87 | /// Returns `true` if the pool is empty (no allocated IDs). 88 | #[inline(always)] 89 | pub fn is_empty(&self) -> bool { 90 | self.len() == 0 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/avian3d/examples/dynamic_character_3d/main.rs: -------------------------------------------------------------------------------- 1 | //! A basic implementation of a character controller for a dynamic rigid body. 2 | //! 3 | //! This showcases the following: 4 | //! 5 | //! - Basic directional movement and jumping 6 | //! - Support for both keyboard and gamepad input 7 | //! - A configurable maximum slope angle for jumping 8 | //! - Loading a platformer environment from a glTF 9 | //! 10 | //! The character controller logic is contained within the `plugin` module. 11 | //! 12 | //! For a kinematic character controller, see the `kinematic_character_3d` example. 13 | 14 | mod plugin; 15 | 16 | use avian3d::{math::*, prelude::*}; 17 | use bevy::prelude::*; 18 | use examples_common_3d::ExampleCommonPlugin; 19 | use plugin::*; 20 | 21 | fn main() { 22 | App::new() 23 | .add_plugins(( 24 | DefaultPlugins, 25 | ExampleCommonPlugin, 26 | PhysicsPlugins::default(), 27 | CharacterControllerPlugin, 28 | )) 29 | .add_systems(Startup, setup) 30 | .run(); 31 | } 32 | 33 | fn setup( 34 | mut commands: Commands, 35 | mut meshes: ResMut>, 36 | mut materials: ResMut>, 37 | assets: Res, 38 | ) { 39 | // Player 40 | commands.spawn(( 41 | Mesh3d(meshes.add(Capsule3d::new(0.4, 1.0))), 42 | MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), 43 | Transform::from_xyz(0.0, 1.5, 0.0), 44 | CharacterControllerBundle::new(Collider::capsule(0.4, 1.0)).with_movement( 45 | 30.0, 46 | 0.92, 47 | 7.0, 48 | (30.0 as Scalar).to_radians(), 49 | ), 50 | Friction::ZERO.with_combine_rule(CoefficientCombine::Min), 51 | Restitution::ZERO.with_combine_rule(CoefficientCombine::Min), 52 | GravityScale(2.0), 53 | )); 54 | 55 | // A cube to move around 56 | commands.spawn(( 57 | RigidBody::Dynamic, 58 | Collider::cuboid(1.0, 1.0, 1.0), 59 | Mesh3d(meshes.add(Cuboid::default())), 60 | MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), 61 | Transform::from_xyz(3.0, 2.0, 3.0), 62 | )); 63 | 64 | // Environment (see the `collider_constructors` example for creating colliders from scenes) 65 | commands.spawn(( 66 | SceneRoot(assets.load("character_controller_demo.glb#Scene0")), 67 | Transform::from_rotation(Quat::from_rotation_y(-core::f32::consts::PI * 0.5)), 68 | ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh), 69 | RigidBody::Static, 70 | )); 71 | 72 | // Light 73 | commands.spawn(( 74 | PointLight { 75 | intensity: 2_000_000.0, 76 | range: 50.0, 77 | shadows_enabled: true, 78 | ..default() 79 | }, 80 | Transform::from_xyz(0.0, 15.0, 0.0), 81 | )); 82 | 83 | // Camera 84 | commands.spawn(( 85 | Camera3d::default(), 86 | Transform::from_xyz(-7.0, 9.5, 15.0).looking_at(Vec3::ZERO, Vec3::Y), 87 | )); 88 | } 89 | -------------------------------------------------------------------------------- /src/dynamics/solver/xpbd/joints/fixed.rs: -------------------------------------------------------------------------------- 1 | use super::{FixedAngleConstraintShared, PointConstraintShared}; 2 | use crate::{ 3 | dynamics::solver::{ 4 | solver_body::{SolverBody, SolverBodyInertia}, 5 | xpbd::*, 6 | }, 7 | prelude::*, 8 | }; 9 | use bevy::prelude::*; 10 | 11 | /// Solver data for the [`FixedJoint`]. 12 | #[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)] 13 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 14 | #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] 15 | #[reflect(Component, Debug, PartialEq)] 16 | pub struct FixedJointSolverData { 17 | pub(super) point_constraint: PointConstraintShared, 18 | pub(super) angle_constraint: FixedAngleConstraintShared, 19 | } 20 | 21 | impl XpbdConstraintSolverData for FixedJointSolverData { 22 | fn clear_lagrange_multipliers(&mut self) { 23 | self.point_constraint.clear_lagrange_multipliers(); 24 | self.angle_constraint.clear_lagrange_multipliers(); 25 | } 26 | 27 | fn total_position_lagrange(&self) -> Vector { 28 | self.point_constraint.total_position_lagrange() 29 | } 30 | 31 | fn total_rotation_lagrange(&self) -> AngularVector { 32 | self.angle_constraint.total_rotation_lagrange() 33 | } 34 | } 35 | 36 | impl XpbdConstraint<2> for FixedJoint { 37 | type SolverData = FixedJointSolverData; 38 | 39 | fn prepare( 40 | &mut self, 41 | bodies: [&RigidBodyQueryReadOnlyItem; 2], 42 | solver_data: &mut FixedJointSolverData, 43 | ) { 44 | let [body1, body2] = bodies; 45 | 46 | let Some(local_anchor1) = self.local_anchor1() else { 47 | return; 48 | }; 49 | let Some(local_anchor2) = self.local_anchor2() else { 50 | return; 51 | }; 52 | let Some(local_basis1) = self.local_basis1() else { 53 | return; 54 | }; 55 | let Some(local_basis2) = self.local_basis2() else { 56 | return; 57 | }; 58 | 59 | // Prepare the point-to-point constraint. 60 | solver_data 61 | .point_constraint 62 | .prepare(bodies, local_anchor1, local_anchor2); 63 | 64 | // Prepare the angular constraint. 65 | solver_data.angle_constraint.prepare( 66 | body1.rotation, 67 | body2.rotation, 68 | local_basis1, 69 | local_basis2, 70 | ); 71 | } 72 | 73 | fn solve( 74 | &mut self, 75 | bodies: [&mut SolverBody; 2], 76 | inertias: [&SolverBodyInertia; 2], 77 | solver_data: &mut FixedJointSolverData, 78 | dt: Scalar, 79 | ) { 80 | let [body1, body2] = bodies; 81 | 82 | // Solve the angular constraint. 83 | solver_data 84 | .angle_constraint 85 | .solve([body1, body2], inertias, self.angle_compliance, dt); 86 | 87 | // Solve the point-to-point constraint. 88 | solver_data 89 | .point_constraint 90 | .solve([body1, body2], inertias, self.point_compliance, dt); 91 | } 92 | } 93 | 94 | impl PositionConstraint for FixedJoint {} 95 | 96 | impl AngularConstraint for FixedJoint {} 97 | -------------------------------------------------------------------------------- /crates/avian3d/examples/kinematic_character_3d/main.rs: -------------------------------------------------------------------------------- 1 | //! A basic implementation of a character controller for a kinematic rigid body. 2 | //! 3 | //! This showcases the following: 4 | //! 5 | //! - Basic directional movement and jumping 6 | //! - Support for both keyboard and gamepad input 7 | //! - A configurable maximum slope angle 8 | //! - Collision response for kinematic bodies 9 | //! - Loading a platformer environment from a glTF 10 | //! 11 | //! The character controller logic is contained within the `plugin` module. 12 | //! 13 | //! For a dynamic character controller, see the `dynamic_character_3d` example. 14 | //! 15 | //! # Warning 16 | //! 17 | //! Note that this is *not* intended to be a fully featured character controller, 18 | //! and the collision logic is quite basic. 19 | //! 20 | //! For a better solution, consider implementing a "collide-and-slide" algorithm, 21 | //! or use an existing third party character controller plugin like Bevy Tnua 22 | //! (a dynamic character controller). 23 | 24 | mod plugin; 25 | 26 | use avian3d::{math::*, prelude::*}; 27 | use bevy::prelude::*; 28 | use examples_common_3d::ExampleCommonPlugin; 29 | use plugin::*; 30 | 31 | fn main() { 32 | App::new() 33 | .add_plugins(( 34 | DefaultPlugins, 35 | ExampleCommonPlugin, 36 | PhysicsPlugins::default(), 37 | CharacterControllerPlugin, 38 | )) 39 | .add_systems(Startup, setup) 40 | .run(); 41 | } 42 | 43 | fn setup( 44 | mut commands: Commands, 45 | mut meshes: ResMut>, 46 | mut materials: ResMut>, 47 | assets: Res, 48 | ) { 49 | // Player 50 | commands.spawn(( 51 | Mesh3d(meshes.add(Capsule3d::new(0.4, 1.0))), 52 | MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), 53 | Transform::from_xyz(0.0, 1.5, 0.0), 54 | CharacterControllerBundle::new(Collider::capsule(0.4, 1.0), Vector::NEG_Y * 9.81 * 2.0) 55 | .with_movement(30.0, 0.92, 7.0, (30.0 as Scalar).to_radians()), 56 | )); 57 | 58 | // A cube to move around 59 | commands.spawn(( 60 | RigidBody::Dynamic, 61 | Collider::cuboid(1.0, 1.0, 1.0), 62 | Mesh3d(meshes.add(Cuboid::default())), 63 | MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), 64 | Transform::from_xyz(3.0, 2.0, 3.0), 65 | )); 66 | 67 | // Environment (see the `collider_constructors` example for creating colliders from scenes) 68 | commands.spawn(( 69 | SceneRoot(assets.load("character_controller_demo.glb#Scene0")), 70 | Transform::from_rotation(Quat::from_rotation_y(-core::f32::consts::PI * 0.5)), 71 | ColliderConstructorHierarchy::new(ColliderConstructor::ConvexHullFromMesh), 72 | RigidBody::Static, 73 | )); 74 | 75 | // Light 76 | commands.spawn(( 77 | PointLight { 78 | intensity: 2_000_000.0, 79 | range: 50.0, 80 | shadows_enabled: true, 81 | ..default() 82 | }, 83 | Transform::from_xyz(0.0, 15.0, 0.0), 84 | )); 85 | 86 | // Camera 87 | commands.spawn(( 88 | Camera3d::default(), 89 | Transform::from_xyz(-7.0, 9.5, 15.0).looking_at(Vec3::ZERO, Vec3::Y), 90 | )); 91 | } 92 | -------------------------------------------------------------------------------- /crates/avian2d/examples/chain_2d.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unnecessary_cast)] 2 | 3 | use avian2d::{math::*, prelude::*}; 4 | use bevy::{prelude::*, window::PrimaryWindow}; 5 | use examples_common_2d::ExampleCommonPlugin; 6 | 7 | fn main() { 8 | App::new() 9 | .add_plugins(( 10 | DefaultPlugins, 11 | ExampleCommonPlugin, 12 | PhysicsPlugins::default(), 13 | )) 14 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 15 | .insert_resource(SubstepCount(50)) 16 | .insert_resource(Gravity(Vector::NEG_Y * 1000.0)) 17 | .add_systems(Startup, setup) 18 | .add_systems(Update, follow_mouse) 19 | .run(); 20 | } 21 | 22 | #[derive(Component)] 23 | struct FollowMouse; 24 | 25 | fn setup( 26 | mut commands: Commands, 27 | mut materials: ResMut>, 28 | mut meshes: ResMut>, 29 | ) { 30 | commands.spawn(Camera2d); 31 | 32 | let particle_count = 100; 33 | let particle_radius = 1.2; 34 | let particle_mesh = meshes.add(Circle::new(particle_radius as f32)); 35 | let particle_material = materials.add(Color::srgb(0.2, 0.7, 0.9)); 36 | 37 | // Spawn kinematic particle that can follow the mouse 38 | let mut previous_particle = commands 39 | .spawn(( 40 | RigidBody::Kinematic, 41 | FollowMouse, 42 | Mesh2d(particle_mesh.clone()), 43 | MeshMaterial2d(particle_material.clone()), 44 | )) 45 | .id(); 46 | 47 | // Spawn the rest of the particles, connecting each one to the previous one with joints 48 | for i in 1..particle_count { 49 | let current_particle = commands 50 | .spawn(( 51 | RigidBody::Dynamic, 52 | MassPropertiesBundle::from_shape(&Circle::new(particle_radius as f32), 1.0), 53 | Mesh2d(particle_mesh.clone()), 54 | MeshMaterial2d(particle_material.clone()), 55 | Transform::from_xyz(0.0, -i as f32 * (particle_radius as f32 * 2.0 + 1.0), 0.0), 56 | )) 57 | .id(); 58 | 59 | commands.spawn( 60 | RevoluteJoint::new(previous_particle, current_particle) 61 | .with_local_anchor2(Vector::Y * (particle_radius * 2.0 + 1.0)) 62 | .with_point_compliance(0.0000001), 63 | ); 64 | 65 | previous_particle = current_particle; 66 | } 67 | } 68 | 69 | fn follow_mouse( 70 | buttons: Res>, 71 | windows: Query<&Window, With>, 72 | camera: Query<(&Camera, &GlobalTransform)>, 73 | mut follower: Query<&mut Transform, With>, 74 | ) -> Result { 75 | if buttons.pressed(MouseButton::Left) { 76 | let window = windows.single()?; 77 | let (camera, camera_transform) = camera.single()?; 78 | let mut follower_position = follower.single_mut()?; 79 | 80 | if let Some(cursor_world_pos) = window 81 | .cursor_position() 82 | .and_then(|cursor| camera.viewport_to_world_2d(camera_transform, cursor).ok()) 83 | { 84 | follower_position.translation = 85 | cursor_world_pos.extend(follower_position.translation.z); 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /src/dynamics/solver/xpbd/joints/shared/fixed_angle_constraint.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dynamics::solver::{ 3 | solver_body::{SolverBody, SolverBodyInertia}, 4 | xpbd::*, 5 | }, 6 | prelude::*, 7 | }; 8 | use bevy::prelude::*; 9 | 10 | /// Constraint data required by the XPBD constraint solver for a fixed angle constraint. 11 | #[derive(Clone, Copy, Debug, Default, PartialEq, Reflect)] 12 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 13 | #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] 14 | #[reflect(Debug, PartialEq)] 15 | pub struct FixedAngleConstraintShared { 16 | /// The target rotation difference between the two bodies. 17 | #[cfg(feature = "2d")] 18 | pub rotation_difference: Scalar, 19 | /// The target rotation difference between the two bodies. 20 | #[cfg(feature = "3d")] 21 | pub rotation_difference: Quaternion, 22 | /// The total Lagrange multiplier across the whole time step. 23 | pub total_lagrange: AngularVector, 24 | } 25 | 26 | impl XpbdConstraintSolverData for FixedAngleConstraintShared { 27 | fn clear_lagrange_multipliers(&mut self) { 28 | self.total_lagrange = AngularVector::ZERO; 29 | } 30 | 31 | fn total_rotation_lagrange(&self) -> AngularVector { 32 | self.total_lagrange 33 | } 34 | } 35 | 36 | impl FixedAngleConstraintShared { 37 | /// Prepares the constraint with the given rotations and local basis orientations. 38 | pub fn prepare( 39 | &mut self, 40 | rotation1: &Rotation, 41 | rotation2: &Rotation, 42 | local_basis1: Rot, 43 | local_basis2: Rot, 44 | ) { 45 | // Prepare the base rotation difference. 46 | #[cfg(feature = "2d")] 47 | { 48 | self.rotation_difference = 49 | (*rotation1 * local_basis1).angle_between(*rotation2 * local_basis2); 50 | } 51 | #[cfg(feature = "3d")] 52 | { 53 | self.rotation_difference = 54 | (rotation1.0 * local_basis1) * (rotation2.0 * local_basis2).inverse(); 55 | } 56 | } 57 | 58 | /// Solves the constraint for the given bodies. 59 | pub fn solve( 60 | &mut self, 61 | bodies: [&mut SolverBody; 2], 62 | inertias: [&SolverBodyInertia; 2], 63 | compliance: Scalar, 64 | dt: Scalar, 65 | ) { 66 | let [body1, body2] = bodies; 67 | let [inertia1, inertia2] = inertias; 68 | 69 | let inv_inertia1 = inertia1.effective_inv_angular_inertia(); 70 | let inv_inertia2 = inertia2.effective_inv_angular_inertia(); 71 | 72 | #[cfg(feature = "2d")] 73 | let difference = 74 | self.rotation_difference + body1.delta_rotation.angle_between(body2.delta_rotation); 75 | #[cfg(feature = "3d")] 76 | // TODO: The XPBD paper doesn't have this minus sign, but it seems to be needed for stability. 77 | // The angular correction code might have a wrong sign elsewhere. 78 | let difference = -2.0 79 | * (self.rotation_difference 80 | * body1.delta_rotation.0 81 | * body2.delta_rotation.0.inverse()) 82 | .xyz(); 83 | 84 | // Align orientation 85 | self.total_lagrange += self.align_orientation( 86 | body1, 87 | body2, 88 | inv_inertia1, 89 | inv_inertia2, 90 | difference, 91 | 0.0, 92 | compliance, 93 | dt, 94 | ); 95 | } 96 | } 97 | 98 | impl AngularConstraint for FixedAngleConstraintShared {} 99 | -------------------------------------------------------------------------------- /crates/avian2d/examples/many_pyramids_2d.rs: -------------------------------------------------------------------------------- 1 | use avian2d::prelude::*; 2 | use bevy::{camera::ScalingMode, prelude::*}; 3 | use examples_common_2d::ExampleCommonPlugin; 4 | 5 | fn main() { 6 | let mut app = App::new(); 7 | 8 | app.add_plugins(( 9 | DefaultPlugins, 10 | PhysicsPlugins::default(), 11 | ExampleCommonPlugin, 12 | )); 13 | 14 | app.add_systems(Startup, setup); 15 | 16 | app.run(); 17 | } 18 | 19 | fn setup( 20 | mut commands: Commands, 21 | mut meshes: ResMut>, 22 | mut materials: ResMut>, 23 | ) { 24 | let base_count = 10; 25 | let h = 0.5; 26 | let row_count = 10; 27 | let column_count = 10; 28 | 29 | let ground_delta_y = 2.0 * h * (base_count + 1) as f32; 30 | let ground_width = 2.0 * h * column_count as f32 * (base_count + 1) as f32; 31 | 32 | // Ground 33 | let ground_shape = Rectangle::new(ground_width, 0.01); 34 | let collider = Collider::from(ground_shape); 35 | let mesh = meshes.add(ground_shape); 36 | let material = materials.add(Color::srgb(0.3, 0.5, 0.3)); 37 | for i in 0..row_count { 38 | commands.spawn(( 39 | RigidBody::Static, 40 | collider.clone(), 41 | Transform::from_xyz(0.0, i as f32 * ground_delta_y, 0.0), 42 | Mesh2d(mesh.clone()), 43 | MeshMaterial2d(material.clone()), 44 | )); 45 | } 46 | 47 | let base_width = 2.0 * h * base_count as f32; 48 | 49 | for i in 0..row_count { 50 | let base_y = i as f32 * ground_delta_y; 51 | for j in 0..column_count { 52 | let center_x = -ground_width / 2.0 + j as f32 * (base_width + 2.0 * h) + h; 53 | spawn_small_pyramid( 54 | &mut commands, 55 | &mut meshes, 56 | &mut materials, 57 | base_count, 58 | h, 59 | center_x, 60 | base_y, 61 | ); 62 | } 63 | } 64 | 65 | commands.spawn(( 66 | Camera2d, 67 | Projection::Orthographic(OrthographicProjection { 68 | scaling_mode: ScalingMode::FixedVertical { 69 | viewport_height: row_count as f32 * ground_delta_y * 1.1, 70 | }, 71 | ..OrthographicProjection::default_2d() 72 | }), 73 | Transform::from_xyz(0.0, row_count as f32 * ground_delta_y / 2.0, 0.0), 74 | )); 75 | } 76 | 77 | /// Spawns a small pyramid structure at the specified position. 78 | fn spawn_small_pyramid( 79 | commands: &mut Commands, 80 | meshes: &mut ResMut>, 81 | materials: &mut ResMut>, 82 | base_count: usize, 83 | h: f32, 84 | center_x: f32, 85 | base_y: f32, 86 | ) { 87 | let box_size = 2.0 * h; 88 | let rectangle = Rectangle::from_length(box_size); 89 | let collider = Collider::from(rectangle); 90 | let mesh = meshes.add(rectangle); 91 | let material = materials.add(Color::srgb(0.2, 0.7, 0.9)); 92 | 93 | for i in 0..base_count { 94 | let y = (2 * i + 1) as f32 * h + base_y; 95 | 96 | for j in i..base_count { 97 | let x = (i + 1) as f32 * h + 2.0 * (j - i) as f32 * h + center_x - 0.5; 98 | 99 | commands.spawn(( 100 | RigidBody::Dynamic, 101 | collider.clone(), 102 | Transform::from_xyz(x, y, 0.0), 103 | Mesh2d(mesh.clone()), 104 | MeshMaterial2d(material.clone()), 105 | )); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/dynamics/solver/diagnostics.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | diagnostic::DiagnosticPath, 3 | prelude::{ReflectResource, Resource}, 4 | reflect::Reflect, 5 | }; 6 | use core::time::Duration; 7 | 8 | use crate::diagnostics::{PhysicsDiagnostics, impl_diagnostic_paths}; 9 | 10 | /// Diagnostics for the physics solver. 11 | #[derive(Resource, Debug, Default, Reflect)] 12 | #[reflect(Resource, Debug)] 13 | pub struct SolverDiagnostics { 14 | /// Time spent preparing constraints. 15 | pub prepare_constraints: Duration, 16 | /// Time spent preparing or clearing velocity increments in [`VelocityIntegrationData`]s. 17 | /// 18 | /// [`VelocityIntegrationData`]: crate::dynamics::integrator::VelocityIntegrationData 19 | pub update_velocity_increments: Duration, 20 | /// Time spent integrating velocities. 21 | pub integrate_velocities: Duration, 22 | /// Time spent warm starting the solver. 23 | pub warm_start: Duration, 24 | /// Time spent solving constraints with bias. 25 | pub solve_constraints: Duration, 26 | /// Time spent integrating positions. 27 | pub integrate_positions: Duration, 28 | /// Time spent relaxing velocities. 29 | pub relax_velocities: Duration, 30 | /// Time spent applying restitution. 31 | pub apply_restitution: Duration, 32 | /// Time spent writing the final results to the bodies. 33 | pub finalize: Duration, 34 | /// Time spent storing impulses for warm starting. 35 | pub store_impulses: Duration, 36 | /// Time spent on swept CCD. 37 | pub swept_ccd: Duration, 38 | /// The number of contact constraints generated. 39 | pub contact_constraint_count: u32, 40 | } 41 | 42 | impl PhysicsDiagnostics for SolverDiagnostics { 43 | fn timer_paths(&self) -> Vec<(&'static DiagnosticPath, Duration)> { 44 | vec![ 45 | (Self::PREPARE_CONSTRAINTS, self.prepare_constraints), 46 | ( 47 | Self::UPDATE_VELOCITY_INCREMENTS, 48 | self.update_velocity_increments, 49 | ), 50 | (Self::INTEGRATE_VELOCITIES, self.integrate_velocities), 51 | (Self::WARM_START, self.warm_start), 52 | (Self::SOLVE_CONSTRAINTS, self.solve_constraints), 53 | (Self::INTEGRATE_POSITIONS, self.integrate_positions), 54 | (Self::RELAX_VELOCITIES, self.relax_velocities), 55 | (Self::APPLY_RESTITUTION, self.apply_restitution), 56 | (Self::FINALIZE, self.finalize), 57 | (Self::STORE_IMPULSES, self.store_impulses), 58 | (Self::SWEPT_CCD, self.swept_ccd), 59 | ] 60 | } 61 | 62 | fn counter_paths(&self) -> Vec<(&'static DiagnosticPath, u32)> { 63 | vec![( 64 | Self::CONTACT_CONSTRAINT_COUNT, 65 | self.contact_constraint_count, 66 | )] 67 | } 68 | } 69 | 70 | impl_diagnostic_paths! { 71 | impl SolverDiagnostics { 72 | PREPARE_CONSTRAINTS: "avian/solver/prepare_constraints", 73 | UPDATE_VELOCITY_INCREMENTS: "avian/solver/update_velocity_increments", 74 | INTEGRATE_VELOCITIES: "avian/solver/integrate_velocities", 75 | WARM_START: "avian/solver/warm_start", 76 | SOLVE_CONSTRAINTS: "avian/solver/solve_constraints", 77 | INTEGRATE_POSITIONS: "avian/solver/integrate_positions", 78 | RELAX_VELOCITIES: "avian/solver/relax_velocities", 79 | APPLY_RESTITUTION: "avian/solver/apply_restitution", 80 | FINALIZE: "avian/solver/finalize", 81 | STORE_IMPULSES: "avian/solver/store_impulses", 82 | SWEPT_CCD: "avian/solver/swept_ccd", 83 | CONTACT_CONSTRAINT_COUNT: "avian/solver/contact_constraint_count", 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/collision/collider/parry/primitives3d.rs: -------------------------------------------------------------------------------- 1 | use bevy_math::primitives::{ 2 | Capsule3d, Cone, Cuboid, Cylinder, InfinitePlane3d, Line3d, Plane3d, Polyline3d, Segment3d, 3 | Sphere, 4 | }; 5 | use parry::shape::SharedShape; 6 | 7 | use crate::{AdjustPrecision, Collider, IntoCollider, Quaternion, Vector}; 8 | 9 | impl IntoCollider for Sphere { 10 | fn collider(&self) -> Collider { 11 | Collider::sphere(self.radius.adjust_precision()) 12 | } 13 | } 14 | 15 | impl IntoCollider for InfinitePlane3d { 16 | fn collider(&self) -> Collider { 17 | let half_size = 10_000.0; 18 | let rotation = Quaternion::from_rotation_arc(Vector::Y, self.normal.adjust_precision()); 19 | let vertices = vec![ 20 | rotation * Vector::new(half_size, 0.0, -half_size), 21 | rotation * Vector::new(-half_size, 0.0, -half_size), 22 | rotation * Vector::new(-half_size, 0.0, half_size), 23 | rotation * Vector::new(half_size, 0.0, half_size), 24 | ]; 25 | 26 | Collider::trimesh(vertices, vec![[0, 1, 2], [1, 2, 0]]) 27 | } 28 | } 29 | 30 | impl IntoCollider for Plane3d { 31 | fn collider(&self) -> Collider { 32 | let half_size = self.half_size.adjust_precision(); 33 | let rotation = Quaternion::from_rotation_arc(Vector::Y, self.normal.adjust_precision()); 34 | let vertices = vec![ 35 | rotation * Vector::new(half_size.x, 0.0, -half_size.y), 36 | rotation * Vector::new(-half_size.x, 0.0, -half_size.y), 37 | rotation * Vector::new(-half_size.x, 0.0, half_size.y), 38 | rotation * Vector::new(half_size.x, 0.0, half_size.y), 39 | ]; 40 | 41 | Collider::trimesh(vertices, vec![[0, 1, 2], [1, 2, 0]]) 42 | } 43 | } 44 | 45 | impl IntoCollider for Line3d { 46 | fn collider(&self) -> Collider { 47 | let vec = self.direction.adjust_precision() * 10_000.0; 48 | Collider::segment(-vec, vec) 49 | } 50 | } 51 | 52 | impl IntoCollider for Segment3d { 53 | fn collider(&self) -> Collider { 54 | let (point1, point2) = (self.point1(), self.point2()); 55 | Collider::segment(point1.adjust_precision(), point2.adjust_precision()) 56 | } 57 | } 58 | 59 | impl IntoCollider for Polyline3d { 60 | fn collider(&self) -> Collider { 61 | let vertices = self.vertices.iter().map(|v| v.adjust_precision()).collect(); 62 | Collider::polyline(vertices, None) 63 | } 64 | } 65 | 66 | impl IntoCollider for Cuboid { 67 | fn collider(&self) -> Collider { 68 | let [hx, hy, hz] = self.half_size.adjust_precision().to_array(); 69 | Collider::from(SharedShape::cuboid(hx, hy, hz)) 70 | } 71 | } 72 | 73 | impl IntoCollider for Cylinder { 74 | fn collider(&self) -> Collider { 75 | Collider::from(SharedShape::cylinder( 76 | self.half_height.adjust_precision(), 77 | self.radius.adjust_precision(), 78 | )) 79 | } 80 | } 81 | 82 | impl IntoCollider for Capsule3d { 83 | fn collider(&self) -> Collider { 84 | Collider::capsule( 85 | self.radius.adjust_precision(), 86 | 2.0 * self.half_length.adjust_precision(), 87 | ) 88 | } 89 | } 90 | 91 | impl IntoCollider for Cone { 92 | fn collider(&self) -> Collider { 93 | Collider::cone( 94 | self.radius.adjust_precision(), 95 | self.height.adjust_precision(), 96 | ) 97 | } 98 | } 99 | 100 | // TODO: ConicalFrustum 101 | // TODO: Torus 102 | -------------------------------------------------------------------------------- /src/dynamics/rigid_body/mass_properties/components/collider.rs: -------------------------------------------------------------------------------- 1 | use super::super::MassProperties; 2 | use crate::prelude::*; 3 | use bevy::prelude::*; 4 | use derive_more::derive::From; 5 | 6 | /// The density of a [`Collider`], used for computing [`ColliderMassProperties`]. 7 | /// Defaults to `1.0`. 8 | /// 9 | /// If the entity has the [`Mass`] component, it will be used instead of the collider's mass. 10 | /// 11 | /// # Example 12 | /// 13 | /// ``` 14 | #[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")] 15 | #[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")] 16 | /// use bevy::prelude::*; 17 | /// 18 | /// // Spawn a body with a collider that has a density of `2.5`. 19 | /// fn setup(mut commands: Commands) { 20 | /// commands.spawn(( 21 | /// RigidBody::Dynamic, 22 | #[cfg_attr(feature = "2d", doc = " Collider::circle(0.5),")] 23 | #[cfg_attr(feature = "3d", doc = " Collider::sphere(0.5),")] 24 | /// ColliderDensity(2.5), 25 | /// )); 26 | /// } 27 | /// ``` 28 | #[derive(Reflect, Clone, Copy, Component, Debug, Deref, DerefMut, PartialEq, PartialOrd, From)] 29 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 30 | #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] 31 | #[reflect(Debug, Component, PartialEq)] 32 | pub struct ColliderDensity(pub f32); 33 | 34 | impl Default for ColliderDensity { 35 | fn default() -> Self { 36 | Self(1.0) 37 | } 38 | } 39 | 40 | impl ColliderDensity { 41 | /// A density of `0.0`, resulting in a collider with no mass. 42 | pub const ZERO: Self = Self(0.0); 43 | } 44 | 45 | /// A read-only component for the mass properties of a [`Collider`]. 46 | /// Computed automatically from the collider's shape and [`ColliderDensity`]. 47 | /// 48 | /// If the entity has the [`Mass`], [`AngularInertia`], or [`CenterOfMass`] components, 49 | /// they will be used instead when updating the associated rigid body's [`ComputedMass`], 50 | /// [`ComputedAngularInertia`], and [`ComputedCenterOfMass`] components respectively. 51 | /// 52 | /// # Example 53 | /// 54 | /// ```no_run 55 | #[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")] 56 | #[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")] 57 | /// use bevy::prelude::*; 58 | /// 59 | /// fn setup(mut commands: Commands) { 60 | #[cfg_attr( 61 | feature = "2d", 62 | doc = " commands.spawn((RigidBody::Dynamic, Collider::circle(0.5)));" 63 | )] 64 | #[cfg_attr( 65 | feature = "3d", 66 | doc = " commands.spawn((RigidBody::Dynamic, Collider::sphere(0.5)));" 67 | )] 68 | /// } 69 | /// 70 | /// fn print_collider_masses(query: Query<&ColliderMassProperties>) { 71 | /// for mass_properties in &query { 72 | /// println!("{}", mass_properties.mass); 73 | /// } 74 | /// } 75 | /// ``` 76 | #[derive(Reflect, Clone, Copy, Component, Debug, Default, Deref, PartialEq, From)] 77 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 78 | #[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))] 79 | #[reflect(Debug, Component, PartialEq)] 80 | pub struct ColliderMassProperties(MassProperties); 81 | 82 | impl ColliderMassProperties { 83 | /// The collider has no mass. 84 | pub const ZERO: Self = Self(MassProperties::ZERO); 85 | 86 | /// Computes mass properties from a given shape and density. 87 | /// 88 | /// Because [`ColliderMassProperties`] is intended to be read-only, adding this as a component manually 89 | /// has no effect. The mass properties will be recomputed using the [`ColliderDensity`]. 90 | #[inline] 91 | pub fn from_shape(shape: &T, density: f32) -> Self { 92 | Self(shape.mass_properties(density)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/diagnostics/entity_counters.rs: -------------------------------------------------------------------------------- 1 | use bevy::{diagnostic::DiagnosticPath, prelude::*}; 2 | 3 | use crate::{ColliderMarker, PhysicsSchedule, PhysicsStepSystems, RigidBody, dynamics::joints::*}; 4 | 5 | use super::{AppDiagnosticsExt, PhysicsDiagnostics, impl_diagnostic_paths}; 6 | 7 | /// A plugin that adds diagnostics for physics entity counts. 8 | pub struct PhysicsEntityDiagnosticsPlugin; 9 | 10 | impl Plugin for PhysicsEntityDiagnosticsPlugin { 11 | fn build(&self, app: &mut App) { 12 | // Register diagnostics for physics entity counts. 13 | // NOTE: This should be done after the `PhysicsDiagnosticsPlugin` is added. 14 | app.register_physics_diagnostics::(); 15 | 16 | app.add_systems( 17 | PhysicsSchedule, 18 | diagnostic_entity_counts.in_set(PhysicsStepSystems::First), 19 | ); 20 | } 21 | } 22 | 23 | /// Diagnostics for physics entity counts. 24 | #[derive(Resource, Debug, Default, Reflect)] 25 | #[reflect(Resource, Debug)] 26 | pub struct PhysicsEntityDiagnostics { 27 | /// The number of dynamic bodies. 28 | pub dynamic_body_count: u32, 29 | /// The number of kinematic bodies. 30 | pub kinematic_body_count: u32, 31 | /// The number of static bodies. 32 | pub static_body_count: u32, 33 | /// The number of colliders. 34 | pub collider_count: u32, 35 | /// The number of joint. 36 | pub joint_count: u32, 37 | } 38 | 39 | impl PhysicsDiagnostics for PhysicsEntityDiagnostics { 40 | fn counter_paths(&self) -> Vec<(&'static DiagnosticPath, u32)> { 41 | vec![ 42 | (Self::DYNAMIC_BODY_COUNT, self.dynamic_body_count), 43 | (Self::KINEMATIC_BODY_COUNT, self.kinematic_body_count), 44 | (Self::STATIC_BODY_COUNT, self.static_body_count), 45 | (Self::COLLIDER_COUNT, self.collider_count), 46 | (Self::JOINT_COUNT, self.joint_count), 47 | ] 48 | } 49 | } 50 | 51 | impl_diagnostic_paths! { 52 | impl PhysicsEntityDiagnostics { 53 | DYNAMIC_BODY_COUNT: "avian/entity_count/dynamic_bodies", 54 | KINEMATIC_BODY_COUNT: "avian/entity_count/kinematic_bodies", 55 | STATIC_BODY_COUNT: "avian/entity_count/static_bodies", 56 | COLLIDER_COUNT: "avian/entity_count/colliders", 57 | JOINT_COUNT: "avian/entity_count/joints", 58 | } 59 | } 60 | 61 | // TODO: This is pretty inefficient. 62 | fn diagnostic_entity_counts( 63 | rigid_bodies_query: Query<&RigidBody>, 64 | colliders_query: Query<&ColliderMarker>, 65 | fixed_joint_query: Query<&FixedJoint>, 66 | prismatic_joint_query: Query<&PrismaticJoint>, 67 | distance_joint_query: Query<&DistanceJoint>, 68 | revolute_joint_query: Query<&RevoluteJoint>, 69 | #[cfg(feature = "3d")] spherical_joint_query: Query<&SphericalJoint>, 70 | mut diagnostics: ResMut, 71 | ) { 72 | diagnostics.dynamic_body_count = rigid_bodies_query 73 | .iter() 74 | .filter(|rb| rb.is_dynamic()) 75 | .count() as u32; 76 | diagnostics.kinematic_body_count = rigid_bodies_query 77 | .iter() 78 | .filter(|rb| rb.is_kinematic()) 79 | .count() as u32; 80 | diagnostics.static_body_count = rigid_bodies_query 81 | .iter() 82 | .filter(|rb| rb.is_static()) 83 | .count() as u32; 84 | diagnostics.collider_count = colliders_query.iter().count() as u32; 85 | diagnostics.joint_count = fixed_joint_query.iter().count() as u32 86 | + prismatic_joint_query.iter().count() as u32 87 | + distance_joint_query.iter().count() as u32 88 | + revolute_joint_query.iter().count() as u32; 89 | #[cfg(feature = "3d")] 90 | { 91 | diagnostics.joint_count += spherical_joint_query.iter().count() as u32; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/avian2d/examples/tumbler.rs: -------------------------------------------------------------------------------- 1 | //! A spinning box with a large number of small boxes tumbling inside of it. 2 | //! This stress tests a dynamic scene where contacts are constantly being 3 | //! created and destroyed. 4 | //! 5 | //! Based on the tumbler sample from the Box2D physics engine. 6 | //! The walls are thinner to also test tunneling prevention. 7 | 8 | #![allow(clippy::unnecessary_cast)] 9 | 10 | use avian2d::{math::*, prelude::*}; 11 | use bevy::prelude::*; 12 | use examples_common_2d::ExampleCommonPlugin; 13 | 14 | fn main() { 15 | App::new() 16 | .add_plugins(( 17 | DefaultPlugins, 18 | ExampleCommonPlugin, 19 | PhysicsPlugins::default().with_length_unit(0.25), 20 | )) 21 | .add_systems(Startup, (spawn_tumbler, setup_camera)) 22 | .run(); 23 | } 24 | 25 | fn spawn_tumbler( 26 | mut commands: Commands, 27 | mut materials: ResMut>, 28 | mut meshes: ResMut>, 29 | ) { 30 | let horizontal_wall = Rectangle::new(20.05, 0.05); 31 | let vertical_wall = Rectangle::new(0.05, 20.05); 32 | 33 | let horizontal_wall_mesh = meshes.add(horizontal_wall); 34 | let vertical_wall_mesh = meshes.add(vertical_wall); 35 | 36 | let wall_material = materials.add(Color::srgb(0.7, 0.7, 0.8)); 37 | 38 | commands.spawn(( 39 | RigidBody::Kinematic, 40 | AngularVelocity((25.0 as Scalar).to_radians()), 41 | Transform::default(), 42 | Visibility::default(), 43 | children![ 44 | ( 45 | Collider::from(horizontal_wall), 46 | Mesh2d(horizontal_wall_mesh.clone()), 47 | MeshMaterial2d(wall_material.clone()), 48 | Transform::from_xyz(0.0, 10.0, 0.0), 49 | ), 50 | ( 51 | Collider::from(horizontal_wall), 52 | Mesh2d(horizontal_wall_mesh.clone()), 53 | MeshMaterial2d(wall_material.clone()), 54 | Transform::from_xyz(0.0, -10.0, 0.0), 55 | ), 56 | ( 57 | Collider::from(vertical_wall), 58 | Mesh2d(vertical_wall_mesh.clone()), 59 | MeshMaterial2d(wall_material.clone()), 60 | Transform::from_xyz(10.0, 0.0, 0.0), 61 | ), 62 | ( 63 | Collider::from(vertical_wall), 64 | Mesh2d(vertical_wall_mesh), 65 | MeshMaterial2d(wall_material), 66 | Transform::from_xyz(-10.0, 0.0, 0.0), 67 | ), 68 | ], 69 | )); 70 | 71 | let grid_count = 45; 72 | 73 | let rectangle = Rectangle::new(0.25, 0.25); 74 | let rectangle_collider = Collider::from(rectangle); 75 | let rectangle_mesh = meshes.add(rectangle); 76 | let rectangle_material = materials.add(Color::srgb(0.2, 0.7, 0.9)); 77 | 78 | let mut y = -0.2 * grid_count as f32; 79 | for _ in 0..grid_count { 80 | let mut x = -0.2 * grid_count as f32; 81 | for _ in 0..grid_count { 82 | commands.spawn(( 83 | RigidBody::Dynamic, 84 | rectangle_collider.clone(), 85 | Mesh2d(rectangle_mesh.clone()), 86 | MeshMaterial2d(rectangle_material.clone()), 87 | Transform::from_xyz(x, y, 0.0), 88 | )); 89 | x += 0.4; 90 | } 91 | y += 0.4; 92 | } 93 | } 94 | 95 | fn setup_camera(mut commands: Commands) { 96 | commands.spawn(( 97 | Camera2d, 98 | Projection::Orthographic(OrthographicProjection { 99 | scale: 0.04, 100 | ..OrthographicProjection::default_2d() 101 | }), 102 | )); 103 | } 104 | -------------------------------------------------------------------------------- /crates/avian3d/examples/cubes.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unnecessary_cast)] 2 | 3 | use avian3d::{math::*, prelude::*}; 4 | use bevy::prelude::*; 5 | use examples_common_3d::ExampleCommonPlugin; 6 | 7 | fn main() { 8 | App::new() 9 | .add_plugins(( 10 | DefaultPlugins, 11 | ExampleCommonPlugin, 12 | PhysicsPlugins::default(), 13 | )) 14 | .insert_resource(ClearColor(Color::srgb(0.05, 0.05, 0.1))) 15 | .add_systems(Startup, setup) 16 | .add_systems(Update, movement) 17 | .run(); 18 | } 19 | 20 | /// The acceleration used for movement. 21 | #[derive(Component)] 22 | struct MovementAcceleration(Scalar); 23 | 24 | fn setup( 25 | mut commands: Commands, 26 | mut materials: ResMut>, 27 | mut meshes: ResMut>, 28 | ) { 29 | let cube_mesh = meshes.add(Cuboid::default()); 30 | 31 | // Ground 32 | commands.spawn(( 33 | Mesh3d(cube_mesh.clone()), 34 | MeshMaterial3d(materials.add(Color::srgb(0.7, 0.7, 0.8))), 35 | Transform::from_xyz(0.0, -2.0, 0.0).with_scale(Vec3::new(100.0, 1.0, 100.0)), 36 | RigidBody::Static, 37 | Collider::cuboid(1.0, 1.0, 1.0), 38 | )); 39 | 40 | let cube_size = 2.0; 41 | 42 | // Spawn cube stacks 43 | for x in -2..2 { 44 | for y in -2..2 { 45 | for z in -2..2 { 46 | let position = Vec3::new(x as f32, y as f32 + 3.0, z as f32) * (cube_size + 0.05); 47 | commands.spawn(( 48 | Mesh3d(cube_mesh.clone()), 49 | MeshMaterial3d(materials.add(Color::srgb(0.2, 0.7, 0.9))), 50 | Transform::from_translation(position).with_scale(Vec3::splat(cube_size as f32)), 51 | RigidBody::Dynamic, 52 | Collider::cuboid(1.0, 1.0, 1.0), 53 | MovementAcceleration(10.0), 54 | )); 55 | } 56 | } 57 | } 58 | 59 | // Directional light 60 | commands.spawn(( 61 | DirectionalLight { 62 | illuminance: 5000.0, 63 | shadows_enabled: true, 64 | ..default() 65 | }, 66 | Transform::default().looking_at(Vec3::new(-1.0, -2.5, -1.5), Vec3::Y), 67 | )); 68 | 69 | // Camera 70 | commands.spawn(( 71 | Camera3d::default(), 72 | Transform::from_translation(Vec3::new(0.0, 12.0, 40.0)).looking_at(Vec3::Y * 5.0, Vec3::Y), 73 | )); 74 | } 75 | 76 | fn movement( 77 | time: Res