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