#![allow(clippy::unnecessary_cast)]
use avian2d::{collision::contact_types::PackedFeatureId, math::*, prelude::*};
use bevy::prelude::*;
use examples_common_2d::ExampleCommonPlugin;
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
ExampleCommonPlugin,
PhysicsPlugins::default().with_length_unit(10.0),
ColliderBackendPlugin::<CircleCollider>::default(),
NarrowPhasePlugin::<CircleCollider>::default(),
))
.insert_resource(ClearColor(Color::srgb(0.01, 0.01, 0.025)))
.insert_resource(Gravity::ZERO)
.add_systems(Startup, setup)
.add_systems(
FixedUpdate,
(center_gravity, rotate).in_set(PhysicsStepSystems::First),
)
.run();
}
#[derive(Component)]
struct CircleCollider {
radius: Scalar,
unscaled_radius: Scalar,
scale: Scalar,
}
impl CircleCollider {
fn new(radius: Scalar) -> Self {
Self {
radius,
unscaled_radius: radius,
scale: 1.0,
}
}
}
impl AnyCollider for CircleCollider {
type Context = ();
fn aabb_with_context(
&self,
position: Vector,
_: impl Into<Rotation>,
_: AabbContext<Self::Context>,
) -> ColliderAabb {
ColliderAabb::new(position, Vector::splat(self.radius))
}
fn contact_manifolds_with_context(
&self,
other: &Self,
position1: Vector,
rotation1: impl Into<Rotation>,
position2: Vector,
_rotation2: impl Into<Rotation>,
prediction_distance: Scalar,
manifolds: &mut Vec<ContactManifold>,
_: ContactManifoldContext<Self::Context>,
) {
manifolds.clear();
let rotation1: Rotation = rotation1.into();
let inv_rotation1 = rotation1.inverse();
let delta_pos = inv_rotation1 * (position2 - position1);
let distance_squared = delta_pos.length_squared();
let sum_radius = self.radius + other.radius;
if distance_squared < (sum_radius + prediction_distance).powi(2) {
let local_normal1 = if distance_squared != 0.0 {
delta_pos.normalize_or_zero()
} else {
Vector::X
};
let local_point1 = local_normal1 * self.radius;
let normal = rotation1 * local_normal1;
let separation = distance_squared.sqrt() - sum_radius;
let point1 = rotation1 * local_point1;
let anchor1 = point1 + normal * separation * 0.5;
let anchor2 = anchor1 + (position1 - position2);
let world_point = position1 + anchor1;
let point = ContactPoint::new(anchor1, anchor2, world_point, -separation)
.with_feature_ids(PackedFeatureId::face(0), PackedFeatureId::face(0));
manifolds.push(ContactManifold::new([point], rotation1 * local_normal1));
}
}
}
impl ComputeMassProperties2d for CircleCollider {
fn mass(&self, density: f32) -> f32 {
let volume = core::f32::consts::PI * self.radius.powi(2) as f32;
density * volume
}
fn unit_angular_inertia(&self) -> f32 {
self.radius.powi(2) as f32 / 2.0
}
fn center_of_mass(&self) -> Vec2 {
Vec2::ZERO
}
}
impl ScalableCollider for CircleCollider {
fn scale(&self) -> Vector {
Vector::splat(self.scale)
}
fn set_scale(&mut self, scale: Vector, _detail: u32) {
self.scale = scale.max_element();
self.radius = self.unscaled_radius * scale.max_element();
}
}
#[derive(Component)]
struct CenterBody;
fn setup(
mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
commands.spawn(Camera2d);
let center_radius = 200.0;
let particle_radius = 5.0;
let red = materials.add(Color::srgb(0.9, 0.3, 0.3));
let blue = materials.add(Color::srgb(0.1, 0.6, 1.0));
let particle_mesh = meshes.add(Circle::new(particle_radius));
commands
.spawn((
Mesh2d(meshes.add(Circle::new(center_radius))),
MeshMaterial2d(materials.add(Color::srgb(0.7, 0.7, 0.8)).clone()),
RigidBody::Kinematic,
CircleCollider::new(center_radius.adjust_precision()),
CenterBody,
))
.with_children(|c| {
let count = 8;
let angle_step = core::f32::consts::TAU / count as f32;
for i in 0..count {
let pos = Quat::from_rotation_z(i as f32 * angle_step) * Vec3::Y * center_radius;
c.spawn((
Mesh2d(particle_mesh.clone()),
MeshMaterial2d(red.clone()),
Transform::from_translation(pos).with_scale(Vec3::ONE * 5.0),
CircleCollider::new(particle_radius.adjust_precision()),
));
}
});
let x_count = 10;
let y_count = 30;
for x in -x_count / 2..x_count / 2 {
for y in -y_count / 2..y_count / 2 {
commands.spawn((
Mesh2d(particle_mesh.clone()),
MeshMaterial2d(blue.clone()),
Transform::from_xyz(
x as f32 * 3.0 * particle_radius - 350.0,
y as f32 * 3.0 * particle_radius,
0.0,
),
RigidBody::Dynamic,
CircleCollider::new(particle_radius.adjust_precision()),
LinearDamping(0.4),
));
}
}
}
fn center_gravity(
mut particles: Query<(&Transform, &mut LinearVelocity), Without<CenterBody>>,
time: Res<Time>,
) {
let delta_seconds = time.delta_secs_f64().adjust_precision();
for (transform, mut lin_vel) in &mut particles {
let pos_delta = transform.translation.truncate().adjust_precision();
let dir = -pos_delta.normalize_or_zero();
lin_vel.0 += 800.0 * delta_seconds * dir;
}
}
fn rotate(mut query: Query<&mut AngularVelocity, With<CenterBody>>, time: Res<Time>) {
let sin = 3.0 * time.elapsed_secs_f64().adjust_precision().sin();
for mut ang_vel in &mut query {
ang_vel.0 = sin;
}
}