rust/hwphysics/src/lib.rs
author alfadur
Tue, 06 Nov 2018 19:44:20 +0300
changeset 14144 165e43c3ed59
parent 14059 c6745a1c827a
child 14178 a4c17cfaa4c9
permissions -rw-r--r--
pull land into collision detector

use std::{
    ops::RangeInclusive
};

use fpnum::*;
use integral_geometry::{
    Point, Size, GridIndex
};
use land2d::Land2D;

type Index = u16;

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
struct PhysicsData {
    position: FPPoint,
    velocity: FPPoint,
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
struct  CollisionData {
    bounds: CircleBounds
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
struct ContactData {
    elasticity: FPNum,
    friction: FPNum
}

pub struct PhysicsCollection {
    positions: Vec<FPPoint>,
    velocities: Vec<FPPoint>
}

impl PhysicsCollection {
    fn push(&mut self, data: PhysicsData) {
        self.positions.push(data.position);
        self.velocities.push(data.velocity);
    }

    fn iter_mut_pos(&mut self) -> impl Iterator<Item = (&mut FPPoint, &FPPoint)> {
        self.positions.iter_mut().zip(self.velocities.iter())
    }
}

pub struct JoinedData {
    physics: PhysicsData,
    collision: CollisionData,
    contact: ContactData
}

pub struct World {
    enabled_physics: PhysicsCollection,
    disabled_physics: Vec<PhysicsData>,

    enabled_collision: Vec<CollisionData>,
    disabled_collision: Vec<CollisionData>,
    grid: Grid,

    physics_cleanup: Vec<PhysicsData>,
    collision_output: Vec<(Index, Index)>,
    land_collision_output: Vec<Index>
}

#[derive(PartialEq, Eq, Clone, Copy, Debug)]
struct CircleBounds {
    center: FPPoint,
    radius: FPNum
}

impl CircleBounds {
    pub fn intersects(&self, other: &CircleBounds) -> bool {
        (other.center - self.center).is_in_range(self.radius + other.radius)
    }

    pub fn rows(&self) -> impl Iterator<Item = (usize, RangeInclusive<usize>)> {
        let radius = self.radius.abs_round() as usize;
        let center = Point::from_fppoint(&self.center);
        (center.y as usize - radius..=center.y as usize + radius)
            .map(move |row| (row, center.x as usize - radius..=center.x as usize + radius))
    }
}

fn fppoint_round(point: &FPPoint) -> Point {
    Point::new(point.x().round() as i32, point.y().round() as i32)
}

struct GridBin {
    refs: Vec<Index>,
    static_entries: Vec<CircleBounds>,
    dynamic_entries: Vec<CircleBounds>
}

impl GridBin {
    fn new() -> Self {
        Self {
            refs: vec![],
            static_entries: vec![],
            dynamic_entries: vec![]
        }
    }
}

const GRID_BIN_SIZE: usize = 256;

struct Grid {
    bins: Vec<GridBin>,
    space_size: Size,
    bins_count: Size,
    index: GridIndex
}

impl Grid {
    fn new(size: Size) -> Self {
        assert!(size.is_power_of_two());
        let bins_count =
            Size::new(size.width / GRID_BIN_SIZE,
                      size.height / GRID_BIN_SIZE);

        Self {
            bins: (0..bins_count.area()).map(|_| GridBin::new()).collect(),
            space_size: size,
            bins_count,
            index: Size::square(GRID_BIN_SIZE).to_grid_index()
        }
    }

    fn bin_index(&self, position: &FPPoint) -> Point {
        self.index.map(fppoint_round(position))
    }

    fn lookup_bin(&mut self, position: &FPPoint) -> &mut GridBin {
        let index = self.bin_index(position);
        &mut self.bins[index.x as usize * self.bins_count.width + index.y as usize]
    }

    fn insert_static(&mut self, index: Index, position: &FPPoint, bounds: &CircleBounds) {
        self.lookup_bin(position).static_entries.push(*bounds)
    }

    fn insert_dynamic(&mut self, index: Index, position: &FPPoint, bounds: &CircleBounds) {
        self.lookup_bin(position).dynamic_entries.push(*bounds)
    }

    fn check_collisions(&self, collisions: &mut Vec<(Index, Index)>) {
        for bin in &self.bins {
            for bounds in &bin.dynamic_entries {
                for other in &bin.dynamic_entries {
                    if bounds.intersects(other) && bounds != other {
                        collisions.push((0, 0))
                    }
                }

                for other in &bin.static_entries {
                    if bounds.intersects(other) {
                        collisions.push((0, 0))
                    }
                }
            }
        }
    }
}

impl World {
    pub fn step(&mut self, time_step: FPNum, land: &Land2D<u32>) {
        for (pos, vel) in self.enabled_physics.iter_mut_pos() {
            *pos += *vel
        }

        self.grid.check_collisions(&mut self.collision_output);
    }

    fn check_land_collisions(&mut self, land: &Land2D<u32>) {
        for collision in &self.enabled_collision {
            if collision.bounds.rows().any(|(y, r)| (&land[y][r]).iter().any(|v| *v != 0)) {
                self.land_collision_output.push(0)
            }
        }
    }

    pub fn add_gear(&mut self, data: JoinedData) {
        if data.physics.velocity == FPPoint::zero() {
            self.disabled_physics.push(data.physics);
            self.grid.insert_static(0, &data.physics.position, &data.collision.bounds);
        } else {
            self.enabled_physics.push(data.physics);
            self.grid.insert_dynamic(0, &data.physics.position, &data.collision.bounds);
        }
    }
}

#[cfg(test)]
mod tests {

}