-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.js
More file actions
233 lines (206 loc) · 6.81 KB
/
Copy pathmain.js
File metadata and controls
233 lines (206 loc) · 6.81 KB
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
const canvas=document.getElementById("canvas");//get the canvas element
const context=canvas.getContext("2d");//get the canvas drawing context so that we can draw
let form=document.getElementById("main_form");
let radiusInput=document.getElementById("radius_input");
let massInput=document.getElementById("mass_input");
let ballSection=document.getElementById("ball_section");
let wallSection=document.getElementById("wall_section");
let selection=document.getElementById("selection");
form.addEventListener("input",(event)=>{
if(selection.value==="Ball"){
ballSection.style.display="block";
}else{
ballSection.style.display="none";
}
if(selection.value==="Wall"){
wallSection.style.display="block";
}else{
wallSection.style.display="none";
}
})
const balls=[];
const walls=[];
let LEFT,RIGHT,UP,DOWN;
let friction=0.05;
let elasticity=1;
/**
* Finds the closest point of the wall relative to the ball
* @param {Ball} b1 The ball
* @param {Wall} w1 The wall
* @returns The closest point of the wall to the ball
*/
function closestPoint(b1,w1){
let ballToWallStart=w1.start.sub(b1.position);
if(Vector.dot(w1.wallUnit(),ballToWallStart)>0){
return w1.start;
}
let wallEndToBall=b1.position.sub(w1.end);
if(Vector.dot(w1.wallUnit(),wallEndToBall)>0){
return w1.end;
}
let closestDistance=Vector.dot(w1.wallUnit(),ballToWallStart);
let closestVector=w1.wallUnit().mult(closestDistance);
return w1.start.sub(closestVector);
}
/**
* Checks if a ball and a wall are colliding
* @param {Ball} b The ball to check
* @param {Wall} w The wall to check
* @returns Boolean
*/
function areBallAndWallColliding(b,w){
let ballToClosest=closestPoint(b,w).sub(b.position);
if(ballToClosest.mag()<=b.radius){
return true;
}
return false
}
/**
* Resolves a collision between a ball and a wall,
* giving the ball a new velocity.
* @param {Ball} b The ball to resolve
* @param {Wall} w The wall to resolve
*/
function resolveCollisionForBallAndWall(b,w){
let normal=b.position.sub(closestPoint(b,w)).unit();
let separatingVelocity=Vector.dot(b.velocity,normal);
let new_separatingVelocity=-separatingVelocity*elasticity;
let vseparatingDifference=separatingVelocity-new_separatingVelocity;
b.velocity=b.velocity.add(normal.mult(-vseparatingDifference));
}
/**
* Moves the ball so it is not penetrating the wall
* @param {Ball} b The ball to move
* @param {Wall} w The wall to check
*/
function resolvePenetrationForBallAndWall(b,w){
let penetrationVector=b.position.sub(closestPoint(b,w));
b.position=b.position.add(penetrationVector.unit().mult(b.radius-penetrationVector.mag()));
}
/**
* `Checks if two balls are colliding
* @param {Ball} b1 ball 1
* @param {Ball} b2 ball 2
* @returns Boolean
*/
function are2BallsColliding(b1,b2){
if(b1.radius+b2.radius>=b2.position.sub(b1.position).mag()){
//if the distance between the balls is smaller than the sum of the radii, they are colliding
return true;
}
return false;
}
/**
* Moves the two balls out of each other
* @param {Ball} b1 ball 1
* @param {Ball} b2 ball 2
*/
function resolvePenetrationFor2Balls(b1,b2){
let distance=b1.position.sub(b2.position);
let penetration_depth=b1.radius+b2.radius-distance.mag();
let penetration_resolution=distance.unit().mult(penetration_depth/2);
b1.position=b1.position.add(penetration_resolution);
b2.position=b2.position.add(penetration_resolution.mult(-1));
}
/**
* Resolves a collision between two balls, giving each
* of them new velocities
* @param {Ball} b1 ball 1
* @param {Ball} b2 ball 2
*/
function resolveCollisionFor2Balls(b1,b2){
let normal=b1.position.sub(b2.position).unit();
let relativeVelocity=b1.velocity.sub(b2.velocity);
let separatingVelocity=Vector.dot(relativeVelocity,normal);
let new_separatingVelocity=-separatingVelocity*elasticity;
let vsep_diff=new_separatingVelocity-separatingVelocity;
let impulse=vsep_diff/(b1.inverseMass+b2.inverseMass);
let impulseVector=normal.mult(impulse);
b1.velocity=b1.velocity.add(impulseVector.mult(b1.inverseMass));
b2.velocity=b2.velocity.add(impulseVector.mult(-b2.inverseMass));
}
/**
* Generates the rotation matrix for a given angle
* @param {number} angle The angle to rotate to
* @returns The rotation matrix
*/
function rotationMatrix(angle){
/*
The rotation matrix is a 2x2 matrix that looks like this:
[cos(angle),-sin(angle)]
[sin(angle),cos(angle)]
It is used to rotate vectors
*/
let matrix=new Matrix(2,2);
matrix.data[0][0]=Math.cos(angle);
matrix.data[0][1]=-Math.sin(angle);
matrix.data[1][0]=Math.sin(angle);
matrix.data[1][1]=Math.cos(angle);
return matrix;
}
window.addEventListener("keydown",function(event){//listen for keys being pressed
event.preventDefault();//prevent the browser from scrolling
//set the direction variables based on keypresses
if(event.keyCode===37)LEFT=true;
if(event.keyCode===38)UP=true;
if(event.keyCode===39)RIGHT=true;
if(event.keyCode===40)DOWN=true;
if(event.keyCode===65)LEFT=true;
if(event.keyCode===68)RIGHT=true;
if(event.keyCode===87)UP=true;
if(event.keyCode===83)DOWN=true;
})
window.addEventListener("keyup",function(event){//listen for keys being released
//and unset the direction variables based on keypresses
if(event.keyCode===37)LEFT=false;
if(event.keyCode===38)UP=false;
if(event.keyCode===39)RIGHT=false;
if(event.keyCode===40)DOWN=false;
if(event.keyCode===65)LEFT=false;
if(event.keyCode===68)RIGHT=false;
if(event.keyCode===87)UP=false;
if(event.keyCode===83)DOWN=false;
})
canvas.addEventListener("click",function(event){
let rect=canvas.getBoundingClientRect();
let x=event.clientX-rect.left;
let y=event.clientY-rect.top;
let mass=massInput.value;
let radius=radiusInput.value;
radius=Number(radius);
mass=Number(mass);
new Ball(x,y,radius,mass);
})
let ball1=new Ball(100,100,25,10);
let ball2=new Ball(200,200,50,2);
let wall1=new Wall(400,100,450,350);
let wall2=new Wall(200,100,150,350);
let leftEdge=new Wall(0,0,0,context.canvas.height);
let rightEdge=new Wall(context.canvas.width,0,context.canvas.width,context.canvas.height);
let topEdge=new Wall(0,0,context.canvas.width,0);
let bottomEdge=new Wall(0,context.canvas.height,context.canvas.width,context.canvas.height);
function loop(){
context.clearRect(0,0,canvas.clientWidth,canvas.clientHeight);
balls.forEach((b,index)=>{
b.move();
walls.forEach((w)=>{
if(areBallAndWallColliding(balls[index],w)){
resolvePenetrationForBallAndWall(balls[index],w);
resolveCollisionForBallAndWall(balls[index],w);
}
})
for(let i=index+1;i<balls.length;i++){
if(are2BallsColliding(balls[index],balls[i])){
resolvePenetrationFor2Balls(balls[index],balls[i]);
resolveCollisionFor2Balls(balls[index],balls[i]);
}
}
b.draw();
})
walls.forEach((w)=>{
w.draw();
})
context.fillText(`Balls: ${balls.length}`,context.canvas.width-50,15);
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);