Commit c0225866 authored by Luke Jones's avatar Luke Jones

rustfmt

parent 0fcdd4c7
Pipeline #62089918 passed with stage
in 2 minutes and 31 seconds
[submodule "tiny_ecs"]
path = tiny_ecs
url = https://gitlab.com/ljcode/tiny_ecs.git
[submodule "vec2d"]
path = vec2d
url = https://gitlab.com/ljcode/vec2d.git
[submodule "cyclone-rs"]
path = cyclone-rs
url = https://gitlab.com/ljcode/cyclone-rs.git
max_width = 100
max_width =80
comment_width = 80
wrap_comments = false
......
[workspace]
members = ["./game", "./tiny_ecs", "./cyclone", "./vec2d"]
[profile.release]
lto = true
opt-level = 3
debuginfo = 0
panic = "abort"
[package]
name = "rust_game"
version = "0.1.1"
license = "MPL-2.0"
readme = "README.md"
authors = ["Luke Jones <luke.nukem.jones@gmail.com>"]
edition = "2018"
[dependencies]
rand = "*"
num-traits = "0.2"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
tiny_ecs = { path = "tiny_ecs" }
[dependencies.sdl2]
version = "0.32"
default-features = false
features = ["image"]
[[bin]]
name = "game"
path = "src/main.rs"
[features]
default = []
lag =[]
Subproject commit 1eba4546b3e159dfa9ffb182428a64a8fb3898db
[package]
name = "cyclone"
version = "0.1.0"
authors = ["Luke Jones <jones_ld@protonmail.com"]
edition = "2018"
[dependencies]
vec2d = { path = "../vec2d" }
tiny_ecs = { path = "../tiny_ecs" }
[dev-dependencies]
vec2d = { path = "../vec2d" }
[dev-dependencies.sdl2]
version = "0.32"
default-features = false
features = ["gfx"]
[[example]]
name = "falling"
crate-type = ["bin"]
extern crate sdl2;
extern crate cyclone;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::gfx::primitives::{DrawRenderer, ToColor};
use cyclone::world::World;
use cyclone::particle::Particle;
use cyclone::contacts::GroundContact;
use cyclone::forces::{Drag, Gravity, AnchoredSpring, AnchoredBungee, Buoyancy};
use vec2d::Vec2d;
use std::collections::HashMap;
fn main() -> Result<(), String> {
let sdl_context = sdl2::init()?;
let video_subsystem = sdl_context.video()?;
let window = video_subsystem.window("SDL2", 800, 600)
.position_centered().build().map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas()
.accelerated().build().map_err(|e| e.to_string())?;
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0,0,0,255));
let mut timer = sdl_context.timer()?;
let mut event_pump = sdl_context.event_pump()?;
let mut particle_world = World::new(1000, None);
let mut particle_container = HashMap::new();
for i in 1..8 {
let mut particle = Particle::new(2.0 + (i as f32), 0.8);
particle.position.x = 0.0 + i as f32 * 100.0;
particle.position.y = 500.0;
if i != 7 {
particle_container.insert(i, particle);
} else {
// Buoyancy particle 200 mass
particle.inv_mass = 0.00483;
particle.mass = 207.0;
particle_container.insert(i, particle);
}
particle_world.add_force_generator(Box::new(
Gravity::new(i, -9.81)));
//particle_world.add_force_generator(Box::new(
// Drag::new(i, 0.01, 0.03)));
};
// anchor one particle
particle_world.add_force_generator(Box::new(
AnchoredSpring::new(1, Vec2d::new(400.0, 500.0), 0.1, 10.0)));
particle_world.add_force_generator(Box::new(
AnchoredBungee::new(4, 1, 0.99, 120.0)));
particle_world.add_force_generator(Box::new(
Buoyancy::new(7, 1.0, 3.0, 400.0, 1000.0)));
particle_world.add_contact_generator(Box::new(
GroundContact::new()));
let fps: f32 = 1000.0 / 60.0; //FPS 60
let mut last_tick = timer.ticks();
let mut lag: f32 = 0.0;
let mut running = true;
let red = (255, 100, 100, 255);
let grey = (100, 100, 100, 255);
'running: loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit {..} |
Event::KeyDown {
keycode: Some(Keycode::Escape), ..
} => {
running = false;
},
_ => {}
}
}
if !running {
break 'running;
}
let this_tick = timer.ticks();
lag += (this_tick - last_tick) as f32;
last_tick = this_tick;
while lag >= fps {
// clear background to black
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0,0,0,255));
canvas.clear();
particle_world.start_frame(&mut particle_container);
particle_world.run_physics(&mut particle_container, lag * 0.01);
// centered circle
canvas.filled_circle(400, 100, 10, red.as_rgba())?;
// draw particles
for (k, v) in &particle_container {
let mut colour = grey;
if *k == 1 || *k == 4 {
colour = red;
}
canvas.filled_circle(
800 - v.position.x as i16,
590 - v.position.y as i16,
10, colour.as_rgba())?;
}
canvas.present();
lag -= fps;
}
}
Ok(())
}
//use crate::REAL_MAX;
use crate::particle::Particle;
use vec2d::Vec2d;
use std::collections::HashMap;
use std::f32::MAX as F32_MAX;
/////////////////////////////////////////////////////////////////////////////////
pub trait ContactGenerator {
fn add_contact(&self, contacts: &mut [Contact], map: &HashMap<u32, Particle>, limit: u32) -> u32;
}
#[derive(Debug)]
pub struct GroundContact {
_private: u8,
}
impl GroundContact {
pub fn new() -> GroundContact {
GroundContact { _private: 0 }
}
}
impl ContactGenerator for GroundContact {
fn add_contact(&self, contacts: &mut [Contact], particles: &HashMap<u32, Particle>, limit: u32) -> u32 {
let mut ci = 0;
let mut count = 0;
for (id, particle) in particles {
let y = particle.position.y;
if y < 0.0 {
contacts[ci].contact_normal = Vec2d::new(0.0, 1.0);
contacts[ci].first = *id;
contacts[ci].second = None;
contacts[ci].penetration = -y;
contacts[ci].restitution = 0.5;
count += 1;
ci += 1;
}
if count >= limit {
return count
}
}
count
}
}
/////////////////////////////////////////////////////////////////////////////////
pub struct ContactResolver {
iterations: u32,
iterations_used: u32,
}
impl ContactResolver {
pub fn new(n: u32) -> ContactResolver {
ContactResolver {
iterations: n,
iterations_used: 0,
}
}
pub fn set_iterations(&mut self, n: u32) {
self.iterations = n;
}
pub fn resolve_contacts(
&mut self,
contacts: &mut [Contact],
used_contacts: usize,
mut particles: &mut HashMap<u32, Particle>,
dt: f32,
) {
self.iterations_used = 0;
while self.iterations_used < self.iterations {
// find the contact with largest closing velocity
let zeroed = Vec2d::default();
let mut max = Vec2d::new(F32_MAX, F32_MAX);
let mut max_index = used_contacts;
for i in 0..used_contacts {
let sep_vel = contacts[i].calc_separating_velocity(&particles, dt);
if sep_vel < max &&
(sep_vel < zeroed || contacts[i].penetration > 0.0) {
max = sep_vel;
max_index = i;
}
}
if max_index == used_contacts {
break;
}
// resolve this contact
contacts[max_index].resolve(&mut particles, dt);
// Update the interpenetrations for all
for i in 0..used_contacts {
if contacts[i].first == contacts[max_index].first {
contacts[i].penetration -=
(contacts[i].first_move *
contacts[i].contact_normal).magnitude();
} else if let Some(second) = contacts[max_index].second {
if contacts[i].first == second {
contacts[i].penetration -=
(contacts[i].second_move *
contacts[i].contact_normal).magnitude();
}
}
if let Some(second_i) = contacts[i].second {
if second_i == contacts[max_index].first {
contacts[i].penetration +=
(contacts[i].first_move *
contacts[i].contact_normal).magnitude();
} else if let Some(second_max) = contacts[max_index].second {
if second_i == second_max {
contacts[i].penetration +=
(contacts[i].second_move *
contacts[i].contact_normal).magnitude();
}
}
}
}
self.iterations_used += 1;
}
}
}
/////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Default, Copy, Clone)]
pub struct Contact {
pub first: u32,
pub second: Option<u32>,
pub first_move: Vec2d<f32>,
pub second_move: Vec2d<f32>,
pub restitution: f32,
pub contact_normal: Vec2d<f32>,
pub penetration: f32,
}
impl Contact {
/// The main contact resolution method
#[inline(always)]
fn resolve(&mut self, mut particles: &mut HashMap<u32, Particle>, dt: f32) {
self.resolve_velocity(&mut particles, dt);
self.resolve_interpenetration(&mut particles, dt);
}
#[inline(always)]
fn calc_separating_velocity(&self, particles: &HashMap<u32, Particle>, _dt: f32) -> Vec2d<f32> {
if let Some(first) = particles.get(&self.first) {
let mut relative_velocity = first.velocity;
if let Some(second) = self.second {
if let Some(other_v) = particles.get(&second) {
relative_velocity -= other_v.velocity
};
};
return relative_velocity * self.contact_normal;
}
panic!("Argh!");//Vec2d::default()
}
fn resolve_velocity(&self, particles: &mut HashMap<u32, Particle>, dt: f32) {
// velocity in direction of contact
let separating_v = self.calc_separating_velocity(&particles, dt);
if separating_v.x > 0.0 && separating_v.y > 0.0 {
// The contact is either separating, or stationary;
// no impulse is required.
return;
}
// calculate new velocity
let mut new_separating_v = -separating_v * self.restitution;
let mut acc_caused_velocity = particles[&self.first].acceleration;
if let Some(second) = &self.second {
acc_caused_velocity -= particles[second].acceleration;
}
let acc_caused_sep_vel = acc_caused_velocity * self.contact_normal * dt;
// If we've got a closing velocity due to acceleration build-up,
// remove it from the new separating velocity
if acc_caused_sep_vel.x < 0.0 && acc_caused_sep_vel.y < 0.0 {
new_separating_v += acc_caused_sep_vel * self.restitution;
if new_separating_v.x < 0.0 && new_separating_v.y < 0.0 {
new_separating_v = Vec2d::default();
}
}
let delta_velocity = new_separating_v - separating_v;
// apply velocity to each entity in proportion to their mass
let total_inverse_mass = match self.get_total_inverse_mass(&particles) {
Some(inverse) => inverse,
None => return,
};
if total_inverse_mass <= 0.0 {
return;
}
let impulse = delta_velocity / total_inverse_mass;
// amount of impulse per inverse mass
let impulse_per_imass = self.contact_normal * impulse;
// Now, apply impulses
if let Some(first) = particles.get_mut(&self.first) {
first.velocity += impulse_per_imass
* first.inv_mass;
}
if let Some(second) = self.second {
if let Some(second) = particles.get_mut(&second) {
second.velocity += impulse_per_imass
* second.inv_mass;
}
}
}
#[inline(always)]
fn get_total_inverse_mass(&self, particles: &HashMap<u32, Particle>) -> Option<f32> {
match particles.get(&self.first) {
Some(first) => match self.second {
Some(id) => match particles.get(&id) {
Some(second) => Some(first.inv_mass + second.inv_mass),
None => Some(first.inv_mass),
},
None => Some(first.inv_mass),
},
None => None,
}
}
fn resolve_interpenetration(&mut self, particles: &mut HashMap<u32, Particle>, dt: f32) {
if self.penetration <= 0.0 {
return;
}
let total_inverse_mass = match self.get_total_inverse_mass(&particles) {
Some(inverse) => inverse,
None => return,
};
if total_inverse_mass <= 0.0 {
return;
}
let move_per_imass = self.contact_normal
* (self.penetration
/ total_inverse_mass);
// Calc movement amounts
if let Some(first) = particles.get(&self.first) {
self.first_move = move_per_imass * first.inv_mass;
if let Some(second) = self.second {
if let Some(second) = particles.get(&second) {
self.second_move = move_per_imass * -second.inv_mass;
}
} else {
self.second_move = Vec2d::default();
}
}
// Apply penetration resolution to positions
if let Some(ref mut first) = particles.get_mut(&self.first) {
first.position.x += self.first_move.x;
first.position.y += self.first_move.y;
};
if let Some(ref mut second) = self.second {
if let Some(second) = particles.get_mut(&second) {
second.position += self.second_move;
}
};
}
}
use crate::particle::Particle;
use std::collections::HashMap;
use vec2d::Vec2d;
pub trait ForceGenerator {
fn update_force(&self, particles: &mut HashMap<u32, Particle>);
}
/////////////////////////////////////////////////////////////////////////////////
pub struct Gravity {
particle: u32,
gravity: f32,
}
impl Gravity {
pub fn new(particle: u32, gravity: f32) -> Gravity {
Gravity { particle, gravity }
}
}
impl ForceGenerator for Gravity {
fn update_force(&self, particles: &mut HashMap<u32, Particle>) {
let particle = particles.get_mut(&self.particle).unwrap();
particle.force_accum.y += self.gravity * particle.mass;
}
}
/////////////////////////////////////////////////////////////////////////////////
pub struct Drag {
particle: u32,
k1: f32,
k2: f32,
}
impl Drag {
pub fn new(particle: u32, k1: f32, k2: f32) -> Drag {
Drag { particle, k1, k2 }
}
}
impl ForceGenerator for Drag {
fn update_force(&self, particles: &mut HashMap<u32, Particle>) {
let particle = particles.get_mut(&self.particle).unwrap();
let mut force = particle.velocity;
let mut drag_coeff = force.magnitude();
drag_coeff = self.k1 * drag_coeff + self.k2 * drag_coeff * drag_coeff;
force = force.normalise();
force *= -drag_coeff;
particle.force_accum += force;
}
}
/////////////////////////////////////////////////////////////////////////////////
pub struct AnchoredSpring {
particle: u32,
anchor: Vec2d<f32>,
spring_const: f32,
rest_len: f32,
}
impl AnchoredSpring {
pub fn new(
particle: u32,
anchor: Vec2d<f32>,
spring_const: f32,
rest_len: f32)
-> AnchoredSpring {
AnchoredSpring { particle, anchor, spring_const, rest_len }
}
}
impl ForceGenerator for AnchoredSpring {
fn update_force(&self, particles: &mut HashMap<u32, Particle>) {
let particle = particles.get_mut(&self.particle).unwrap();
// Calculate the vector of the spring
let mut force = particle.position;
force -= self.anchor;
// Calculate the magnitude of the force
let mut magnitude = force.magnitude();
magnitude = (self.rest_len - magnitude) * self.spring_const;
// Calculate the final force and apply it
force = force.normalise();
force *= magnitude;
particle.force_accum += force;
}
}
/////////////////////////////////////////////////////////////////////////////////
pub struct AnchoredBungee {
particle: u32,
anchor: u32,
spring_const: f32,
rest_len: f32,
}
impl AnchoredBungee {
pub fn new(
particle: u32,
anchor: u32,
spring_const: f32,
rest_len: f32)
-> AnchoredBungee {
AnchoredBungee { particle, anchor, spring_const, rest_len }
}
}
impl ForceGenerator for AnchoredBungee {
fn update_force(&self, particles: &mut HashMap<u32, Particle>) {
let pos = particles[&self.anchor].position;
let particle = particles.get_mut(&self.particle).unwrap();
// Calculate the vector of the spring
let mut force = particle.position;
force -= pos;
// Calculate the magnitude of the force
let mut magnitude = force.magnitude();
if magnitude <= self.rest_len {
return
}
magnitude -= self.rest_len;
magnitude *= self.spring_const;
// Calculate the final force and apply it
force = force.normalise();
force *= -magnitude;
particle.force_accum += force;
}
}
/////////////////////////////////////////////////////////////////////////////////
pub struct Buoyancy {
particle: u32,
max_depth: f32,
volume: f32,
water_height: f32,
liquid_density: f32,
}
impl Buoyancy {
pub fn new(
particle: u32,
max_depth: f32,
volume: f32,
water_height: f32,
liquid_density: f32)
-> Buoyancy {
Buoyancy { particle, max_depth, volume, water_height, liquid_density }
}
}
impl ForceGenerator for Buoyancy {
fn update_force(&self, particles: &mut HashMap<u32, Particle>) {
let particle = particles.get_mut(&self.particle).unwrap();
let depth = particle.position.y;
// check if out of water
if depth >= self.water_height + self.max_depth {
return;
}
let mut force = Vec2d::default();
// check if at max depth
if depth < self.water_height - self.max_depth {
force.y = self.liquid_density * self.volume;
particle.force_accum += force;
return;
}
// otherwise partly submerged
force.y = self.liquid_density * self.volume *
(depth - self.max_depth - self.water_height) / 2.0 * self.max_depth;
particle.force_accum += force;
}
}
/////////////////////////////////////////////////////////////////////////////////
pub struct ForceRegistration {
fg: Box<ForceGenerator>,
}
pub struct ForceRegistry {
registrations: Vec<ForceRegistration>,
}
impl ForceRegistry {
pub fn new() -> ForceRegistry {
ForceRegistry {
registrations: Vec::new()
}
}
pub fn add(&mut self, fg: Box<ForceGenerator>) {
self.registrations.push(
ForceRegistration {
fg,
});
}
pub fn remove(&mut self) {}
pub fn clear(&mut self) {}
pub fn update_forces(&mut self, map: &mut HashMap<u32, Particle>, dt: f32) {
for reg in &self.registrations {
reg.fg.update_force(map);
}
}
}
extern crate vec2d;
pub mod particle;
pub mod forces;
pub mod world;
pub mod contacts;
#[cfg(test)]
mod tests {
use crate::world::World;
use crate::particle::Particle;
use crate::contacts::GroundContact;
use crate::forces::{Drag, Gravity, AnchoredSpring};
use std::collections::HashMap;
use vec2d::Vec2d;
#[test]
fn setup() {
// max_contacts, iterations
let mut world = World::new(1000, None);
let mut bodies = HashMap::new();
let mut particle = Particle::new(1.0, 0.3);
particle.position.x = 1.0;
particle.position.y = 100.0;
bodies.insert(0, particle);
let mut particle = Particle::new(1.0, 0.3);
particle.position.x = 1.0;
particle.position.y = 100.0;
bodies.insert(1, particle);
let mut particle = Particle::new(1.0, 0.2);
particle.position.x = 1.0;
particle.position.y = 100.0;
bodies.insert(2, particle);
// Insert force gen
world.add_force_generator(Box::new(Gravity::new(0, -9.81)));
world.add_force_generator(Box::new(Drag::new(0, 1.0, 1.0)));
world.add_force_generator(Box::new(Gravity::new(1, -9.81)));
world.add_force_generator(Box::new(Drag::new(1, 1.0, 1.0)));
world.add_force_generator(Box::new(Gravity::new(2, -9.81)));
world.add_force_generator(Box::new(Drag::new(2, 1.0, 1.0)));
// Insert contact gen