diff options
Diffstat (limited to 'Spear/Math/Collision.hs')
-rw-r--r-- | Spear/Math/Collision.hs | 482 |
1 files changed, 241 insertions, 241 deletions
diff --git a/Spear/Math/Collision.hs b/Spear/Math/Collision.hs index 47cc5fd..a69ea7a 100644 --- a/Spear/Math/Collision.hs +++ b/Spear/Math/Collision.hs | |||
@@ -1,242 +1,242 @@ | |||
1 | module Spear.Math.Collision | 1 | module Spear.Math.Collision |
2 | ( | 2 | ( |
3 | CollisionType(..) | 3 | CollisionType(..) |
4 | -- * 2D Collision | 4 | -- * 2D Collision |
5 | , Collisionable2(..) | 5 | , Collisionable2(..) |
6 | , Collisioner2(..) | 6 | , Collisioner2(..) |
7 | -- ** Construction | 7 | -- ** Construction |
8 | , aabb2Collisioner | 8 | , aabb2Collisioner |
9 | , circleCollisioner | 9 | , circleCollisioner |
10 | , mkCols | 10 | , mkCols |
11 | -- ** Collision test | 11 | -- ** Collision test |
12 | , collide | 12 | , collide |
13 | -- ** Manipulation | 13 | -- ** Manipulation |
14 | , move | 14 | , move |
15 | -- ** Helpers | 15 | -- ** Helpers |
16 | , buildAABB2 | 16 | , buildAABB2 |
17 | , aabb2FromCircle | 17 | , aabb2FromCircle |
18 | , circleFromAABB2 | 18 | , circleFromAABB2 |
19 | -- * 3D Collision | 19 | -- * 3D Collision |
20 | , Collisionable3(..) | 20 | , Collisionable3(..) |
21 | -- ** Helpers | 21 | -- ** Helpers |
22 | , aabb3FromSphere | 22 | , aabb3FromSphere |
23 | ) | 23 | ) |
24 | where | 24 | where |
25 | 25 | ||
26 | import Spear.Assets.Model | 26 | import Spear.Assets.Model |
27 | import Spear.Math.AABB | 27 | import Spear.Math.AABB |
28 | import Spear.Math.Circle | 28 | import Spear.Math.Circle |
29 | import qualified Spear.Math.Matrix4 as M4 | 29 | import qualified Spear.Math.Matrix4 as M4 |
30 | import Spear.Math.Plane | 30 | import Spear.Math.Plane |
31 | import Spear.Math.Sphere | 31 | import Spear.Math.Sphere |
32 | import Spear.Math.Vector | 32 | import Spear.Math.Vector |
33 | 33 | ||
34 | import Data.List (foldl') | 34 | import Data.List (foldl') |
35 | 35 | ||
36 | data CollisionType = NoCollision | Collision | FullyContains | FullyContainedBy | 36 | data CollisionType = NoCollision | Collision | FullyContains | FullyContainedBy |
37 | deriving (Eq, Show) | 37 | deriving (Eq, Show) |
38 | 38 | ||
39 | -- 2D collision | 39 | -- 2D collision |
40 | 40 | ||
41 | class Collisionable2 a where | 41 | class Collisionable2 a where |
42 | 42 | ||
43 | -- | Collide the object with an AABB. | 43 | -- | Collide the object with an AABB. |
44 | collideAABB2 :: AABB2 -> a -> CollisionType | 44 | collideAABB2 :: AABB2 -> a -> CollisionType |
45 | 45 | ||
46 | -- | Collide the object with a circle. | 46 | -- | Collide the object with a circle. |
47 | collideCircle :: Circle -> a -> CollisionType | 47 | collideCircle :: Circle -> a -> CollisionType |
48 | 48 | ||
49 | instance Collisionable2 AABB2 where | 49 | instance Collisionable2 AABB2 where |
50 | 50 | ||
51 | collideAABB2 box1@(AABB2 min1 max1) box2@(AABB2 min2 max2) | 51 | collideAABB2 box1@(AABB2 min1 max1) box2@(AABB2 min2 max2) |
52 | | (x max1) < (x min2) = NoCollision | 52 | | (x max1) < (x min2) = NoCollision |
53 | | (x min1) > (x max2) = NoCollision | 53 | | (x min1) > (x max2) = NoCollision |
54 | | (y max1) < (y min2) = NoCollision | 54 | | (y max1) < (y min2) = NoCollision |
55 | | (y min1) > (y max2) = NoCollision | 55 | | (y min1) > (y max2) = NoCollision |
56 | | box1 `aabb2pt` min2 && box1 `aabb2pt` max2 = FullyContains | 56 | | box1 `aabb2pt` min2 && box1 `aabb2pt` max2 = FullyContains |
57 | | box2 `aabb2pt` min1 && box2 `aabb2pt` max1 = FullyContainedBy | 57 | | box2 `aabb2pt` min1 && box2 `aabb2pt` max1 = FullyContainedBy |
58 | | otherwise = Collision | 58 | | otherwise = Collision |
59 | 59 | ||
60 | collideCircle circle@(Circle c r) aabb@(AABB2 min max) | 60 | collideCircle circle@(Circle c r) aabb@(AABB2 min max) |
61 | | test == FullyContains || test == FullyContainedBy = test | 61 | | test == FullyContains || test == FullyContainedBy = test |
62 | | normSq (c - boxC) > (l + r)^2 = NoCollision | 62 | | normSq (c - boxC) > (l + r)^2 = NoCollision |
63 | | otherwise = Collision | 63 | | otherwise = Collision |
64 | where | 64 | where |
65 | test = collideAABB2 aabb $ aabb2FromCircle circle | 65 | test = collideAABB2 aabb $ aabb2FromCircle circle |
66 | boxC = min + (max-min)/2 | 66 | boxC = min + (max-min)/2 |
67 | l = norm $ min + (vec2 (x boxC) (y min)) - min | 67 | l = norm $ min + (vec2 (x boxC) (y min)) - min |
68 | 68 | ||
69 | instance Collisionable2 Circle where | 69 | instance Collisionable2 Circle where |
70 | 70 | ||
71 | collideAABB2 box circle = case collideCircle circle box of | 71 | collideAABB2 box circle = case collideCircle circle box of |
72 | FullyContains -> FullyContainedBy | 72 | FullyContains -> FullyContainedBy |
73 | FullyContainedBy -> FullyContains | 73 | FullyContainedBy -> FullyContains |
74 | x -> x | 74 | x -> x |
75 | 75 | ||
76 | collideCircle s1@(Circle c1 r1) s2@(Circle c2 r2) | 76 | collideCircle s1@(Circle c1 r1) s2@(Circle c2 r2) |
77 | | distance_centers <= sub_radii = if (r1 > r2) then FullyContains else FullyContainedBy | 77 | | distance_centers <= sub_radii = if (r1 > r2) then FullyContains else FullyContainedBy |
78 | | distance_centers <= sum_radii = Collision | 78 | | distance_centers <= sum_radii = Collision |
79 | | otherwise = NoCollision | 79 | | otherwise = NoCollision |
80 | where | 80 | where |
81 | distance_centers = normSq $ c1 - c2 | 81 | distance_centers = normSq $ c1 - c2 |
82 | sum_radii = (r1 + r2)^2 | 82 | sum_radii = (r1 + r2)^2 |
83 | sub_radii = (r1 - r2)^2 | 83 | sub_radii = (r1 - r2)^2 |
84 | 84 | ||
85 | instance Collisionable2 Collisioner2 where | 85 | instance Collisionable2 Collisioner2 where |
86 | 86 | ||
87 | collideAABB2 box (AABB2Col self) = collideAABB2 box self | 87 | collideAABB2 box (AABB2Col self) = collideAABB2 box self |
88 | collideAABB2 box (CircleCol self) = collideAABB2 box self | 88 | collideAABB2 box (CircleCol self) = collideAABB2 box self |
89 | 89 | ||
90 | collideCircle circle (AABB2Col self) = collideCircle circle self | 90 | collideCircle circle (AABB2Col self) = collideCircle circle self |
91 | collideCircle circle (CircleCol self) = collideCircle circle self | 91 | collideCircle circle (CircleCol self) = collideCircle circle self |
92 | 92 | ||
93 | aabbPoints :: AABB2 -> [Vector2] | 93 | aabbPoints :: AABB2 -> [Vector2] |
94 | aabbPoints (AABB2 min max) = [p1,p2,p3,p4,p5,p6,p7,p8] | 94 | aabbPoints (AABB2 min max) = [p1,p2,p3,p4,p5,p6,p7,p8] |
95 | where | 95 | where |
96 | p1 = vec2 (x min) (y min) | 96 | p1 = vec2 (x min) (y min) |
97 | p2 = vec2 (x min) (y min) | 97 | p2 = vec2 (x min) (y min) |
98 | p3 = vec2 (x min) (y max) | 98 | p3 = vec2 (x min) (y max) |
99 | p4 = vec2 (x min) (y max) | 99 | p4 = vec2 (x min) (y max) |
100 | p5 = vec2 (x max) (y min) | 100 | p5 = vec2 (x max) (y min) |
101 | p6 = vec2 (x max) (y min) | 101 | p6 = vec2 (x max) (y min) |
102 | p7 = vec2 (x max) (y max) | 102 | p7 = vec2 (x max) (y max) |
103 | p8 = vec2 (x max) (y max) | 103 | p8 = vec2 (x max) (y max) |
104 | 104 | ||
105 | 105 | ||
106 | -- | A collisioner component. | 106 | -- | A collisioner component. |
107 | data Collisioner2 | 107 | data Collisioner2 |
108 | -- | An axis-aligned bounding box. | 108 | -- | An axis-aligned bounding box. |
109 | = AABB2Col {-# UNPACK #-} !AABB2 | 109 | = AABB2Col {-# UNPACK #-} !AABB2 |
110 | -- | A bounding circle. | 110 | -- | A bounding circle. |
111 | | CircleCol {-# UNPACK #-} !Circle | 111 | | CircleCol {-# UNPACK #-} !Circle |
112 | 112 | ||
113 | 113 | ||
114 | -- | Create a collisioner from the specified box. | 114 | -- | Create a collisioner from the specified box. |
115 | aabb2Collisioner :: AABB2 -> Collisioner2 | 115 | aabb2Collisioner :: AABB2 -> Collisioner2 |
116 | aabb2Collisioner = AABB2Col | 116 | aabb2Collisioner = AABB2Col |
117 | 117 | ||
118 | -- | Create a collisioner from the specified circle. | 118 | -- | Create a collisioner from the specified circle. |
119 | circleCollisioner :: Circle -> Collisioner2 | 119 | circleCollisioner :: Circle -> Collisioner2 |
120 | circleCollisioner = CircleCol | 120 | circleCollisioner = CircleCol |
121 | 121 | ||
122 | -- | Compute AABB collisioners in view space from the given AABB. | 122 | -- | Compute AABB collisioners in view space from the given AABB. |
123 | mkCols :: M4.Matrix4 -- ^ Modelview matrix | 123 | mkCols :: M4.Matrix4 -- ^ Modelview matrix |
124 | -> Box | 124 | -> Box |
125 | -> [Collisioner2] | 125 | -> [Collisioner2] |
126 | mkCols modelview (Box (Vec3 xmin ymin zmin) (Vec3 xmax ymax zmax)) = | 126 | mkCols modelview (Box (Vec3 xmin ymin zmin) (Vec3 xmax ymax zmax)) = |
127 | let | 127 | let |
128 | toVec2 v = vec2 (x v) (y v) | 128 | toVec2 v = vec2 (x v) (y v) |
129 | p1 = toVec2 $ modelview `M4.mulp` vec3 xmin ymin zmax | 129 | p1 = toVec2 $ modelview `M4.mulp` vec3 xmin ymin zmax |
130 | p2 = toVec2 $ modelview `M4.mulp` vec3 xmax ymin zmin | 130 | p2 = toVec2 $ modelview `M4.mulp` vec3 xmax ymin zmin |
131 | p3 = toVec2 $ modelview `M4.mulp` vec3 xmax ymax zmin | 131 | p3 = toVec2 $ modelview `M4.mulp` vec3 xmax ymax zmin |
132 | col1 = AABB2Col $ AABB2 p1 p2 | 132 | col1 = AABB2Col $ AABB2 p1 p2 |
133 | col2 = AABB2Col $ AABB2 p1 p3 | 133 | col2 = AABB2Col $ AABB2 p1 p3 |
134 | in | 134 | in |
135 | [col1, col2] | 135 | [col1, col2] |
136 | 136 | ||
137 | -- | Create the minimal AABB fully containing the specified collisioners. | 137 | -- | Create the minimal AABB fully containing the specified collisioners. |
138 | buildAABB2 :: [Collisioner2] -> AABB2 | 138 | buildAABB2 :: [Collisioner2] -> AABB2 |
139 | buildAABB2 cols = aabb2 $ generatePoints cols | 139 | buildAABB2 cols = aabb2 $ generatePoints cols |
140 | 140 | ||
141 | -- | Create the minimal box fully containing the specified circle. | 141 | -- | Create the minimal box fully containing the specified circle. |
142 | aabb2FromCircle :: Circle -> AABB2 | 142 | aabb2FromCircle :: Circle -> AABB2 |
143 | aabb2FromCircle (Circle c r) = AABB2 bot top | 143 | aabb2FromCircle (Circle c r) = AABB2 bot top |
144 | where | 144 | where |
145 | bot = c - (vec2 r r) | 145 | bot = c - (vec2 r r) |
146 | top = c + (vec2 r r) | 146 | top = c + (vec2 r r) |
147 | 147 | ||
148 | -- | Create the minimal circle fully containing the specified box. | 148 | -- | Create the minimal circle fully containing the specified box. |
149 | circleFromAABB2 :: AABB2 -> Circle | 149 | circleFromAABB2 :: AABB2 -> Circle |
150 | circleFromAABB2 (AABB2 min max) = Circle c r | 150 | circleFromAABB2 (AABB2 min max) = Circle c r |
151 | where | 151 | where |
152 | c = scale 0.5 (min + max) | 152 | c = scale 0.5 (min + max) |
153 | r = norm . scale 0.5 $ max - min | 153 | r = norm . scale 0.5 $ max - min |
154 | 154 | ||
155 | generatePoints :: [Collisioner2] -> [Vector2] | 155 | generatePoints :: [Collisioner2] -> [Vector2] |
156 | generatePoints = foldl' generate [] | 156 | generatePoints = foldl' generate [] |
157 | where | 157 | where |
158 | generate acc (AABB2Col (AABB2 pmin pmax)) = p1:p2:p3:p4:p5:p6:p7:p8:acc | 158 | generate acc (AABB2Col (AABB2 pmin pmax)) = p1:p2:p3:p4:p5:p6:p7:p8:acc |
159 | where | 159 | where |
160 | p1 = vec2 (x pmin) (y pmin) | 160 | p1 = vec2 (x pmin) (y pmin) |
161 | p2 = vec2 (x pmin) (y pmin) | 161 | p2 = vec2 (x pmin) (y pmin) |
162 | p3 = vec2 (x pmin) (y pmax) | 162 | p3 = vec2 (x pmin) (y pmax) |
163 | p4 = vec2 (x pmin) (y pmax) | 163 | p4 = vec2 (x pmin) (y pmax) |
164 | p5 = vec2 (x pmax) (y pmin) | 164 | p5 = vec2 (x pmax) (y pmin) |
165 | p6 = vec2 (x pmax) (y pmin) | 165 | p6 = vec2 (x pmax) (y pmin) |
166 | p7 = vec2 (x pmax) (y pmax) | 166 | p7 = vec2 (x pmax) (y pmax) |
167 | p8 = vec2 (x pmax) (y pmax) | 167 | p8 = vec2 (x pmax) (y pmax) |
168 | 168 | ||
169 | generate acc (CircleCol (Circle c r)) = p1:p2:p3:p4:acc | 169 | generate acc (CircleCol (Circle c r)) = p1:p2:p3:p4:acc |
170 | where | 170 | where |
171 | p1 = c + unitx2 * (vec2 r r) | 171 | p1 = c + unitx2 * (vec2 r r) |
172 | p2 = c - unitx2 * (vec2 r r) | 172 | p2 = c - unitx2 * (vec2 r r) |
173 | p3 = c + unity2 * (vec2 r r) | 173 | p3 = c + unity2 * (vec2 r r) |
174 | p4 = c - unity2 * (vec2 r r) | 174 | p4 = c - unity2 * (vec2 r r) |
175 | 175 | ||
176 | -- | Collide the given collisioners. | 176 | -- | Collide the given collisioners. |
177 | collide :: Collisioner2 -> Collisioner2 -> CollisionType | 177 | collide :: Collisioner2 -> Collisioner2 -> CollisionType |
178 | collide (AABB2Col box1) (AABB2Col box2) = collideAABB2 box1 box2 | 178 | collide (AABB2Col box1) (AABB2Col box2) = collideAABB2 box1 box2 |
179 | collide (AABB2Col box) (CircleCol circle) = collideAABB2 box circle | 179 | collide (AABB2Col box) (CircleCol circle) = collideAABB2 box circle |
180 | collide (CircleCol s1) (CircleCol s2) = collideCircle s1 s2 | 180 | collide (CircleCol s1) (CircleCol s2) = collideCircle s1 s2 |
181 | collide (CircleCol circle) (AABB2Col box) = collideCircle circle box | 181 | collide (CircleCol circle) (AABB2Col box) = collideCircle circle box |
182 | 182 | ||
183 | -- | Move the collisioner. | 183 | -- | Move the collisioner. |
184 | move :: Vector2 -> Collisioner2 -> Collisioner2 | 184 | move :: Vector2 -> Collisioner2 -> Collisioner2 |
185 | move v (AABB2Col (AABB2 min max)) = AABB2Col (AABB2 (min+v) (max+v)) | 185 | move v (AABB2Col (AABB2 min max)) = AABB2Col (AABB2 (min+v) (max+v)) |
186 | move v (CircleCol (Circle c r)) = CircleCol (Circle (c+v) r) | 186 | move v (CircleCol (Circle c r)) = CircleCol (Circle (c+v) r) |
187 | 187 | ||
188 | 188 | ||
189 | -- 3D collision | 189 | -- 3D collision |
190 | 190 | ||
191 | class Collisionable3 a where | 191 | class Collisionable3 a where |
192 | 192 | ||
193 | -- | Collide the object with an AABB. | 193 | -- | Collide the object with an AABB. |
194 | collideAABB3 :: AABB3 -> a -> CollisionType | 194 | collideAABB3 :: AABB3 -> a -> CollisionType |
195 | 195 | ||
196 | -- | Collide the object with a sphere. | 196 | -- | Collide the object with a sphere. |
197 | collideSphere :: Sphere -> a -> CollisionType | 197 | collideSphere :: Sphere -> a -> CollisionType |
198 | 198 | ||
199 | instance Collisionable3 AABB3 where | 199 | instance Collisionable3 AABB3 where |
200 | 200 | ||
201 | collideAABB3 box1@(AABB3 min1 max1) box2@(AABB3 min2 max2) | 201 | collideAABB3 box1@(AABB3 min1 max1) box2@(AABB3 min2 max2) |
202 | | (x max1) < (x min2) = NoCollision | 202 | | (x max1) < (x min2) = NoCollision |
203 | | (x min1) > (x max2) = NoCollision | 203 | | (x min1) > (x max2) = NoCollision |
204 | | (y max1) < (y min2) = NoCollision | 204 | | (y max1) < (y min2) = NoCollision |
205 | | (y min1) > (y max2) = NoCollision | 205 | | (y min1) > (y max2) = NoCollision |
206 | | box1 `aabb3pt` min2 && box1 `aabb3pt` max2 = FullyContains | 206 | | box1 `aabb3pt` min2 && box1 `aabb3pt` max2 = FullyContains |
207 | | box2 `aabb3pt` min1 && box2 `aabb3pt` max1 = FullyContainedBy | 207 | | box2 `aabb3pt` min1 && box2 `aabb3pt` max1 = FullyContainedBy |
208 | | otherwise = Collision | 208 | | otherwise = Collision |
209 | 209 | ||
210 | collideSphere sphere@(Sphere c r) aabb@(AABB3 min max) | 210 | collideSphere sphere@(Sphere c r) aabb@(AABB3 min max) |
211 | | test == FullyContains || test == FullyContainedBy = test | 211 | | test == FullyContains || test == FullyContainedBy = test |
212 | | normSq (c - boxC) > (l + r)^2 = NoCollision | 212 | | normSq (c - boxC) > (l + r)^2 = NoCollision |
213 | | otherwise = Collision | 213 | | otherwise = Collision |
214 | where | 214 | where |
215 | test = collideAABB3 aabb $ aabb3FromSphere sphere | 215 | test = collideAABB3 aabb $ aabb3FromSphere sphere |
216 | boxC = min + v | 216 | boxC = min + v |
217 | l = norm v | 217 | l = norm v |
218 | v = (max-min)/2 | 218 | v = (max-min)/2 |
219 | 219 | ||
220 | instance Collisionable3 Sphere where | 220 | instance Collisionable3 Sphere where |
221 | 221 | ||
222 | collideAABB3 box sphere = case collideSphere sphere box of | 222 | collideAABB3 box sphere = case collideSphere sphere box of |
223 | FullyContains -> FullyContainedBy | 223 | FullyContains -> FullyContainedBy |
224 | FullyContainedBy -> FullyContains | 224 | FullyContainedBy -> FullyContains |
225 | x -> x | 225 | x -> x |
226 | 226 | ||
227 | collideSphere s1@(Sphere c1 r1) s2@(Sphere c2 r2) | 227 | collideSphere s1@(Sphere c1 r1) s2@(Sphere c2 r2) |
228 | | distance_centers <= sub_radii = | 228 | | distance_centers <= sub_radii = |
229 | if (r1 > r2) then FullyContains else FullyContainedBy | 229 | if (r1 > r2) then FullyContains else FullyContainedBy |
230 | | distance_centers <= sum_radii = Collision | 230 | | distance_centers <= sum_radii = Collision |
231 | | otherwise = NoCollision | 231 | | otherwise = NoCollision |
232 | where | 232 | where |
233 | distance_centers = normSq $ c1 - c2 | 233 | distance_centers = normSq $ c1 - c2 |
234 | sum_radii = (r1 + r2)^2 | 234 | sum_radii = (r1 + r2)^2 |
235 | sub_radii = (r1 - r2)^2 | 235 | sub_radii = (r1 - r2)^2 |
236 | 236 | ||
237 | -- | Create the minimal box fully containing the specified sphere. | 237 | -- | Create the minimal box fully containing the specified sphere. |
238 | aabb3FromSphere :: Sphere -> AABB3 | 238 | aabb3FromSphere :: Sphere -> AABB3 |
239 | aabb3FromSphere (Sphere c r) = AABB3 bot top | 239 | aabb3FromSphere (Sphere c r) = AABB3 bot top |
240 | where | 240 | where |
241 | bot = c - (vec3 r r r) | 241 | bot = c - (vec3 r r r) |
242 | top = c + (vec3 r r r) \ No newline at end of file | 242 | top = c + (vec3 r r r) \ No newline at end of file |