-
Notifications
You must be signed in to change notification settings - Fork 0
/
BubblePhysics.elm
122 lines (96 loc) · 3.87 KB
/
BubblePhysics.elm
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
-- based roughly on http://gamedevelopment.tutsplus.com/tutorials/gamedev-6331
module BubblePhysics where
-- plain old pair for coordinates, vectors
type Vec2 = (Float,Float)
type Body b = { b |
velocity: Vec2, -- direction and speed
inverseMass: Float, -- we usually use only inverse mass for calculations
restitution: Float -- bounciness factor
}
-- it's all about the bubble
type Bubble = Body {
radius: Float, pos: Vec2 -- center position
}
type Box = Body {
min: Vec2, max: Vec2
}
-- basic bubble with some defaults
makeBubble radius pos velocity =
makeBubble2 radius pos velocity 1 1
makeBubble2 radius pos velocity density restitution =
{ radius = radius, pos = pos, velocity = velocity,
inverseMass = 1/(pi*radius*radius*density),
restitution = restitution }
-- just vector things
plus: Vec2 -> Vec2 -> Vec2
plus (x0,y0) (x1,y1) = (x0+x1,y0+y1)
minus: Vec2 -> Vec2 -> Vec2
minus (x0,y0) (x1,y1) = (x0-x1,y0-y1)
dot: Vec2 -> Vec2 -> Float
dot (x0,y0) (x1,y1) = x0*x1 + y0*y1
div2: Vec2 -> Float -> Vec2
div2 (x,y) a = (x/a, y/a)
mul2: Vec2 -> Float -> Vec2
mul2 (x,y) a = (x*a, y*a)
-- squared norm/length of ector
lenSq: Vec2 -> Float
lenSq (x,y) = x*x + y*y
-- collision calculation for different types of bodies
type CollisionResult = { normal: Vec2, penetration: Float }
-- calculate collision normal, penetration depth of a collision among bubbles
-- simple optimization: doesn't compute sqrt unless necessary
collision: Bubble -> Bubble -> CollisionResult
collision b0 b1 =
let
b0b1 = minus b1.pos b0.pos
radiusb0b1 = b0.radius+b1.radius
distanceSq = lenSq b0b1
in
if | distanceSq == 0 -> { normal = (1,0), penetration = b0.radius } -- same position, arbitrary normal
| distanceSq >= radiusb0b1*radiusb0b1 -> { normal = (1,0), penetration = 0 } -- no intersection, arbitrary normal
| otherwise ->
let d = sqrt distanceSq
in { normal = div2 b0b1 d, penetration = radiusb0b1 - d }
-- modify bodies' trajectories when they collide
resolveCollision: CollisionResult -> Body a -> Body a -> (Body a, Body a)
resolveCollision {normal,penetration} b0 b1 =
let
relativeVelocity = minus b1.velocity b0.velocity
velocityAlongNormal = dot relativeVelocity normal
in
if penetration == 0 || velocityAlongNormal > 0 then (b0,b1) -- no collision or velocities separating
else let
restitution = min b0.restitution b1.restitution -- collision restitution
invMassSum = (b0.inverseMass + b1.inverseMass)
j = (-(1 + restitution) * velocityAlongNormal) / invMassSum -- impulse scalar
impulse = mul2 normal j
in ({ b0 | velocity <- minus b0.velocity (mul2 impulse b0.inverseMass) },
{ b1 | velocity <- plus b1.velocity (mul2 impulse b1.inverseMass) })
-- collide a0 with all the bubbles, modifying b along the way.
-- return (updated a0, [updated bubbles])
collideWith: Bubble -> [Bubble] -> [Bubble] -> [Bubble]
collideWith a0 bubbles acc = case bubbles of
[] -> a0 :: acc
(b0 :: bs) ->
let collisionResult = collision a0 b0
(a1,b1) = resolveCollision collisionResult a0 b0
in collideWith a1 bs (b1 :: acc)
-- recursive collision resolution
collide: [Bubble] -> [Bubble] -> [Bubble]
collide acc bubbles =
case bubbles of
[] -> acc
h::t ->
let (h1 :: t1) = collideWith h t []
in collide (h1::acc) t1
-- apply force to a bubble
applyForce force bubble = { bubble | velocity <- plus force bubble.velocity}
-- update bubble location by applying their velocity
update bubble = { bubble | pos <- plus bubble.pos bubble.velocity }
-- applies accellerating force, does movement and resolves collisions for all the bubbles
step: Vec2 -> [Bubble] -> [Bubble]
step force bubbles =
let bubblesWithForce = map (applyForce force) bubbles
in map update (collide [] bubblesWithForce)
-- resolve all collisions; optimization: broad phase
-- TODO apply forces