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 }
    }
}