// mlodato, 20190318 extern crate zvxryb_broadphase as broadphase; extern crate backtrace; extern crate cgmath; extern crate env_logger; extern crate rand; extern crate specs; #[macro_use] extern crate glium; use glium::glutin; #[macro_use] extern crate log; #[macro_use(defer)] extern crate scopeguard; use cgmath::prelude::*; use rand::prelude::*; use specs::prelude::*; use broadphase::Bounds; use cgmath::{Point2, Vector2}; use std::alloc::{GlobalAlloc, Layout as AllocLayout, System as SystemAlloc}; use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering as AtomicOrdering}; use std::time::{Duration, Instant}; enum AllocState { Normal, Dump, Alloc, } struct AllocLogger { count: AtomicUsize, state: AtomicU32, } impl AllocLogger { fn begin_dump(&self) { while let Err(val) = self.state.compare_exchange_weak( AllocState::Normal as u32, AllocState::Dump as u32, AtomicOrdering::SeqCst, AtomicOrdering::Relaxed ) { if val == AllocState::Dump as u32 { break; } } } fn end_dump(&self) { while let Err(val) = self.state.compare_exchange_weak( AllocState::Dump as u32, AllocState::Normal as u32, AtomicOrdering::SeqCst, AtomicOrdering::Relaxed ) { if val == AllocState::Normal as u32 { break; } } } fn clear_and_get_stats(&self) -> usize { self.count.swap(0, AtomicOrdering::Relaxed) } } unsafe impl GlobalAlloc for AllocLogger { #[inline(never)] unsafe fn alloc(&self, layout: AllocLayout) -> *mut u8 { let state = self.state.swap(AllocState::Alloc as u32, AtomicOrdering::SeqCst); defer!{ if state != AllocState::Alloc as u32 { self.state.store(state, AtomicOrdering::SeqCst); } } if state != AllocState::Alloc as u32 { self.count.fetch_add(1, AtomicOrdering::Relaxed); } if state == AllocState::Dump as u32 { let bt = backtrace::Backtrace::new(); trace!("{:?}", bt); } SystemAlloc.alloc(layout) } unsafe fn dealloc(&self, ptr: *mut u8, layout: AllocLayout) { SystemAlloc.dealloc(ptr, layout); } } #[global_allocator] static ALLOCATOR: AllocLogger = AllocLogger{ count: AtomicUsize::new(0), state: AtomicU32::new(AllocState::Normal as u32), }; struct Time { real: Instant, sim : Duration, draw: Duration, step: Duration, } impl Time { fn update(&mut self, step: Duration, max_delta: Duration) { let now = Instant::now(); let delta = now - self.real; self.real = now; self.draw += std::cmp::min(delta, max_delta); self.step = step; } fn step(&mut self) -> bool { if self.sim + self.step <= self.draw { self.sim += self.step; true } else { false } } fn remainder(&self) -> Duration { self.draw - self.sim } } impl Default for Time { fn default() -> Self { Self { real: Instant::now(), sim : Default::default(), draw: Default::default(), step: Default::default(), } } } struct CollisionSystemConfig { enabled: bool, dump_frame_allocs: bool, bounds: Bounds> } impl Default for CollisionSystemConfig { fn default() -> Self { Self{ enabled: true, dump_frame_allocs: false, bounds: Bounds::new( Point2::new(0f32, 0f32), Point2::new(1f32, 1f32))} } } impl CollisionSystemConfig { fn bounds(w: u32, h: u32) -> Bounds> { const BORDER: f32 = 1f32; let scale = if w > h { w } else { h } as f32; let min = Point2::new(0f32, 0f32).sub_element_wise(BORDER); let max = min.add_element_wise(scale).add_element_wise(BORDER); Bounds::new(min, max) } fn from_screen_size((w, h): (u32, u32)) -> Self { Self{ enabled: true, dump_frame_allocs: false, bounds: Self::bounds(w, h)} } fn update_bounds(&mut self, w: u32, h: u32) { self.bounds = Self::bounds(w, h); } } struct ScreenSize(u32, u32); impl Default for ScreenSize { fn default() -> Self { Self(1, 1) } } impl From<(u32, u32)> for ScreenSize { fn from((w, h): (u32, u32)) -> Self { Self(w, h) } } impl From> for ScreenSize { fn from(size: glutin::dpi::PhysicalSize) -> Self { Self(size.width, size.height) } } struct BallCount(u32); impl Default for BallCount { fn default() -> Self { Self(0) } } #[derive(Copy, Clone)] struct Color(f32, f32, f32, f32); impl Default for Color { fn default() -> Self { Self(1.0, 1.0, 1.0, 1.0) } } impl Into<[f32; 4]> for Color { fn into(self) -> [f32; 4] { [self.0, self.1, self.2, self.3] } } impl specs::Component for Color { type Storage = specs::VecStorage; } struct Lifetime(Duration); impl specs::Component for Lifetime { type Storage = specs::VecStorage; } impl From for Lifetime { fn from(expires: Duration) -> Self { Self(expires) } } #[derive(Copy, Clone)] struct VerletPosition(Point2, Point2); impl specs::Component for VerletPosition { type Storage = specs::VecStorage; } impl From<(f32, f32)> for VerletPosition { fn from(pos: (f32, f32)) -> Self { Self(pos.into(), pos.into()) } } struct Radius(f32); impl specs::Component for Radius { type Storage = specs::VecStorage; } impl From for Radius { fn from(radius: f32) -> Self { Self(radius) } } fn create_ball( builder: T, lifetime: Lifetime, position: VerletPosition, radius: Radius, ) -> specs::Entity { builder .with(lifetime) .with(position) .with(radius) .with(Color::default()) .build() } struct Lifecycle; impl<'a> specs::System<'a> for Lifecycle { #[allow(clippy::type_complexity)] type SystemData = ( specs::Entities<'a>, specs::Read<'a, specs::LazyUpdate>, specs::Read<'a, Time>, specs::Read<'a, ScreenSize>, specs::Write<'a, BallCount>, specs::ReadStorage<'a, Lifetime>, ); #[inline(never)] fn run(&mut self, data: Self::SystemData) { let (entities, lazy, time, screen_size, mut ball_count, lifetimes) = data; for (entity, &Lifetime(expires)) in (&entities, &lifetimes).join() { if expires < time.sim { entities.delete(entity).unwrap(); ball_count.0 -= 1; } } const BALL_COUNT_MAX: u32 = 2500; const LIFETIME_MIN_MS: u32 = 10000; const LIFETIME_MAX_MS: u32 = 50000; for _ in 0..BALL_COUNT_MAX*time.step.subsec_millis()/LIFETIME_MIN_MS { if ball_count.0 >= BALL_COUNT_MAX { break; } let lifetime = Duration::from_millis(rand::thread_rng().gen_range( LIFETIME_MIN_MS as u64, LIFETIME_MAX_MS as u64)); let r = rand::thread_rng().gen_range(0.5f32, 2.0f32).exp(); let x0 = 0f32 + r; let x1 = screen_size.0 as f32 - 2f32 * r + x0; let x = rand::thread_rng().gen_range(x0, x1); let y = screen_size.1 as f32 + r; create_ball( lazy.create_entity(&entities), (time.sim + lifetime).into(), (x, y).into(), r.into(), ); ball_count.0 += 1; } } } struct Kinematics; impl<'a> specs::System<'a> for Kinematics { type SystemData = ( specs::Read<'a, Time>, specs::WriteStorage<'a, VerletPosition>, ); #[inline(never)] fn run(&mut self, data: Self::SystemData) { let (time, mut positions) = data; let dt = (time.step.as_secs() as f32) + (time.step.subsec_micros() as f32) / 1_000_000f32; let gravity = 50f32 * dt * dt; for mut pos in (&mut positions).join() { let mut pos_2 = pos.1 + (pos.1 - pos.0); pos_2.y -= gravity; pos.0 = pos.1; pos.1 = pos_2; } for mut pos in (&mut positions).join() { let velocity = pos.1 - pos.0; let speed = velocity.magnitude(); const SPEED_LIMIT: f32 = 1.5f32; if speed > SPEED_LIMIT { pos.1 = pos.0 + SPEED_LIMIT * velocity / speed; } } } } struct Collisions { system: broadphase::Layer, collisions: Vec<(specs::Entity, specs::Entity, f32, Vector2)>, } impl Collisions { fn new() -> Self { Self { system: broadphase::LayerBuilder::new() .with_min_depth(4) .build(), collisions: Vec::new(), } } } impl<'a> specs::System<'a> for Collisions { #[allow(clippy::type_complexity)] type SystemData = ( specs::Entities<'a>, specs::Read<'a, ScreenSize>, specs::Write<'a, CollisionSystemConfig>, specs::WriteStorage<'a, VerletPosition>, specs::ReadStorage<'a, Radius>, specs::WriteStorage<'a, Color> ); #[inline(never)] fn run(&mut self, data: Self::SystemData) { let (entities, screen_size, mut collision_config, mut positions, radii, mut colors) = data; for color in (&mut colors).join() { *color = Color(1f32, 0.5f32, 0f32, 1f32); } let start = Instant::now(); if collision_config.enabled { ALLOCATOR.clear_and_get_stats(); if collision_config.dump_frame_allocs { ALLOCATOR.begin_dump(); collision_config.dump_frame_allocs = false; } defer!{ALLOCATOR.end_dump();} self.system.clear(); self.system.extend(collision_config.bounds, (&entities, &positions, &radii).join() .map(|(ent, &pos, &Radius(r))| { let bounds = Bounds{ min: Point2::new(pos.1.x - r, pos.1.y - r), max: Point2::new(pos.1.x + r, pos.1.y + r)}; (bounds, ent.id())})); self.system.par_sort(); { if let Some((_dist, id, _point)) = self.system.pick_ray( collision_config.bounds, Point2::new(0f32, 360f32), Vector2::new(1f32, 0f32), std::f32::INFINITY, None, |ray_origin, ray_direction, _dist, id| { let ent = entities.entity(id); let color = colors.get_mut(ent).unwrap(); *color = Color(0.5f32, 1f32, 0f32, 1f32); let position = positions.get(ent).unwrap(); let Radius(r) = radii.get(ent).unwrap(); let center = Point2::new(position.1.x, position.1.y); let ball_dir = center - ray_origin; let ball_proj = ray_direction.dot(ball_dir); let ball_extent = (ball_proj.powi(2) - ball_dir.magnitude2() + r.powi(2)).sqrt(); let range_min = ball_proj - ball_extent; let range_max = ball_proj + ball_extent; if range_max < 0f32 { std::f32::INFINITY } else if range_min < 0f32 { 0f32 } else { range_min } }) { let ent = entities.entity(id); let color = colors.get_mut(ent).unwrap(); *color = Color(1f32, 0f32, 0f32, 1f32); } } self.collisions.clear(); self.collisions.extend(self.system.par_scan() .iter() .filter_map(|&(id0, id1)| { let ent0 = entities.entity(id0); let ent1 = entities.entity(id1); let pos0 = positions.get(ent0).unwrap(); let pos1 = positions.get(ent1).unwrap(); let Radius(r0) = radii.get(ent0).unwrap(); let Radius(r1) = radii.get(ent1).unwrap(); let offset = pos1.1 - pos0.1; let dist = offset.magnitude(); let dist_min = r0 + r1; if dist > dist_min { None } else { let d = dist_min - dist; let n = if dist > 0.001 { offset / dist } else { Vector2::unit_x() }; let u = r1.powi(3) / (r0.powi(3) + r1.powi(3)); Some((ent0, ent1, u, n * d)) }})); print!("collisions: {:6} ", self.collisions.len()); let alloc_count = ALLOCATOR.clear_and_get_stats(); print!("allocs: {:6} ", alloc_count); } else { self.collisions.clear(); for (ent0, pos0, &Radius(r0)) in (&entities, &positions, &radii).join() { for (ent1, pos1, &Radius(r1)) in (&entities, &positions, &radii).join() { if ent1.id() >= ent0.id() { continue; } let offset = pos1.1 - pos0.1; let dist = offset.magnitude(); let dist_min = r0 + r1; if dist < dist_min { let d = dist_min - dist; let n = offset / dist; let u = r1.powi(3) / (r0.powi(3) + r1.powi(3)); self.collisions .push((ent0, ent1, u, n * d)); } } } } print!("elapsed: {:6}us\r", start.elapsed().subsec_micros()); for &(ent0, ent1, u, v) in &self.collisions { positions.get_mut(ent0).unwrap().1 -= v * u; positions.get_mut(ent1).unwrap().1 += v * (1f32 - u); } let x_min = 0f32; let y_min = 0f32; let x_max = screen_size.0 as f32 + x_min; let y_max = screen_size.1 as f32 + y_min; for (mut pos, &Radius(r)) in (&mut positions, &radii).join() { if pos.1.x - r < x_min { pos.1.x = x_min + r } if pos.1.y - r < y_min { pos.1.y = y_min + r } if pos.1.x + r > x_max { pos.1.x = x_max - r } if pos.1.y + r > y_max { pos.1.y = y_max - r } } } } #[derive(Copy, Clone)] struct VertexData { offset: [f32; 2], } implement_vertex!(VertexData, offset); #[derive(Copy, Clone)] struct InstanceData { origin: [f32; 2], scale : [f32; 2], color : [f32; 4], } implement_vertex!(InstanceData, origin, scale, color); struct InstanceBuffer { vbo: glium::VertexBuffer, count: usize } impl InstanceBuffer { fn with_capacity(display: &glium::Display, count: usize) -> Option { let vbo = glium::VertexBuffer::::empty_persistent(display, count).ok()?; Some(Self{ vbo, count }) } fn resize(&mut self, display: &glium::Display, count: usize) { if count > self.vbo.len() { self.vbo = glium::VertexBuffer::::empty_persistent(display, count) .expect("failed to create vbo"); } self.count = count; } fn slice(&self) -> glium::vertex::VertexBufferSlice<'_, InstanceData> { self.vbo.slice(0..self.count).unwrap() } fn write(&mut self, display: &glium::Display, data: &[InstanceData]) { self.resize(display, data.len()); if !data.is_empty() { self.slice().write(data); } } } struct Renderer { program_main: glium::Program, screen_size : [f32; 2], boxes : InstanceBuffer, circles : InstanceBuffer, vbo_box : glium::VertexBuffer, vbo_circle : glium::VertexBuffer, } impl Renderer { fn from_display(display: &glium::Display) -> Self { let screen_size = display.get_framebuffer_dimensions(); let screen_size = [screen_size.0 as f32, screen_size.1 as f32]; let program_main = glium::Program::from_source(display, r#" #version 450 core in vec2 offset; in vec2 origin; in vec2 scale; in vec4 color; out vec4 v_color; uniform vec2 screen_size; void main() { vec2 position = origin + scale * offset; v_color = color; gl_Position = vec4(2.0 * position / screen_size - 1.0, 0.0, 1.0); } "#, r#" #version 450 core in vec4 v_color; out vec4 f_color; void main() { f_color = vec4(v_color.rgb, 1.0); } "#, None) .expect("failed to compile shader"); let boxes = InstanceBuffer::with_capacity(display, 40_000).expect("failed to create boxes instance buffer"); let circles = InstanceBuffer::with_capacity(display, 10_000).expect("failed to create circles instance buffer"); let vbo_box = glium::VertexBuffer::immutable(display, &[ VertexData{ offset: [-0.5, -0.5] }, VertexData{ offset: [ 0.5, -0.5] }, VertexData{ offset: [ 0.5, 0.5] }, VertexData{ offset: [-0.5, 0.5] }, ]).expect("failed to create box vbo"); let vbo_circle = { const SIDES: u32 = 16; let data: Vec<_> = (0..SIDES) .map(|i| 2f32 * std::f32::consts::PI * (i as f32) / (SIDES as f32)) .map(|u| [u.cos(), u.sin()]) .map(|offset| VertexData{ offset }) .collect(); glium::VertexBuffer::immutable(display, data.as_slice()) .expect("failed to create circle vbo") }; Self{ program_main, screen_size, boxes, circles, vbo_box, vbo_circle, } } fn update_screen_size(&mut self, size: [f32; 2]) { self.screen_size = size; } fn update_boxes(&mut self, display: &glium::Display, boxes: &[InstanceData]) { self.boxes.write(display, boxes); } fn update_circles(&mut self, display: &glium::Display, circles: &[InstanceData]) { self.circles.write(display, circles); } fn draw(&self, frame: &mut glium::Frame) { use glium::Surface; let params = glium::DrawParameters{ .. Default::default() }; let uniforms = uniform!{ screen_size: self.screen_size }; frame.draw( (&self.vbo_circle, self.circles.slice().per_instance().unwrap()), glium::index::NoIndices(glium::index::PrimitiveType::LineLoop), &self.program_main, &uniforms, ¶ms) .expect("failed to draw circles"); frame.draw( (&self.vbo_box, self.boxes.slice().per_instance().unwrap()), glium::index::NoIndices(glium::index::PrimitiveType::LineLoop), &self.program_main, &uniforms, ¶ms) .expect("failed to draw boxes"); } } struct GameState { world: specs::World, lifecycle: Lifecycle, kinematics: Kinematics, collisions: Collisions, } impl GameState { const FRAME_RATE: u32 = 100; const FRAME_TIME_US: u32 = 1_000_000u32 / Self::FRAME_RATE; const MIN_FRAME_RATE: u32 = 20; const MAX_FRAME_TIME_US: u32 = 1_000_000u32 / Self::MIN_FRAME_RATE; fn new() -> Self { Self{ world: specs::World::new(), lifecycle: Lifecycle {}, kinematics: Kinematics {}, collisions: Collisions::new(), } } } impl GameState { #[inline(never)] fn update(&mut self) { let step = Duration::from_micros(Self::FRAME_TIME_US as u64); let max_delta = Duration::from_micros(Self::MAX_FRAME_TIME_US as u64); self.world.get_mut::