aboutsummaryrefslogtreecommitdiff
path: root/Spear/Math/Collision.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Spear/Math/Collision.hs')
-rw-r--r--Spear/Math/Collision.hs242
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 @@
1module 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)
24where
25
26import Spear.Assets.Model
27import Spear.Math.AABB
28import Spear.Math.Circle
29import qualified Spear.Math.Matrix4 as M4
30import Spear.Math.Plane
31import Spear.Math.Sphere
32import Spear.Math.Vector
33
34import Data.List (foldl')
35
36data CollisionType = NoCollision | Collision | FullyContains | FullyContainedBy
37 deriving (Eq, Show)
38
39-- 2D collision
40
41class 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
49instance 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
69instance 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
85instance 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
93aabbPoints :: AABB2 -> [Vector2]
94aabbPoints (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.
107data 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.
115aabb2Collisioner :: AABB2 -> Collisioner2
116aabb2Collisioner = AABB2Col
117
118-- | Create a collisioner from the specified circle.
119circleCollisioner :: Circle -> Collisioner2
120circleCollisioner = CircleCol
121
122-- | Compute AABB collisioners in view space from the given AABB.
123mkCols :: M4.Matrix4 -- ^ Modelview matrix
124 -> Box
125 -> [Collisioner2]
126mkCols 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.
138buildAABB2 :: [Collisioner2] -> AABB2
139buildAABB2 cols = aabb2 $ generatePoints cols
140
141-- | Create the minimal box fully containing the specified circle.
142aabb2FromCircle :: Circle -> AABB2
143aabb2FromCircle (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.
149circleFromAABB2 :: AABB2 -> Circle
150circleFromAABB2 (AABB2 min max) = Circle c r
151 where
152 c = scale 0.5 (min + max)
153 r = norm . scale 0.5 $ max - min
154
155generatePoints :: [Collisioner2] -> [Vector2]
156generatePoints = 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.
177collide :: Collisioner2 -> Collisioner2 -> CollisionType
178collide (AABB2Col box1) (AABB2Col box2) = collideAABB2 box1 box2
179collide (AABB2Col box) (CircleCol circle) = collideAABB2 box circle
180collide (CircleCol s1) (CircleCol s2) = collideCircle s1 s2
181collide (CircleCol circle) (AABB2Col box) = collideCircle circle box
182
183-- | Move the collisioner.
184move :: Vector2 -> Collisioner2 -> Collisioner2
185move v (AABB2Col (AABB2 min max)) = AABB2Col (AABB2 (min+v) (max+v))
186move v (CircleCol (Circle c r)) = CircleCol (Circle (c+v) r)
187
188
189-- 3D collision
190
191class 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
199instance 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
220instance 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.
238aabb3FromSphere :: Sphere -> AABB3
239aabb3FromSphere (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