Compare commits
1 commit
main
...
bevy-rapie
Author | SHA1 | Date | |
---|---|---|---|
5e5aec6c19 |
6 changed files with 2059 additions and 1102 deletions
2838
Cargo.lock
generated
2838
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -6,9 +6,12 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nalgebra = { version = "0.32.3", features = ["glam017"] }
|
bevy = { version = "0.12.1" }
|
||||||
nannou = "0.19.0"
|
bevy_dylib = "0.12.1"
|
||||||
rapier2d = "0.18.0"
|
bevy_rapier2d = "*"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
|
@ -1,56 +0,0 @@
|
||||||
use nannou::prelude::*;
|
|
||||||
use rapier2d::geometry::{Collider, ColliderSet, TypedShape};
|
|
||||||
|
|
||||||
pub(crate) trait Drawable {
|
|
||||||
fn draw(&self, draw: &Draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drawable for Vec<T>
|
|
||||||
where
|
|
||||||
T: Drawable,
|
|
||||||
{
|
|
||||||
fn draw(&self, draw: &Draw) {
|
|
||||||
self.iter().for_each(|s| s.draw(draw))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawable for Collider {
|
|
||||||
fn draw(&self, draw: &Draw) {
|
|
||||||
match self.shape().as_typed_shape() {
|
|
||||||
TypedShape::Ball(ball) => {
|
|
||||||
draw.ellipse()
|
|
||||||
.radius(ball.radius)
|
|
||||||
.xy((*self.translation()).into());
|
|
||||||
}
|
|
||||||
TypedShape::Cuboid(cube) => {
|
|
||||||
draw.rect()
|
|
||||||
.w(cube.half_extents.x * 2.0)
|
|
||||||
.h(cube.half_extents.y * 2.0)
|
|
||||||
.xy((*self.translation()).into());
|
|
||||||
}
|
|
||||||
_ => eprintln!("Attempted to draw a shape with no draw impl"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawable for ColliderSet {
|
|
||||||
fn draw(&self, draw: &Draw) {
|
|
||||||
self.iter().for_each(|(_, collider)| draw.draw(collider))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait DrawShape<T>
|
|
||||||
where
|
|
||||||
T: Drawable,
|
|
||||||
{
|
|
||||||
fn draw(&self, drawable: &T);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DrawShape<T> for Draw
|
|
||||||
where
|
|
||||||
T: Drawable,
|
|
||||||
{
|
|
||||||
fn draw(&self, drawable: &T) {
|
|
||||||
drawable.draw(self);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{
|
|
||||||
CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
|
|
||||||
RigidBodySet,
|
|
||||||
},
|
|
||||||
geometry::{BroadPhase, ColliderSet, NarrowPhase},
|
|
||||||
math::Vector,
|
|
||||||
pipeline::{PhysicsPipeline, QueryPipeline},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PhysicsEngine {
|
|
||||||
pub state: PhysicsState,
|
|
||||||
pub pipeline: PhysicsPipeline,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PhysicsState {
|
|
||||||
pub islands: IslandManager,
|
|
||||||
pub broad_phase: BroadPhase,
|
|
||||||
pub narrow_phase: NarrowPhase,
|
|
||||||
pub bodies: RigidBodySet,
|
|
||||||
pub colliders: ColliderSet,
|
|
||||||
pub joints: ImpulseJointSet,
|
|
||||||
pub multibody_joints: MultibodyJointSet,
|
|
||||||
pub ccd_solver: CCDSolver,
|
|
||||||
pub query_pipeline: QueryPipeline,
|
|
||||||
pub integration_parameters: IntegrationParameters,
|
|
||||||
pub gravity: Vector<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysicsEngine {
|
|
||||||
pub fn step(&mut self) {
|
|
||||||
self.pipeline.step(
|
|
||||||
&self.state.gravity,
|
|
||||||
&self.state.integration_parameters,
|
|
||||||
&mut self.state.islands,
|
|
||||||
&mut self.state.broad_phase,
|
|
||||||
&mut self.state.narrow_phase,
|
|
||||||
&mut self.state.bodies,
|
|
||||||
&mut self.state.colliders,
|
|
||||||
&mut self.state.joints,
|
|
||||||
&mut self.state.multibody_joints,
|
|
||||||
&mut self.state.ccd_solver,
|
|
||||||
Some(&mut self.state.query_pipeline),
|
|
||||||
&(),
|
|
||||||
&(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
206
src/main.rs
206
src/main.rs
|
@ -1,168 +1,44 @@
|
||||||
mod drawable;
|
use bevy::prelude::*;
|
||||||
mod engine;
|
use bevy_rapier2d::prelude::*;
|
||||||
|
|
||||||
use drawable::DrawShape;
|
|
||||||
use engine::{PhysicsEngine, PhysicsState};
|
|
||||||
|
|
||||||
use nalgebra::vector;
|
|
||||||
use nannou::prelude::*;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodySet},
|
|
||||||
geometry::{ColliderBuilder, ColliderSet},
|
|
||||||
};
|
|
||||||
|
|
||||||
const WINDOW_WIDTH: u32 = 512;
|
|
||||||
const WINDOW_HEIGHT: u32 = WINDOW_WIDTH;
|
|
||||||
const PARTICLE_COUNT: u32 = 200;
|
|
||||||
const PARTICLE_SIZE: f32 = 5.;
|
|
||||||
|
|
||||||
struct Model {
|
|
||||||
// Store the window ID so we can refer to this specific window later if needed.
|
|
||||||
window: WindowId,
|
|
||||||
// particles: Vec<Particle>,
|
|
||||||
engine: PhysicsEngine,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_vec_in_rect(boundary: &Rect) -> nalgebra::Vector2<f32> {
|
|
||||||
let x = random_range(-100., 100.);
|
|
||||||
let y = random_range(-150., boundary.top());
|
|
||||||
|
|
||||||
vector![x, y]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_particles(boundary: &Rect, colliders: &mut ColliderSet, bodies: &mut RigidBodySet) {
|
|
||||||
// Keeping track of already placed balls to avoid overlap
|
|
||||||
// Need to look into a way to do this with rapier directly
|
|
||||||
let mut positions: Vec<nalgebra::Vector2<f32>> = Vec::new();
|
|
||||||
|
|
||||||
for _ in 0..PARTICLE_COUNT {
|
|
||||||
let mut xy = random_vec_in_rect(boundary);
|
|
||||||
while !positions
|
|
||||||
.iter()
|
|
||||||
.all(|pos| pos.metric_distance(&xy) > PARTICLE_SIZE * 2.)
|
|
||||||
{
|
|
||||||
xy = random_vec_in_rect(boundary)
|
|
||||||
}
|
|
||||||
positions.push(xy);
|
|
||||||
|
|
||||||
/* Create the bouncing ball. */
|
|
||||||
let rigid_body = RigidBodyBuilder::dynamic().translation(xy).build();
|
|
||||||
let collider = ColliderBuilder::ball(PARTICLE_SIZE)
|
|
||||||
.restitution(0.1)
|
|
||||||
.build();
|
|
||||||
let ball_body_handle = bodies.insert(rigid_body);
|
|
||||||
colliders.insert_with_parent(collider, ball_body_handle, bodies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn model(app: &App) -> Model {
|
|
||||||
// One thing that tripped me up when begginning nannou was realizing
|
|
||||||
// that view and event methods are per window, and update functions are
|
|
||||||
// per app
|
|
||||||
|
|
||||||
// Create a new window! Store the ID so we can refer to it later.
|
|
||||||
let window = app
|
|
||||||
.new_window()
|
|
||||||
.size(WINDOW_WIDTH, WINDOW_HEIGHT)
|
|
||||||
.title("nannou")
|
|
||||||
.view(view) // The function that will be called for presenting graphics to a frame.
|
|
||||||
.event(event) // The function that will be called when the window receives events.
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut engine = PhysicsEngine {
|
|
||||||
state: PhysicsState {
|
|
||||||
gravity: vector![0., -9.81 * 10.],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let boundary = app.window(window).unwrap().rect();
|
|
||||||
|
|
||||||
/* Create the ground. */
|
|
||||||
let collider = ColliderBuilder::cuboid(boundary.w(), 4.)
|
|
||||||
.translation(vector![0., boundary.bottom()])
|
|
||||||
.build();
|
|
||||||
engine.state.colliders.insert(collider);
|
|
||||||
|
|
||||||
/* Create the walls. */
|
|
||||||
let collider = ColliderBuilder::cuboid(4., boundary.h())
|
|
||||||
.translation(vector![boundary.left(), 0.])
|
|
||||||
.build();
|
|
||||||
engine.state.colliders.insert(collider);
|
|
||||||
let collider = ColliderBuilder::cuboid(4., boundary.h())
|
|
||||||
.translation(vector![boundary.right(), 0.])
|
|
||||||
.build();
|
|
||||||
engine.state.colliders.insert(collider);
|
|
||||||
|
|
||||||
/* Create the ceiling. */
|
|
||||||
let collider = ColliderBuilder::cuboid(boundary.w(), 4.)
|
|
||||||
.translation(vector![0., boundary.top()])
|
|
||||||
.build();
|
|
||||||
engine.state.colliders.insert(collider);
|
|
||||||
|
|
||||||
fill_particles(
|
|
||||||
&boundary,
|
|
||||||
&mut engine.state.colliders,
|
|
||||||
&mut engine.state.bodies,
|
|
||||||
);
|
|
||||||
|
|
||||||
Model { window, engine }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle events related to the window and update the model if necessary
|
|
||||||
fn event(app: &App, model: &mut Model, event: WindowEvent) {
|
|
||||||
if let KeyReleased(Key::Escape) = event {
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Resized(_) = event {
|
|
||||||
// Rust borrowing rules means I need to first gather a list of handles,
|
|
||||||
// then delete them all
|
|
||||||
let handles: Vec<RigidBodyHandle> = model
|
|
||||||
.engine
|
|
||||||
.state
|
|
||||||
.bodies
|
|
||||||
.iter()
|
|
||||||
.map(|(handle, _)| handle)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for handle in handles {
|
|
||||||
model.engine.state.bodies.remove(
|
|
||||||
handle,
|
|
||||||
&mut model.engine.state.islands,
|
|
||||||
&mut model.engine.state.colliders,
|
|
||||||
&mut model.engine.state.joints,
|
|
||||||
&mut model.engine.state.multibody_joints,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fill_particles(
|
|
||||||
&app.window(model.window).unwrap().rect(),
|
|
||||||
&mut model.engine.state.colliders,
|
|
||||||
&mut model.engine.state.bodies,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the state of your application here. By default, this gets called right before `view`.
|
|
||||||
fn update(_app: &App, model: &mut Model, _update: Update) {
|
|
||||||
model.engine.step();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the state of your `Model` into the given `Frame` here.
|
|
||||||
fn view(app: &App, model: &Model, frame: Frame) {
|
|
||||||
let canvas = app.draw();
|
|
||||||
canvas.background().color(CORNFLOWERBLUE);
|
|
||||||
|
|
||||||
canvas.draw(&model.engine.state.colliders);
|
|
||||||
|
|
||||||
// I don't think there is even a fail condition in this function, but it returns a result?
|
|
||||||
canvas.to_frame(app, &frame).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
nannou::app(model).update(update).run();
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(100.0))
|
||||||
|
.add_plugins(RapierDebugRenderPlugin::default())
|
||||||
|
.add_systems(Startup, setup_graphics)
|
||||||
|
.add_systems(Startup, setup_physics)
|
||||||
|
.add_systems(Update, print_ball_altitude)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_graphics(mut commands: Commands) {
|
||||||
|
// Add a camera so we can see the debug-render.
|
||||||
|
commands.spawn(Camera2dBundle::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_physics(mut commands: Commands) {
|
||||||
|
/* Create the ground. */
|
||||||
|
commands
|
||||||
|
.spawn(Collider::cuboid(500.0, 50.0))
|
||||||
|
.insert(TransformBundle::from(Transform::from_xyz(0.0, -100.0, 0.0)));
|
||||||
|
|
||||||
|
/* Create the bouncing ball. */
|
||||||
|
commands
|
||||||
|
.spawn(RigidBody::Dynamic)
|
||||||
|
.insert(Collider::ball(10.0))
|
||||||
|
.insert(Restitution::coefficient(2.0))
|
||||||
|
.insert(TransformBundle::from(Transform::from_xyz(0.0, 400.0, 0.0)));
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn(RigidBody::Dynamic)
|
||||||
|
.insert(Collider::ball(10.0))
|
||||||
|
.insert(Restitution::coefficient(2.0))
|
||||||
|
.insert(TransformBundle::from(Transform::from_xyz(0.0, 600.0, 0.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_ball_altitude(positions: Query<&Transform, With<RigidBody>>) {
|
||||||
|
for transform in positions.iter() {
|
||||||
|
println!("Ball altitude: {}", transform.translation.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue