-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhail-storm.js
157 lines (133 loc) · 4.55 KB
/
hail-storm.js
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
152
153
154
155
156
157
'use strict'
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const width = canvas.width = document.getElementById("main").clientWidth
const height = canvas.height = window.innerHeight * 8 / 10
let stones = []
const iAccCoef = 5e5
let accCoef = iAccCoef
let groundBounce = 0.3
function random(min, max) {
const num = Math.floor(Math.random() * (max - min + 1)) + min
return num
}
// from https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional_collision_with_two_moving_objects
function afterCollision(v1, v2, m1, m2, theta1, theta2, psy) {
let a1 = (v1*Math.cos(theta1-psy)*(m1-m2) + 2*m2*v2*Math.cos(theta2-psy))/(m1+m2)
let b1 = v1*Math.sin(theta1-psy)
let v1x = a1*Math.cos(psy) + b1*Math.cos(psy+Math.PI/2)
let v1y = a1*Math.sin(psy) + b1*Math.sin(psy+Math.PI/2)
let a2 = (v2*Math.cos(theta2-psy)*(m2-m1) + 2*m1*v1*Math.cos(theta1-psy))/(m1+m2)
let b2 = v2*Math.sin(theta2-psy)
let v2x = a2*Math.cos(psy) + b2*Math.cos(psy+Math.PI/2)
let v2y = a2*Math.sin(psy) + b2*Math.sin(psy+Math.PI/2)
return [v1x,v1y,v2x,v2y]
}
function angle(x1,y1,x2,y2) {
//let [[ex1,ey1],[ex2,ey2]] = (x1<x2 ? [[x1,y1],[x2,y2]] : [[x2,y2],[x1,y1]])
let w = x2-x1
let h = y2-y1
let r = (w == 0 ? Math.sign(h) * Math.PI/2 :
Math.atan(h/w) + (w >= 0 ? 0 : Math.PI))
return r
}
function toPol(x,y) {
let d = Math.sqrt(x*x+y*y)
let a = angle(0,0,x,y)
return [d,a]
}
function distance(x1, y1, x2, y2) {
return Math.pow(x1-x2,2) + Math.pow(y1-y2,2)
}
class Hailstone {
constructor(x,y,color,size, xSpeed,ySpeed) {
this.x = x
this.y = y
this.color = color
this.size = size
this.xSpeed = xSpeed
this.ySpeed = ySpeed
}
yAcc() {
return Math.pow(this.size,3) / accCoef
}
draw() {
ctx.beginPath();
ctx.fillStyle = this.color
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI)
ctx.fill()
}
clone() {
return new Hailstone(this.x, this.y, this.color, this.size, this.xSpeed, this.ySpeed)
}
update() {
// Find intersectincting ball
// map(e => e) is necessary to create a copy of the 'stones' array as sort modifies it
let sorted = stones.map(e => e).sort((a,b)=> distance(a.x,a.y,this.x,this.y)-distance(this.x,this.y,b.x,b.y))
let closest = sorted.slice(1,2)[0]
let r = this.clone()
if (closest && distance(this.x, this.y,closest.x,closest.y)<(Math.pow(this.size+closest.size,2))) {
let psy = angle(this.x, this.y, closest.x, closest.y)
let [v1, theta1] = toPol(this.xSpeed, this.ySpeed)
let [v2, theta2] = toPol(closest.xSpeed, closest.ySpeed)
let [v1x,v1y,v2x,v2y] = afterCollision(v1,v2, Math.pow(this.size,3), Math.pow(closest.size,3),theta1,theta2,psy)
r.xSpeed = v1x
r.ySpeed = v1y
}
// Next step
let nextYSpeed = r.ySpeed + r.yAcc()
let nextY = r.y + r.ySpeed
// Check for bounce on ground
if (nextY > height - this.size) {
nextY = height-this.size
nextYSpeed = - this.ySpeed*groundBounce
}
r.y = nextY
r.ySpeed = nextYSpeed
// Check if ball is sliding on the floor, then reduce speed
if (nextY == height-this.size && Math.abs(nextYSpeed) < 0.01) {
r.xSpeed = r.xSpeed / 2
}
let nextX = r.x + r.xSpeed
// Check for sides
r.x = (nextX < 0 ? nextX + width : (nextX>=width ? nextX-width : nextX))
return r
}
}
function newHailstone() {
let size = random(10,20)
let x = random(size, width-size)
let y = size
let color = random(15,255)
let hs = new Hailstone(x,y,`rgba(${color},0,${color},1)`, size, 0, 0)
hs.ySpeed = width * hs.yAcc()
hs.draw()
stones.push(hs)
}
function reset() {
// interesting way to clear the array !
stones.length = 0
}
function loop() {
// Background to animation
ctx.fillStyle = 'rgba(255,255,255,1)'
ctx.fillRect(0,0,width,height)
ctx.moveTo(0, height)
ctx.lineTo(width, height)
ctx.stroke()
let g = document.getElementById('gravity')
if (g) {
accCoef = iAccCoef / parseFloat(g.value)
}
let gb = document.getElementById('groundBounce')
if (gb) {
groundBounce = parseFloat(gb.value)
}
for(let i = 0; i < stones.length; i++) {
stones[i].draw()
}
let stones2 = stones.map(s => s.update())
stones = stones2
requestAnimationFrame(loop);
}
loop();