use fnv::FnvHashMap;
use std::mem;
use core::events::{EventManager, EventKey, EventKeysMap, InternalEvent};
use core::{Hitbox, HbVel, HbId, HIGH_TIME, HbProfile, HbGroup};
use core::grid::Grid;
use core::dur_hitbox::DurHitbox;
use geom::PlacedShape;
use util::TightSet;
pub struct Collider<P: HbProfile> {
hitboxes: FnvHashMap<HbId, HitboxInfo<P>>,
time: f64,
grid: Grid,
padding: f64,
events: EventManager
}
impl <P: HbProfile> Collider<P> {
pub fn new() -> Collider<P> {
let cell_width = P::cell_width();
let padding = P::padding();
assert!(cell_width > padding, "requires cell_width > padding");
assert!(padding > 0.0, "requires padding > 0.0");
Collider {
hitboxes : FnvHashMap::default(),
time : 0.0,
grid : Grid::new(cell_width),
padding : padding,
events : EventManager::new()
}
}
pub fn time(&self) -> f64 {
self.time
}
pub fn next_time(&self) -> f64 {
self.events.peek_time()
}
pub fn set_time(&mut self, time: f64) {
assert!(time >= self.time, "cannot rewind time");
assert!(time <= self.next_time(), "time must not exceed next_time()");
assert!(time < HIGH_TIME, "time must not exceed {}", HIGH_TIME);
self.time = time;
}
pub fn next(&mut self) -> Option<(HbEvent, P, P)> {
while let Some(event) = self.events.next(self.time, &mut self.hitboxes) {
if let Some((event, id_1, id_2)) = self.process_event(event) {
return Some((event, self.hitboxes[&id_1].profile, self.hitboxes[&id_2].profile));
}
}
None
}
fn process_event(&mut self, event: InternalEvent) -> Option<(HbEvent, HbId, HbId)> {
match event {
InternalEvent::Collide(id_1, id_2) => {
let mut hitbox_info_1 = self.hitboxes.remove(&id_1).unwrap();
{
let hitbox_info_2 = self.hitboxes.get_mut(&id_2).unwrap();
Collider::process_collision(id_1, &mut hitbox_info_1, id_2, hitbox_info_2,
&mut self.events, self.time, self.padding);
}
assert!(self.hitboxes.insert(id_1, hitbox_info_1).is_none());
Some(new_event(HbEvent::Collide, id_1, id_2))
},
InternalEvent::Separate(id_1, id_2) => {
let mut hitbox_info_1 = self.hitboxes.remove(&id_1).unwrap();
{
let hitbox_info_2 = self.hitboxes.get_mut(&id_2).unwrap();
assert!(hitbox_info_1.overlaps.remove(&id_2));
assert!(hitbox_info_2.overlaps.remove(&id_1));
let delay = hitbox_info_1.hitbox_at_time(self.time).collide_time(&hitbox_info_2.hitbox_at_time(self.time));
self.events.add_pair_event(self.time + delay, InternalEvent::Collide(id_1, id_2),
&mut hitbox_info_1.event_keys, &mut hitbox_info_2.event_keys);
}
assert!(self.hitboxes.insert(id_1, hitbox_info_1).is_none());
Some(new_event(HbEvent::Separate, id_1, id_2))
},
InternalEvent::Reiterate(id) => {
self.internal_update_hitbox(id, None);
None
},
#[cfg(debug_assertions)]
InternalEvent::PanicSmallHitbox(id) => {
panic!("hitbox {} became too small", id)
},
#[cfg(debug_assertions)]
InternalEvent::PanicDurationPassed(id) => {
panic!("hitbox {} was not updated before duration passed", id)
}
}
}
fn process_collision(id_1: HbId, hb_1: &mut HitboxInfo<P>, id_2: HbId, hb_2: &mut HitboxInfo<P>,
events: &mut EventManager, time: f64, padding: f64) {
assert!(hb_1.overlaps.insert(id_2));
assert!(hb_2.overlaps.insert(id_1));
let delay = hb_1.hitbox_at_time(time).separate_time(&hb_2.hitbox_at_time(time), padding);
events.add_pair_event(time + delay, InternalEvent::Separate(id_1, id_2),
&mut hb_1.event_keys, &mut hb_2.event_keys);
}
pub fn get_hitbox(&self, id: HbId) -> Hitbox {
self.hitboxes[&id].pub_hitbox_at_time(self.time)
}
pub fn add_hitbox(&mut self, profile: P, hitbox: Hitbox) -> Vec<P> {
hitbox.validate(self.padding, self.time);
let id = profile.id();
let has_group = profile.group().is_some();
let mut info = HitboxInfo::new(hitbox, profile, self.time);
self.solitaire_event_check(id, &mut info, has_group);
let dur_hitbox = info.hitbox.to_dur_hitbox(self.time);
self.update_hitbox_tracking(id, info, None, dur_hitbox)
}
pub fn set_hitbox_vel(&mut self, id: HbId, vel: HbVel) {
if self.hitboxes[&id].hitbox.vel != vel {
self.internal_update_hitbox(id, Some(vel));
}
}
fn internal_update_hitbox(&mut self, id: HbId, vel: Option<HbVel>) {
let mut info = self.hitboxes.remove(&id).unwrap_or_else(|| panic!("hitbox id {} not found", id));
let old_hitbox = info.hitbox.to_dur_hitbox(info.start_time);
info.hitbox = info.pub_hitbox_at_time(self.time);
if let Some(vel) = vel {
info.hitbox.vel = vel;
info.hitbox.validate(self.padding, self.time);
}
info.start_time = self.time;
let has_group = info.profile.group().is_some();
self.events.clear_related_events(id, &mut info.event_keys, &mut self.hitboxes);
self.solitaire_event_check(id, &mut info, has_group);
let new_hitbox = info.hitbox.to_dur_hitbox(self.time);
let result = self.update_hitbox_tracking(id, info, Some(old_hitbox), new_hitbox);
assert!(result.is_empty());
}
pub fn remove_hitbox(&mut self, id: HbId) -> Vec<P> {
let mut info = self.hitboxes.remove(&id).unwrap_or_else(|| panic!("hitbox id {} not found", id));
self.events.clear_related_events(id, &mut info.event_keys, &mut self.hitboxes);
if let Some(group) = info.profile.group() {
let info_start_time = info.start_time;
let empty_group_array: &[HbGroup] = &[];
self.grid.update_hitbox(
id, group, Some(&info.hitbox.to_dur_hitbox(info_start_time)), None, empty_group_array
);
}
self.clear_overlaps(id, &mut info)
}
pub fn get_overlaps(&self, id: HbId) -> Vec<P> {
let info = self.hitboxes.get(&id).unwrap_or_else(|| panic!("hitbox id {} not found", id));
info.overlaps.iter()
.map(|other_id| self.hitboxes[other_id].profile)
.collect()
}
pub fn is_overlapping(&self, id_1: HbId, id_2: HbId) -> bool {
self.hitboxes.get(&id_1)
.map(|info| info.overlaps.contains(&id_2))
.unwrap_or(false)
}
pub fn query_overlaps(&self, shape: &PlacedShape, profile: &P) -> Vec<P> {
self.grid.shape_cellmates(shape, profile.interact_groups()).iter()
.map(|id| &self.hitboxes[id])
.filter(|info| info.profile.can_interact(profile))
.filter(|info| info.pub_hitbox_at_time(self.time).value.overlaps(shape))
.map(|info| info.profile)
.collect()
}
fn update_hitbox_tracking(&mut self, id: HbId, mut info: HitboxInfo<P>, old_hitbox: Option<DurHitbox>,
new_hitbox: DurHitbox) -> Vec<P> {
let mut result = Vec::new();
if let Some(group) = info.profile.group() {
for &other_id in info.overlaps.clone().iter() {
let other_info = self.hitboxes.get_mut(&other_id).unwrap();
let delay = new_hitbox.separate_time(&other_info.hitbox_at_time(self.time), self.padding);
self.events.add_pair_event(self.time + delay, InternalEvent::Separate(id, other_id),
&mut info.event_keys, &mut other_info.event_keys);
}
let test_ids = self.grid.update_hitbox(
id, group, old_hitbox.as_ref(), Some(&new_hitbox), info.profile.interact_groups()
).unwrap();
for other_id in test_ids {
if old_hitbox.is_none() || !info.overlaps.contains(&other_id) {
let other_info = self.hitboxes.get_mut(&other_id).unwrap();
if info.profile.can_interact(&other_info.profile) {
let delay = new_hitbox.collide_time(&other_info.hitbox_at_time(self.time));
if old_hitbox.is_none() && delay == 0.0 {
result.push(other_info.profile);
Collider::process_collision(id, &mut info, other_id, other_info,
&mut self.events, self.time, self.padding);
} else {
self.events.add_pair_event(self.time + delay, InternalEvent::Collide(id, other_id),
&mut info.event_keys, &mut other_info.event_keys);
}
}
}
}
}
assert!(self.hitboxes.insert(id, info).is_none());
result
}
fn clear_overlaps(&mut self, id: HbId, hitbox_info: &mut HitboxInfo<P>) -> Vec<P> {
hitbox_info.overlaps.drain().map(|other_id| {
let other_hitbox_info = self.hitboxes.get_mut(&other_id).unwrap();
assert!(other_hitbox_info.overlaps.remove(&id));
other_hitbox_info.profile
}).collect()
}
#[cfg(debug_assertions)]
fn solitaire_event_check(&mut self, id: HbId, hitbox_info: &mut HitboxInfo<P>, has_group: bool) {
hitbox_info.pub_end_time = hitbox_info.hitbox.vel.end_time;
let mut result = (self.time + self.grid.cell_period(&hitbox_info.hitbox, has_group), InternalEvent::Reiterate(id));
let end_time = hitbox_info.hitbox.vel.end_time;
if end_time < result.0 { result = (end_time, InternalEvent::PanicDurationPassed(id)); }
let end_time = self.time + hitbox_info.hitbox.time_until_too_small(self.padding);
if end_time < result.0 { result = (end_time, InternalEvent::PanicSmallHitbox(id)); }
hitbox_info.hitbox.vel.end_time = result.0;
self.events.add_solitaire_event(result.0, result.1, &mut hitbox_info.event_keys);
}
#[cfg(not(debug_assertions))]
fn solitaire_event_check(&mut self, id: HbId, hitbox_info: &mut HitboxInfo<P>, has_group: bool) {
hitbox_info.pub_end_time = hitbox_info.hitbox.vel.end_time;
let mut result = (self.time + self.grid.cell_period(&hitbox_info.hitbox, has_group), true);
let end_time = hitbox_info.hitbox.vel.end_time;
if end_time < result.0 { result = (end_time, false); }
let end_time = self.time + hitbox_info.hitbox.time_until_too_small(self.padding);
if end_time < result.0 { result = (end_time, false); }
hitbox_info.hitbox.vel.end_time = result.0;
if result.1 { self.events.add_solitaire_event(result.0, InternalEvent::Reiterate(id), &mut hitbox_info.event_keys); }
}
}
impl <P: HbProfile> EventKeysMap for FnvHashMap<HbId, HitboxInfo<P>> {
fn event_keys_mut(&mut self, id: HbId) -> &mut TightSet<EventKey> {
&mut self.get_mut(&id).unwrap().event_keys
}
}
struct HitboxInfo<P: HbProfile> {
profile: P,
hitbox: Hitbox,
start_time: f64,
pub_end_time: f64,
event_keys: TightSet<EventKey>,
overlaps: TightSet<HbId>
}
impl <P: HbProfile> HitboxInfo<P> {
fn new(hitbox: Hitbox, profile: P, start_time: f64) -> HitboxInfo<P> {
HitboxInfo {
profile: profile,
pub_end_time: hitbox.vel.end_time,
hitbox: hitbox,
start_time: start_time,
event_keys: TightSet::new(),
overlaps: TightSet::new()
}
}
fn hitbox_at_time(&self, time: f64) -> DurHitbox {
assert!(time >= self.start_time && time <= self.hitbox.vel.end_time, "invalid time");
let mut result = self.hitbox.clone();
result.value = result.advanced_shape(time - self.start_time);
result.to_dur_hitbox(time)
}
fn pub_hitbox_at_time(&self, time: f64) -> Hitbox {
assert!(time >= self.start_time && time <= self.pub_end_time, "invalid time");
let mut result = self.hitbox.clone();
result.vel.end_time = self.pub_end_time;
result.value = result.advanced_shape(time - self.start_time);
result
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum HbEvent {
Collide,
Separate
}
fn new_event(event: HbEvent, mut id_1: HbId, mut id_2: HbId) -> (HbEvent, HbId, HbId) {
assert!(id_1 != id_2, "ids must be different: {} {}", id_1, id_2);
if id_1 > id_2 { mem::swap(&mut id_1, &mut id_2); }
(event, id_1, id_2)
}