1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
// Copyright 2016 Matthew D. Michelotti // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. mod grid; mod collider; mod events; mod dur_hitbox; pub use self::collider::*; use geom::*; use float::*; use self::dur_hitbox::DurHitbox; const HIGH_TIME: f64 = 1e50; /// Type used as a handle for referencing hitboxes in a `Collider` instance. pub type HitboxId = u64; /// Represents a moving shape for continuous collision testing. #[derive(PartialEq, Clone, Debug)] pub struct Hitbox { /// The placed shape `shape` at the given point in time. /// /// The width and height of the shape must be greater than `padding` (in the `Collider` constructor) /// at all times. pub shape: PlacedShape, /// A velocity that describes how the shape is changing over time. /// /// The `vel` may include the velocity of the width and height of the `shape` as /// well as the velocity of the position. /// /// Since the width and height of the shape is greater than `padding` at all times, /// if a shape velocity is set that decreases the dimensions of the shape over time, /// then the user is responsible for ensuring that the shape will not decrease below this threshold. /// Collider will catch such mistakes in unoptimized builds. pub vel: PlacedShape, /// An upper-bound on the time at which the hitbox will be updated by the user. /// /// This is an advanced feature for efficiency and does not impact the results. /// Infinity is used as the default, but using a lower value may improve performance /// /// Collider will panic if the end time is exceeded without update, /// at least in unoptimized builds. It is ultimately the user's responsibility /// to ensure that end times are not exceeded. pub end_time: N64 } //TODO invoke hitbox.validate() in more places so that inconsistencies are still found in optimized builds, just found later #[cfg(feature = "noisy-floats")] impl Eq for Hitbox {} impl Hitbox { /// Constructs a new hitbox with the given `shape` and a `vel` of zero and `duration` of infinity. pub fn new(shape: PlacedShape) -> Hitbox { Hitbox { shape : shape, vel : PlacedShape::new(Vec2::zero(), Shape::new(shape.kind(), Vec2::zero())), end_time : N64::infinity() } } fn advanced_shape(&self, time: N64) -> PlacedShape { assert!(time < HIGH_TIME, "requires time < {}", HIGH_TIME); self.shape + self.vel * r64(time.raw()) } fn validate(&self, min_size: R64, present_time: N64) { assert!(!self.end_time.is_nan() && self.end_time >= present_time, "end time must exceed present time"); assert!(self.shape.kind() == self.vel.kind(), "shape and vel have different kinds"); assert!(self.shape.dims().x >= min_size && self.shape.dims().y >= min_size, "shape width/height must be at least {}", min_size); } fn time_until_too_small(&self, min_size: R64) -> N64 { let min_size = min_size * 0.9; assert!(self.shape.dims().x > min_size && self.shape.dims().y > min_size); let mut time = N64::infinity(); if self.vel.dims().x < 0.0 { time = time.min(N64::from(min_size - self.shape.dims().x) / N64::from(self.vel.dims().x)); } if self.vel.dims().y < 0.0 { time = time.min(N64::from(min_size - self.shape.dims().y) / N64::from(self.vel.dims().y)); } time } fn to_dur_hitbox(&self, time: N64) -> DurHitbox { assert!(time <= self.end_time); DurHitbox { shape: self.shape, vel: self.vel, duration: self.end_time - time } } } /// Contains types that describe interactions between hitboxes. pub mod inter { /// A group id that may be used as a first measure to efficiently filter out hitboxes that don't interact. /// /// The total number of groups used should in general be very small. /// Often 1 is enough, and 4 is excessive. /// As an example, in a [danmaku](https://en.wikipedia.org/wiki/Shoot_%27em_up#Bullet_hell_and_niche_appeal) game /// (which has many bullets on screen that do not interact with each other), /// we may use one group for bullets and one group for everything else, /// to avoid the quadratic cost of comparing all nearby bullets with each other. pub type Group = u32; static DEFAULT_GROUPS: [Group; 1] = [0]; /// Used to determine which pairs of hitboxes should be checked for collisions /// and which pairs should be ignored. pub trait Interactivity { /// Returns the group id associated with the hitbox. /// Default is `Some(0)`. /// /// If `None` is returned, then no collisions will be reported /// for this hitbox at all. fn group(&self) -> Option<Group> { Some(0) } /// Returns a list of groups that this hitbox can interact with. /// Using large lists of groups may be inefficient. /// Default is `[0]`. fn interact_groups(&self) -> &'static [Group] { &DEFAULT_GROUPS } /// Returns true if the pair of hitboxes should be checked for collisions. /// This method should be commutative. /// This method should be consistent with `group` and `interact_groups`, /// although possibly more restrictive. fn can_interact(&self, other: &Self) -> bool; } /// The default implementation of `Interactivity`, in which /// every hitbox is allowed to interact with every other hitbox. #[derive(Default)] pub struct DefaultInteractivity; impl Interactivity for DefaultInteractivity { fn can_interact(&self, _other: &Self) -> bool { true } } }