├── 01 - Drawing a Ball ├── index.html └── script.js ├── 02 - Moving the Ball ├── index.html └── script.js ├── 03 - The Ball Class ├── index.html └── script.js ├── 04 - Ball Acceleration ├── index.html └── script.js ├── 05 - Introducing Vectors ├── index.html └── script.js ├── 06 - Unit Vector, Dot Product ├── index.html └── script.js ├── 07 - Ball to Ball Collision ├── index.html └── script.js ├── 08 - Collision Response ├── index.html └── script.js ├── 09 - Mass and Elasticity ├── index.html └── script.js ├── 10 - Adding the Walls ├── index.html └── script.js ├── 11 - Rotation Matrix ├── index.html └── script.js ├── 12 - Steering a Capsule ├── index.html └── script.js ├── 13 - Capsule to Capsule ├── index.html └── script.js ├── 14 - Rotational Collision ├── index.html └── script.js ├── 15 - Separating Axis Theorem ├── index.html └── script.js ├── 16 - Minimum Translation Vector ├── index.html └── script.js ├── 17 - Bodies and Shapes ├── index.html └── script.js ├── 18 - Collision Manifold ├── index.html └── script.js ├── 19 - A Star Is Born ├── index.html └── script.js ├── 20 - Ready for Games ├── game.js ├── index.html ├── mocorgo.js └── userInput.js ├── Game Template ├── game_template.js ├── index.html ├── mocorgo.js └── userInput.js ├── LICENSE └── README.md /01 - Drawing a Ball/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Drawing the Ball 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /01 - Drawing a Ball/script.js: -------------------------------------------------------------------------------- 1 | //store the canvas element in a variable 2 | const canvas = document.getElementById('canvas'); 3 | //define the rendering context 4 | const ctx = canvas.getContext('2d'); 5 | 6 | //a function that draws a circle and fills it with a color 7 | function drawBall(x, y, r){ 8 | ctx.beginPath(); 9 | ctx.arc(x, y, r, 0, 2*Math.PI); 10 | ctx.strokeStyle = "black"; 11 | ctx.stroke(); 12 | ctx.fillStyle = "red"; 13 | ctx.fill(); 14 | ctx.closePath(); 15 | } 16 | 17 | //calling the function twice 18 | drawBall(100, 100, 20); 19 | drawBall(200, 200, 30); 20 | -------------------------------------------------------------------------------- /02 - Moving the Ball/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Moving the Ball 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /02 - Moving the Ball/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | let x = 100; 5 | let y = 100; 6 | 7 | //boolean variables - if true, ball moves in the direction 8 | let LEFT, UP, RIGHT, DOWN; 9 | 10 | function drawBall(x, y, r){ 11 | ctx.beginPath(); 12 | ctx.arc(x, y, r, 0, 2*Math.PI); 13 | ctx.strokeStyle = "black"; 14 | ctx.stroke(); 15 | ctx.fillStyle = "red"; 16 | ctx.fill(); 17 | ctx.closePath(); 18 | } 19 | 20 | //direction booleans become true if the arrow key is pressed 21 | canvas.addEventListener('keydown', function(e){ 22 | if(e.keyCode === 37){ 23 | LEFT = true; 24 | } 25 | if(e.keyCode === 38){ 26 | UP = true; 27 | } 28 | if(e.keyCode === 39){ 29 | RIGHT = true; 30 | } 31 | if(e.keyCode === 40){ 32 | DOWN = true; 33 | } 34 | }); 35 | 36 | //direction booleans become true if the arrow key is released 37 | canvas.addEventListener('keyup', function(e){ 38 | if(e.keyCode === 37){ 39 | LEFT = false; 40 | } 41 | if(e.keyCode === 38){ 42 | UP = false; 43 | } 44 | if(e.keyCode === 39){ 45 | RIGHT = false; 46 | } 47 | if(e.keyCode === 40){ 48 | DOWN = false; 49 | } 50 | }); 51 | 52 | //changes the x or y position depending on the boolean values 53 | function move(){ 54 | if(LEFT){ 55 | x--; 56 | } 57 | if(UP){ 58 | y--; 59 | } 60 | if(RIGHT){ 61 | x++; 62 | } 63 | if(DOWN){ 64 | y++; 65 | } 66 | } 67 | 68 | //main loop that runs around 60 times per second 69 | function mainLoop() { 70 | ctx.clearRect(0, 0, 640, 480); 71 | move(); 72 | drawBall(x, y, 20); 73 | requestAnimationFrame(mainLoop); 74 | } 75 | requestAnimationFrame(mainLoop); 76 | 77 | -------------------------------------------------------------------------------- /03 - The Ball Class/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | The Ball Class 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /03 - The Ball Class/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | //array that stores all the Ball objects 5 | const BALLZ = []; 6 | 7 | let LEFT, UP, RIGHT, DOWN; 8 | 9 | //creating a Ball class 10 | class Ball{ 11 | //special method that gets called at the instantiation 12 | constructor(x, y, r){ 13 | this.x = x; 14 | this.y = y; 15 | this.r = r; 16 | this.player = false; 17 | //pushing the Ball object into the BALLZ array 18 | BALLZ.push(this); 19 | } 20 | 21 | drawBall(){ 22 | ctx.beginPath(); 23 | ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI); 24 | ctx.strokeStyle = "black"; 25 | ctx.stroke(); 26 | ctx.fillStyle = "red"; 27 | ctx.fill(); 28 | ctx.closePath(); 29 | } 30 | } 31 | 32 | function keyControl(b){ 33 | canvas.addEventListener('keydown', function(e){ 34 | if(e.keyCode === 37){ 35 | LEFT = true; 36 | } 37 | if(e.keyCode === 38){ 38 | UP = true; 39 | } 40 | if(e.keyCode === 39){ 41 | RIGHT = true; 42 | } 43 | if(e.keyCode === 40){ 44 | DOWN = true; 45 | } 46 | }); 47 | 48 | canvas.addEventListener('keyup', function(e){ 49 | if(e.keyCode === 37){ 50 | LEFT = false; 51 | } 52 | if(e.keyCode === 38){ 53 | UP = false; 54 | } 55 | if(e.keyCode === 39){ 56 | RIGHT = false; 57 | } 58 | if(e.keyCode === 40){ 59 | DOWN = false; 60 | } 61 | }); 62 | 63 | if(LEFT){ 64 | b.x--; 65 | } 66 | if(UP){ 67 | b.y--; 68 | } 69 | if(RIGHT){ 70 | b.x++; 71 | } 72 | if(DOWN){ 73 | b.y++; 74 | } 75 | 76 | } 77 | 78 | function mainLoop() { 79 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 80 | //iterating through the BALLZ array 81 | BALLZ.forEach((b) => { 82 | b.drawBall(); 83 | if (b.player){ 84 | keyControl(b); 85 | } 86 | }); 87 | requestAnimationFrame(mainLoop); 88 | } 89 | 90 | //create two Ball objects 91 | let Ball1 = new Ball(200, 200, 30); 92 | let Ball2 = new Ball(300, 300, 20); 93 | Ball1.player = true; 94 | 95 | requestAnimationFrame(mainLoop); 96 | 97 | -------------------------------------------------------------------------------- /04 - Ball Acceleration/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Ball Acceleration 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /04 - Ball Acceleration/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | 6 | let LEFT, UP, RIGHT, DOWN; 7 | 8 | //velocity gets multiplied by (1-friction) 9 | let friction = 0.1; 10 | 11 | class Ball{ 12 | constructor(x, y, r){ 13 | this.x = x; 14 | this.y = y; 15 | this.r = r; 16 | this.vel_x = 0; 17 | this.vel_y = 0; 18 | this.acc_x = 0; 19 | this.acc_y = 0; 20 | this.acceleration = 1; 21 | this.player = false; 22 | BALLZ.push(this); 23 | } 24 | 25 | drawBall(){ 26 | ctx.beginPath(); 27 | ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI); 28 | ctx.strokeStyle = "black"; 29 | ctx.stroke(); 30 | ctx.fillStyle = "red"; 31 | ctx.fill(); 32 | ctx.closePath(); 33 | } 34 | 35 | //displaying the current acceleration and the velocity of the ball 36 | display(){ 37 | ctx.beginPath(); 38 | ctx.moveTo(this.x, this.y); 39 | ctx.lineTo(this.x + this.acc_x*100, this.y + this.acc_y*100); 40 | ctx.strokeStyle = "green"; 41 | ctx.stroke(); 42 | ctx.closePath(); 43 | ctx.beginPath(); 44 | ctx.moveTo(this.x, this.y); 45 | ctx.lineTo(this.x + this.vel_x*10, this.y + this.vel_y*10); 46 | ctx.strokeStyle = "blue"; 47 | ctx.stroke(); 48 | ctx.closePath(); 49 | } 50 | } 51 | 52 | function keyControl(b){ 53 | canvas.addEventListener('keydown', function(e){ 54 | if(e.keyCode === 37){ 55 | LEFT = true; 56 | } 57 | if(e.keyCode === 38){ 58 | UP = true; 59 | } 60 | if(e.keyCode === 39){ 61 | RIGHT = true; 62 | } 63 | if(e.keyCode === 40){ 64 | DOWN = true; 65 | } 66 | }); 67 | 68 | canvas.addEventListener('keyup', function(e){ 69 | if(e.keyCode === 37){ 70 | LEFT = false; 71 | } 72 | if(e.keyCode === 38){ 73 | UP = false; 74 | } 75 | if(e.keyCode === 39){ 76 | RIGHT = false; 77 | } 78 | if(e.keyCode === 40){ 79 | DOWN = false; 80 | } 81 | }); 82 | 83 | //if true, the accelertion component gets a certain value 84 | if(LEFT){ 85 | b.acc_x = -b.acceleration; 86 | } 87 | if(UP){ 88 | b.acc_y = -b.acceleration; 89 | } 90 | if(RIGHT){ 91 | b.acc_x = b.acceleration; 92 | } 93 | if(DOWN){ 94 | b.acc_y = b.acceleration; 95 | } 96 | if(!UP && !DOWN){ 97 | b.acc_y = 0; 98 | } 99 | if(!RIGHT && !LEFT){ 100 | b.acc_x = 0; 101 | } 102 | 103 | //acceleration values added to the velocity components 104 | b.vel_x += b.acc_x; 105 | b.vel_y += b.acc_y; 106 | //velocity gets multiplied by a number between 0 and 1 107 | b.vel_x *= 1-friction; 108 | b.vel_y *= 1-friction; 109 | //velocity values added to the current x, y position 110 | b.x += b.vel_x; 111 | b.y += b.vel_y; 112 | 113 | } 114 | 115 | function mainLoop() { 116 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 117 | BALLZ.forEach((b) => { 118 | b.drawBall(); 119 | if (b.player){ 120 | keyControl(b); 121 | } 122 | b.display(); 123 | }); 124 | requestAnimationFrame(mainLoop); 125 | } 126 | 127 | let Ball1 = new Ball(200, 200, 100); 128 | Ball1.player = true; 129 | 130 | requestAnimationFrame(mainLoop); 131 | 132 | -------------------------------------------------------------------------------- /05 - Introducing Vectors/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Introducing Vectors 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /05 - Introducing Vectors/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | 6 | let LEFT, UP, RIGHT, DOWN; 7 | let friction = 0.1; 8 | 9 | //a class Vector with basic vector operations 10 | class Vector{ 11 | constructor(x, y){ 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | add(v){ 17 | return new Vector(this.x+v.x, this.y+v.y); 18 | } 19 | 20 | subtr(v){ 21 | return new Vector(this.x-v.x, this.y-v.y); 22 | } 23 | 24 | mag(){ 25 | return Math.sqrt(this.x**2 + this.y**2) 26 | } 27 | 28 | mult(n){ 29 | return new Vector(this.x*n, this.y*n); 30 | } 31 | 32 | drawVec(start_x, start_y, n, color){ 33 | ctx.beginPath(); 34 | ctx.moveTo(start_x, start_y); 35 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 36 | ctx.strokeStyle = color; 37 | ctx.stroke(); 38 | ctx.closePath(); 39 | } 40 | } 41 | 42 | class Ball{ 43 | constructor(x, y, r){ 44 | this.x = x; 45 | this.y = y; 46 | this.r = r; 47 | this.vel = new Vector(0,0); 48 | this.acc = new Vector(0,0); 49 | this.acceleration = 1; 50 | this.player = false; 51 | BALLZ.push(this); 52 | } 53 | 54 | drawBall(){ 55 | ctx.beginPath(); 56 | ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI); 57 | ctx.strokeStyle = "black"; 58 | ctx.stroke(); 59 | ctx.fillStyle = "red"; 60 | ctx.fill(); 61 | ctx.closePath(); 62 | } 63 | 64 | display(){ 65 | this.vel.drawVec(this.x, this.y, 10, "green"); 66 | this.acc.drawVec(this.x, this.y, 100, "blue"); 67 | } 68 | } 69 | 70 | function keyControl(b){ 71 | canvas.addEventListener('keydown', function(e){ 72 | if(e.keyCode === 37){ 73 | LEFT = true; 74 | } 75 | if(e.keyCode === 38){ 76 | UP = true; 77 | } 78 | if(e.keyCode === 39){ 79 | RIGHT = true; 80 | } 81 | if(e.keyCode === 40){ 82 | DOWN = true; 83 | } 84 | }); 85 | 86 | canvas.addEventListener('keyup', function(e){ 87 | if(e.keyCode === 37){ 88 | LEFT = false; 89 | } 90 | if(e.keyCode === 38){ 91 | UP = false; 92 | } 93 | if(e.keyCode === 39){ 94 | RIGHT = false; 95 | } 96 | if(e.keyCode === 40){ 97 | DOWN = false; 98 | } 99 | }); 100 | 101 | if(LEFT){ 102 | b.acc.x = -b.acceleration; 103 | } 104 | if(UP){ 105 | b.acc.y = -b.acceleration; 106 | } 107 | if(RIGHT){ 108 | b.acc.x = b.acceleration; 109 | } 110 | if(DOWN){ 111 | b.acc.y = b.acceleration; 112 | } 113 | if(!LEFT && !RIGHT){ 114 | b.acc.x = 0; 115 | } 116 | if(!UP && !DOWN){ 117 | b.acc.y = 0; 118 | } 119 | 120 | //acceleration vector gets added to the velocity vector 121 | b.vel = b.vel.add(b.acc); 122 | b.vel = b.vel.mult(1-friction); 123 | b.x += b.vel.x; 124 | b.y += b.vel.y; 125 | } 126 | 127 | function mainLoop(timestamp) { 128 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 129 | BALLZ.forEach((b) => { 130 | b.drawBall(); 131 | if (b.player){ 132 | keyControl(b); 133 | } 134 | b.display(); 135 | }) 136 | requestAnimationFrame(mainLoop); 137 | } 138 | 139 | let Ball1 = new Ball(200, 200, 30); 140 | Ball1.player = true; 141 | 142 | requestAnimationFrame(mainLoop); 143 | 144 | -------------------------------------------------------------------------------- /06 - Unit Vector, Dot Product/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Unit Vector, Dot Product 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /06 - Unit Vector, Dot Product/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | 6 | let LEFT, UP, RIGHT, DOWN; 7 | let friction = 0.1; 8 | 9 | class Vector{ 10 | constructor(x, y){ 11 | this.x = x; 12 | this.y = y; 13 | } 14 | 15 | add(v){ 16 | return new Vector(this.x+v.x, this.y+v.y); 17 | } 18 | 19 | subtr(v){ 20 | return new Vector(this.x-v.x, this.y-v.y); 21 | } 22 | 23 | mag(){ 24 | return Math.sqrt(this.x**2 + this.y**2); 25 | } 26 | 27 | mult(n){ 28 | return new Vector(this.x*n, this.y*n); 29 | } 30 | 31 | //returns a perpendicular normal vector 32 | normal(){ 33 | return new Vector(-this.y, this.x).unit(); 34 | } 35 | 36 | //returns a vector with same direction and 1 length 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath() 52 | } 53 | 54 | //returns the length of a vectors projection onto the other one 55 | static dot(v1, v2){ 56 | return v1.x*v2.x + v1.y*v2.y; 57 | } 58 | } 59 | 60 | class Ball{ 61 | constructor(x, y, r){ 62 | this.x = x; 63 | this.y = y; 64 | this.r = r; 65 | this.vel = new Vector(0,0); 66 | this.acc = new Vector(0,0); 67 | this.acceleration = 1; 68 | this.player = false; 69 | BALLZ.push(this); 70 | } 71 | 72 | drawBall(){ 73 | ctx.beginPath(); 74 | ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI); 75 | ctx.strokeStyle = "black"; 76 | ctx.stroke(); 77 | ctx.fillStyle = "red"; 78 | ctx.fill(); 79 | ctx.closePath(); 80 | } 81 | 82 | display(){ 83 | this.vel.drawVec(550, 400, 10, "green"); 84 | this.acc.unit().drawVec(550, 400, 50, "blue"); 85 | ctx.beginPath(); 86 | ctx.arc(550, 400, 50, 0, 2*Math.PI); 87 | ctx.strokeStyle = "black"; 88 | ctx.stroke(); 89 | ctx.closePath(); 90 | } 91 | } 92 | 93 | function keyControl(b){ 94 | canvas.addEventListener('keydown', function(e){ 95 | if(e.keyCode === 37){ 96 | LEFT = true; 97 | } 98 | if(e.keyCode === 38){ 99 | UP = true; 100 | } 101 | if(e.keyCode === 39){ 102 | RIGHT = true; 103 | } 104 | if(e.keyCode === 40){ 105 | DOWN = true; 106 | } 107 | }); 108 | 109 | canvas.addEventListener('keyup', function(e){ 110 | if(e.keyCode === 37){ 111 | LEFT = false; 112 | } 113 | if(e.keyCode === 38){ 114 | UP = false; 115 | } 116 | if(e.keyCode === 39){ 117 | RIGHT = false; 118 | } 119 | if(e.keyCode === 40){ 120 | DOWN = false; 121 | } 122 | }); 123 | 124 | if(LEFT){ 125 | b.acc.x = -b.acceleration; 126 | } 127 | if(UP){ 128 | b.acc.y = -b.acceleration; 129 | } 130 | if(RIGHT){ 131 | b.acc.x = b.acceleration; 132 | } 133 | if(DOWN){ 134 | b.acc.y = b.acceleration; 135 | } 136 | if(!LEFT && !RIGHT){ 137 | b.acc.x = 0; 138 | } 139 | if(!UP && !DOWN){ 140 | b.acc.y = 0; 141 | } 142 | 143 | b.acc = b.acc.unit().mult(b.acceleration); 144 | b.vel = b.vel.add(b.acc); 145 | b.vel = b.vel.mult(1-friction); 146 | b.x += b.vel.x; 147 | b.y += b.vel.y; 148 | } 149 | 150 | function mainLoop(timestamp) { 151 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 152 | BALLZ.forEach((b) => { 153 | b.drawBall(); 154 | if (b.player){ 155 | keyControl(b); 156 | } 157 | b.display(); 158 | }) 159 | requestAnimationFrame(mainLoop); 160 | } 161 | 162 | let Ball1 = new Ball(200, 200, 30); 163 | Ball1.player = true; 164 | 165 | requestAnimationFrame(mainLoop); 166 | 167 | -------------------------------------------------------------------------------- /07 - Ball to Ball Collision/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Ball to Ball Collision 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /07 - Ball to Ball Collision/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | 6 | let LEFT, UP, RIGHT, DOWN; 7 | let friction = 0.1; 8 | 9 | class Vector{ 10 | constructor(x, y){ 11 | this.x = x; 12 | this.y = y; 13 | } 14 | 15 | add(v){ 16 | return new Vector(this.x+v.x, this.y+v.y); 17 | } 18 | 19 | subtr(v){ 20 | return new Vector(this.x-v.x, this.y-v.y); 21 | } 22 | 23 | mag(){ 24 | return Math.sqrt(this.x**2 + this.y**2); 25 | } 26 | 27 | mult(n){ 28 | return new Vector(this.x*n, this.y*n); 29 | } 30 | 31 | normal(){ 32 | return new Vector(-this.y, this.x).unit(); 33 | } 34 | 35 | unit(){ 36 | if(this.mag() === 0){ 37 | return new Vector(0,0); 38 | } else { 39 | return new Vector(this.x/this.mag(), this.y/this.mag()); 40 | } 41 | } 42 | 43 | drawVec(start_x, start_y, n, color){ 44 | ctx.beginPath(); 45 | ctx.moveTo(start_x, start_y); 46 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 47 | ctx.strokeStyle = color; 48 | ctx.stroke(); 49 | ctx.closePath(); 50 | } 51 | 52 | static dot(v1, v2){ 53 | return v1.x*v2.x + v1.y*v2.y; 54 | } 55 | } 56 | 57 | class Ball{ 58 | constructor(x, y, r){ 59 | this.pos = new Vector(x,y); 60 | this.r = r; 61 | this.vel = new Vector(0,0); 62 | this.acc = new Vector(0,0); 63 | this.acceleration = 1; 64 | this.player = false; 65 | BALLZ.push(this); 66 | } 67 | 68 | drawBall(){ 69 | ctx.beginPath(); 70 | ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2*Math.PI); 71 | ctx.strokeStyle = "black"; 72 | ctx.stroke(); 73 | ctx.fillStyle = "red"; 74 | ctx.fill(); 75 | ctx.closePath(); 76 | } 77 | 78 | display(){ 79 | this.vel.drawVec(550, 400, 10, "green"); 80 | this.acc.unit().drawVec(550, 400, 50, "blue"); 81 | ctx.beginPath(); 82 | ctx.arc(550, 400, 50, 0, 2*Math.PI); 83 | ctx.strokeStyle = "black"; 84 | ctx.stroke(); 85 | ctx.closePath(); 86 | } 87 | } 88 | 89 | function keyControl(b){ 90 | canvas.addEventListener('keydown', function(e){ 91 | if(e.keyCode === 37){ 92 | LEFT = true; 93 | } 94 | if(e.keyCode === 38){ 95 | UP = true; 96 | } 97 | if(e.keyCode === 39){ 98 | RIGHT = true; 99 | } 100 | if(e.keyCode === 40){ 101 | DOWN = true; 102 | } 103 | }); 104 | 105 | canvas.addEventListener('keyup', function(e){ 106 | if(e.keyCode === 37){ 107 | LEFT = false; 108 | } 109 | if(e.keyCode === 38){ 110 | UP = false; 111 | } 112 | if(e.keyCode === 39){ 113 | RIGHT = false; 114 | } 115 | if(e.keyCode === 40){ 116 | DOWN = false; 117 | } 118 | }); 119 | 120 | if(LEFT){ 121 | b.acc.x = -b.acceleration; 122 | } 123 | if(UP){ 124 | b.acc.y = -b.acceleration; 125 | } 126 | if(RIGHT){ 127 | b.acc.x = b.acceleration; 128 | } 129 | if(DOWN){ 130 | b.acc.y = b.acceleration; 131 | } 132 | if(!LEFT && !RIGHT){ 133 | b.acc.x = 0; 134 | } 135 | if(!UP && !DOWN){ 136 | b.acc.y = 0; 137 | } 138 | 139 | b.acc = b.acc.unit().mult(b.acceleration); 140 | b.vel = b.vel.add(b.acc); 141 | b.vel = b.vel.mult(1-friction); 142 | b.pos = b.pos.add(b.vel); 143 | } 144 | 145 | function round(number, precision){ 146 | let factor = 10**precision; 147 | return Math.round(number * factor) / factor; 148 | } 149 | 150 | //collision detection between two balls 151 | function coll_det_bb(b1, b2){ 152 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 153 | return true; 154 | } else { 155 | return false; 156 | } 157 | } 158 | 159 | //penetration resolution 160 | //repositions the balls based on the penetration depth and the collision normal 161 | function pen_res_bb(b1, b2){ 162 | let dist = b1.pos.subtr(b2.pos); 163 | let pen_depth = b1.r + b2.r - dist.mag(); 164 | let pen_res = dist.unit().mult(pen_depth/2); 165 | b1.pos = b1.pos.add(pen_res); 166 | b2.pos = b2.pos.add(pen_res.mult(-1)); 167 | } 168 | 169 | function mainLoop(timestamp) { 170 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 171 | BALLZ.forEach((b, index) => { 172 | b.drawBall(); 173 | if (b.player){ 174 | keyControl(b); 175 | } 176 | 177 | //calls the collision detection for each ball pairs 178 | for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Collision Response 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /08 - Collision Response/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | 6 | let LEFT, UP, RIGHT, DOWN; 7 | let friction = 0.0; 8 | 9 | class Vector{ 10 | constructor(x, y){ 11 | this.x = x; 12 | this.y = y; 13 | } 14 | 15 | add(v){ 16 | return new Vector(this.x+v.x, this.y+v.y); 17 | } 18 | 19 | subtr(v){ 20 | return new Vector(this.x-v.x, this.y-v.y); 21 | } 22 | 23 | mag(){ 24 | return Math.sqrt(this.x**2 + this.y**2); 25 | } 26 | 27 | mult(n){ 28 | return new Vector(this.x*n, this.y*n); 29 | } 30 | 31 | normal(){ 32 | return new Vector(-this.y, this.x).unit(); 33 | } 34 | 35 | unit(){ 36 | if(this.mag() === 0){ 37 | return new Vector(0,0); 38 | } else { 39 | return new Vector(this.x/this.mag(), this.y/this.mag()); 40 | } 41 | } 42 | 43 | drawVec(start_x, start_y, n, color){ 44 | ctx.beginPath(); 45 | ctx.moveTo(start_x, start_y); 46 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 47 | ctx.strokeStyle = color; 48 | ctx.stroke(); 49 | ctx.closePath(); 50 | } 51 | 52 | static dot(v1, v2){ 53 | return v1.x*v2.x + v1.y*v2.y; 54 | } 55 | } 56 | 57 | class Ball{ 58 | constructor(x, y, r){ 59 | this.pos = new Vector(x,y); 60 | this.r = r; 61 | this.vel = new Vector(0,0); 62 | this.acc = new Vector(0,0); 63 | this.acceleration = 1; 64 | this.player = false; 65 | BALLZ.push(this); 66 | } 67 | 68 | drawBall(){ 69 | ctx.beginPath(); 70 | ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2*Math.PI); 71 | ctx.strokeStyle = "black"; 72 | ctx.stroke(); 73 | ctx.fillStyle = "red"; 74 | ctx.fill(); 75 | ctx.closePath(); 76 | } 77 | 78 | display(){ 79 | this.vel.drawVec(550, 400, 10, "green"); 80 | this.acc.unit().drawVec(550, 400, 50, "blue"); 81 | ctx.beginPath(); 82 | ctx.arc(550, 400, 50, 0, 2*Math.PI); 83 | ctx.strokeStyle = "black"; 84 | ctx.stroke(); 85 | ctx.closePath(); 86 | } 87 | 88 | reposition(){ 89 | this.acc = this.acc.unit().mult(this.acceleration); 90 | this.vel = this.vel.add(this.acc); 91 | this.vel = this.vel.mult(1-friction); 92 | this.pos = this.pos.add(this.vel); 93 | } 94 | } 95 | 96 | function keyControl(b){ 97 | canvas.addEventListener('keydown', function(e){ 98 | if(e.keyCode === 37){ 99 | LEFT = true; 100 | } 101 | if(e.keyCode === 38){ 102 | UP = true; 103 | } 104 | if(e.keyCode === 39){ 105 | RIGHT = true; 106 | } 107 | if(e.keyCode === 40){ 108 | DOWN = true; 109 | } 110 | }); 111 | 112 | canvas.addEventListener('keyup', function(e){ 113 | if(e.keyCode === 37){ 114 | LEFT = false; 115 | } 116 | if(e.keyCode === 38){ 117 | UP = false; 118 | } 119 | if(e.keyCode === 39){ 120 | RIGHT = false; 121 | } 122 | if(e.keyCode === 40){ 123 | DOWN = false; 124 | } 125 | }); 126 | 127 | if(LEFT){ 128 | b.acc.x = -b.acceleration; 129 | } 130 | if(UP){ 131 | b.acc.y = -b.acceleration; 132 | } 133 | if(RIGHT){ 134 | b.acc.x = b.acceleration; 135 | } 136 | if(DOWN){ 137 | b.acc.y = b.acceleration; 138 | } 139 | if(!LEFT && !RIGHT){ 140 | b.acc.x = 0; 141 | } 142 | if(!UP && !DOWN){ 143 | b.acc.y = 0; 144 | } 145 | } 146 | 147 | function round(number, precision){ 148 | let factor = 10**precision; 149 | return Math.round(number * factor) / factor; 150 | } 151 | 152 | function coll_det_bb(b1, b2){ 153 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 154 | return true; 155 | } else { 156 | return false; 157 | } 158 | } 159 | 160 | function pen_res_bb(b1, b2){ 161 | let dist = b1.pos.subtr(b2.pos); 162 | let pen_depth = b1.r + b2.r - dist.mag(); 163 | let pen_res = dist.unit().mult(pen_depth/2); 164 | b1.pos = b1.pos.add(pen_res); 165 | b2.pos = b2.pos.add(pen_res.mult(-1)); 166 | } 167 | 168 | //collision resolution 169 | //calculates the balls new velocity vectors after the collision 170 | function coll_res_bb(b1, b2){ 171 | //collision normal vector 172 | let normal = b1.pos.subtr(b2.pos).unit(); 173 | //relative velocity vector 174 | let relVel = b1.vel.subtr(b2.vel); 175 | //separating velocity - relVel projected onto the collision normal vector 176 | let sepVel = Vector.dot(relVel, normal); 177 | //the projection value after the collision (multiplied by -1) 178 | let new_sepVel = -sepVel; 179 | //collision normal vector with the magnitude of the new_sepVel 180 | let sepVelVec = normal.mult(new_sepVel); 181 | 182 | //adding the separating velocity vector to the original vel. vector 183 | b1.vel = b1.vel.add(sepVelVec); 184 | //adding its opposite to the other balls original vel. vector 185 | b2.vel = b2.vel.add(sepVelVec.mult(-1)); 186 | } 187 | 188 | function momentum_display(){ 189 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 190 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 191 | } 192 | 193 | function mainLoop(timestamp) { 194 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 195 | BALLZ.forEach((b, index) => { 196 | b.drawBall(); 197 | if (b.player){ 198 | keyControl(b); 199 | } 200 | for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Mass and Elasticity 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /09 - Mass and Elasticity/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | 6 | let LEFT, UP, RIGHT, DOWN; 7 | let friction = 0.05; 8 | 9 | class Vector{ 10 | constructor(x, y){ 11 | this.x = x; 12 | this.y = y; 13 | } 14 | 15 | add(v){ 16 | return new Vector(this.x+v.x, this.y+v.y); 17 | } 18 | 19 | subtr(v){ 20 | return new Vector(this.x-v.x, this.y-v.y); 21 | } 22 | 23 | mag(){ 24 | return Math.sqrt(this.x**2 + this.y**2); 25 | } 26 | 27 | mult(n){ 28 | return new Vector(this.x*n, this.y*n); 29 | } 30 | 31 | normal(){ 32 | return new Vector(-this.y, this.x).unit(); 33 | } 34 | 35 | unit(){ 36 | if(this.mag() === 0){ 37 | return new Vector(0,0); 38 | } else { 39 | return new Vector(this.x/this.mag(), this.y/this.mag()); 40 | } 41 | } 42 | 43 | drawVec(start_x, start_y, n, color){ 44 | ctx.beginPath(); 45 | ctx.moveTo(start_x, start_y); 46 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 47 | ctx.strokeStyle = color; 48 | ctx.stroke(); 49 | ctx.closePath(); 50 | } 51 | 52 | static dot(v1, v2){ 53 | return v1.x*v2.x + v1.y*v2.y; 54 | } 55 | } 56 | 57 | class Ball{ 58 | constructor(x, y, r, m){ 59 | this.pos = new Vector(x,y); 60 | this.r = r; 61 | this.m = m; 62 | if (this.m === 0){ 63 | this.inv_m = 0; 64 | } else { 65 | this.inv_m = 1 / this.m; 66 | } 67 | this.elasticity = 1; 68 | this.vel = new Vector(0,0); 69 | this.acc = new Vector(0,0); 70 | this.acceleration = 1; 71 | this.player = false; 72 | BALLZ.push(this); 73 | } 74 | 75 | drawBall(){ 76 | ctx.beginPath(); 77 | ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2*Math.PI); 78 | ctx.strokeStyle = "black"; 79 | ctx.stroke(); 80 | ctx.fillStyle = "red"; 81 | ctx.fill(); 82 | ctx.closePath(); 83 | } 84 | 85 | display(){ 86 | this.vel.drawVec(this.pos.x, this.pos.y, 10, "green"); 87 | ctx.fillStyle = "black"; 88 | ctx.fillText("m = "+this.m, this.pos.x-10, this.pos.y-5); 89 | ctx.fillText("e = "+this.elasticity, this.pos.x-10, this.pos.y+5); 90 | } 91 | 92 | reposition(){ 93 | this.acc = this.acc.unit().mult(this.acceleration); 94 | this.vel = this.vel.add(this.acc); 95 | this.vel = this.vel.mult(1-friction); 96 | this.pos = this.pos.add(this.vel); 97 | } 98 | } 99 | 100 | function keyControl(b){ 101 | canvas.addEventListener('keydown', function(e){ 102 | if(e.keyCode === 37){ 103 | LEFT = true; 104 | } 105 | if(e.keyCode === 38){ 106 | UP = true; 107 | } 108 | if(e.keyCode === 39){ 109 | RIGHT = true; 110 | } 111 | if(e.keyCode === 40){ 112 | DOWN = true; 113 | } 114 | }); 115 | 116 | canvas.addEventListener('keyup', function(e){ 117 | if(e.keyCode === 37){ 118 | LEFT = false; 119 | } 120 | if(e.keyCode === 38){ 121 | UP = false; 122 | } 123 | if(e.keyCode === 39){ 124 | RIGHT = false; 125 | } 126 | if(e.keyCode === 40){ 127 | DOWN = false; 128 | } 129 | }); 130 | 131 | if(LEFT){ 132 | b.acc.x = -b.acceleration; 133 | } 134 | if(UP){ 135 | b.acc.y = -b.acceleration; 136 | } 137 | if(RIGHT){ 138 | b.acc.x = b.acceleration; 139 | } 140 | if(DOWN){ 141 | b.acc.y = b.acceleration; 142 | } 143 | if(!LEFT && !RIGHT){ 144 | b.acc.x = 0; 145 | } 146 | if(!UP && !DOWN){ 147 | b.acc.y = 0; 148 | } 149 | } 150 | 151 | function round(number, precision){ 152 | let factor = 10**precision; 153 | return Math.round(number * factor) / factor; 154 | } 155 | 156 | function randInt(min, max){ 157 | return Math.floor(Math.random() * (max-min+1)) + min; 158 | } 159 | 160 | function coll_det_bb(b1, b2){ 161 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 162 | return true; 163 | } else { 164 | return false; 165 | } 166 | } 167 | 168 | function pen_res_bb(b1, b2){ 169 | let dist = b1.pos.subtr(b2.pos); 170 | let pen_depth = b1.r + b2.r - dist.mag(); 171 | //dividing the penetration depth in the ratio of the inverse masses 172 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 173 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 174 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 175 | } 176 | 177 | function coll_res_bb(b1, b2){ 178 | let normal = b1.pos.subtr(b2.pos).unit(); 179 | let relVel = b1.vel.subtr(b2.vel); 180 | let sepVel = Vector.dot(relVel, normal); 181 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 182 | 183 | //the difference between the new and the original sep.velocity value 184 | let vsep_diff = new_sepVel - sepVel; 185 | 186 | //dividing the impulse value in the ration of the inverse masses 187 | //and adding the impulse vector to the original vel. vectors 188 | //according to their inverse mass 189 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 190 | let impulseVec = normal.mult(impulse); 191 | 192 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 193 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 194 | } 195 | 196 | function momentum_display(){ 197 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 198 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 199 | } 200 | 201 | function mainLoop(timestamp) { 202 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 203 | BALLZ.forEach((b, index) => { 204 | b.drawBall(); 205 | if (b.player){ 206 | keyControl(b); 207 | } 208 | for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Adding the Walls 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /10 - Adding the Walls/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | 7 | let LEFT, UP, RIGHT, DOWN; 8 | let friction = 0.05; 9 | 10 | class Vector{ 11 | constructor(x, y){ 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | add(v){ 17 | return new Vector(this.x+v.x, this.y+v.y); 18 | } 19 | 20 | subtr(v){ 21 | return new Vector(this.x-v.x, this.y-v.y); 22 | } 23 | 24 | mag(){ 25 | return Math.sqrt(this.x**2 + this.y**2); 26 | } 27 | 28 | mult(n){ 29 | return new Vector(this.x*n, this.y*n); 30 | } 31 | 32 | normal(){ 33 | return new Vector(-this.y, this.x).unit(); 34 | } 35 | 36 | unit(){ 37 | if(this.mag() === 0){ 38 | return new Vector(0,0); 39 | } else { 40 | return new Vector(this.x/this.mag(), this.y/this.mag()); 41 | } 42 | } 43 | 44 | drawVec(start_x, start_y, n, color){ 45 | ctx.beginPath(); 46 | ctx.moveTo(start_x, start_y); 47 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 48 | ctx.strokeStyle = color; 49 | ctx.stroke(); 50 | ctx.closePath(); 51 | } 52 | 53 | static dot(v1, v2){ 54 | return v1.x*v2.x + v1.y*v2.y; 55 | } 56 | } 57 | 58 | class Ball{ 59 | constructor(x, y, r, m){ 60 | this.pos = new Vector(x,y); 61 | this.r = r; 62 | this.m = m; 63 | if (this.m === 0){ 64 | this.inv_m = 0; 65 | } else { 66 | this.inv_m = 1 / this.m; 67 | } 68 | this.elasticity = 1; 69 | this.vel = new Vector(0,0); 70 | this.acc = new Vector(0,0); 71 | this.acceleration = 1; 72 | this.player = false; 73 | BALLZ.push(this); 74 | } 75 | 76 | drawBall(){ 77 | ctx.beginPath(); 78 | ctx.arc(this.pos.x, this.pos.y, this.r, 0, 2*Math.PI); 79 | ctx.strokeStyle = "black"; 80 | ctx.stroke(); 81 | ctx.fillStyle = "red"; 82 | ctx.fill(); 83 | ctx.closePath(); 84 | } 85 | 86 | display(){ 87 | this.vel.drawVec(this.pos.x, this.pos.y, 10, "green"); 88 | ctx.fillStyle = "black"; 89 | ctx.fillText("m = "+this.m, this.pos.x-10, this.pos.y-5); 90 | ctx.fillText("e = "+this.elasticity, this.pos.x-10, this.pos.y+5); 91 | } 92 | 93 | reposition(){ 94 | this.acc = this.acc.unit().mult(this.acceleration); 95 | this.vel = this.vel.add(this.acc); 96 | this.vel = this.vel.mult(1-friction); 97 | this.pos = this.pos.add(this.vel); 98 | } 99 | } 100 | 101 | //Walls are line segments between two points 102 | class Wall{ 103 | constructor(x1, y1, x2, y2){ 104 | this.start = new Vector(x1, y1); 105 | this.end = new Vector(x2, y2); 106 | WALLZ.push(this); 107 | } 108 | 109 | drawWall(){ 110 | ctx.beginPath(); 111 | ctx.moveTo(this.start.x, this.start.y); 112 | ctx.lineTo(this.end.x, this.end.y); 113 | ctx.strokeStyle = "black"; 114 | ctx.stroke(); 115 | ctx.closePath(); 116 | } 117 | 118 | wallUnit(){ 119 | return this.end.subtr(this.start).unit(); 120 | } 121 | } 122 | 123 | function keyControl(b){ 124 | canvas.addEventListener('keydown', function(e){ 125 | if(e.keyCode === 37){ 126 | LEFT = true; 127 | } 128 | if(e.keyCode === 38){ 129 | UP = true; 130 | } 131 | if(e.keyCode === 39){ 132 | RIGHT = true; 133 | } 134 | if(e.keyCode === 40){ 135 | DOWN = true; 136 | } 137 | }); 138 | 139 | canvas.addEventListener('keyup', function(e){ 140 | if(e.keyCode === 37){ 141 | LEFT = false; 142 | } 143 | if(e.keyCode === 38){ 144 | UP = false; 145 | } 146 | if(e.keyCode === 39){ 147 | RIGHT = false; 148 | } 149 | if(e.keyCode === 40){ 150 | DOWN = false; 151 | } 152 | }); 153 | 154 | if(LEFT){ 155 | b.acc.x = -b.acceleration; 156 | } 157 | if(UP){ 158 | b.acc.y = -b.acceleration; 159 | } 160 | if(RIGHT){ 161 | b.acc.x = b.acceleration; 162 | } 163 | if(DOWN){ 164 | b.acc.y = b.acceleration; 165 | } 166 | if(!LEFT && !RIGHT){ 167 | b.acc.x = 0; 168 | } 169 | if(!UP && !DOWN){ 170 | b.acc.y = 0; 171 | } 172 | } 173 | 174 | function round(number, precision){ 175 | let factor = 10**precision; 176 | return Math.round(number * factor) / factor; 177 | } 178 | 179 | function randInt(min, max){ 180 | return Math.floor(Math.random() * (max-min+1)) + min; 181 | } 182 | 183 | //returns with the closest point on a line segment to a given point 184 | function closestPointBW(b1, w1){ 185 | let ballToWallStart = w1.start.subtr(b1.pos); 186 | if(Vector.dot(w1.wallUnit(), ballToWallStart) > 0){ 187 | return w1.start; 188 | } 189 | 190 | let wallEndToBall = b1.pos.subtr(w1.end); 191 | if(Vector.dot(w1.wallUnit(), wallEndToBall) > 0){ 192 | return w1.end; 193 | } 194 | 195 | let closestDist = Vector.dot(w1.wallUnit(), ballToWallStart); 196 | let closestVect = w1.wallUnit().mult(closestDist); 197 | return w1.start.subtr(closestVect); 198 | } 199 | 200 | function coll_det_bb(b1, b2){ 201 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 202 | return true; 203 | } else { 204 | return false; 205 | } 206 | } 207 | 208 | //collision detection between ball and wall 209 | function coll_det_bw(b1, w1){ 210 | let ballToClosest = closestPointBW(b1, w1).subtr(b1.pos); 211 | if (ballToClosest.mag() <= b1.r){ 212 | return true; 213 | } 214 | } 215 | 216 | function pen_res_bb(b1, b2){ 217 | let dist = b1.pos.subtr(b2.pos); 218 | let pen_depth = b1.r + b2.r - dist.mag(); 219 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 220 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 221 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 222 | } 223 | 224 | //penetration resolution between ball and wall 225 | function pen_res_bw(b1, w1){ 226 | let penVect = b1.pos.subtr(closestPointBW(b1, w1)); 227 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 228 | } 229 | 230 | function coll_res_bb(b1, b2){ 231 | let normal = b1.pos.subtr(b2.pos).unit(); 232 | let relVel = b1.vel.subtr(b2.vel); 233 | let sepVel = Vector.dot(relVel, normal); 234 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 235 | 236 | let vsep_diff = new_sepVel - sepVel; 237 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 238 | let impulseVec = normal.mult(impulse); 239 | 240 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 241 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 242 | } 243 | 244 | //collision response between ball and wall 245 | function coll_res_bw(b1, w1){ 246 | let normal = b1.pos.subtr(closestPointBW(b1, w1)).unit(); 247 | let sepVel = Vector.dot(b1.vel, normal); 248 | let new_sepVel = -sepVel * b1.elasticity; 249 | let vsep_diff = sepVel - new_sepVel; 250 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 251 | } 252 | 253 | function momentum_display(){ 254 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 255 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 256 | } 257 | 258 | function mainLoop(timestamp) { 259 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 260 | BALLZ.forEach((b, index) => { 261 | b.drawBall(); 262 | if (b.player){ 263 | keyControl(b); 264 | } 265 | //each ball object iterates through each wall object 266 | WALLZ.forEach((w) => { 267 | if(coll_det_bw(BALLZ[index], w)){ 268 | pen_res_bw(BALLZ[index], w); 269 | coll_res_bw(BALLZ[index], w); 270 | } 271 | }) 272 | for(let i = index+1; i { 284 | w.drawWall(); 285 | }) 286 | 287 | requestAnimationFrame(mainLoop); 288 | } 289 | 290 | for (let i = 0; i < 10; i++){ 291 | let newBall = new Ball(randInt(100,500), randInt(50,400), randInt(20,50), randInt(0,10)); 292 | newBall.elasticity = randInt(0,10) / 10; 293 | } 294 | let Wall2 = new Wall(300, 400, 550, 200); 295 | 296 | //walls along the canvas edges 297 | let edge1 = new Wall(0, 0, canvas.clientWidth, 0); 298 | let edge2 = new Wall(canvas.clientWidth, 0, canvas.clientWidth, canvas.clientHeight); 299 | let edge3 = new Wall(canvas.clientWidth, canvas.clientHeight, 0, canvas.clientHeight); 300 | let edge4 = new Wall(0, canvas.clientHeight, 0, 0); 301 | BALLZ[0].player = true; 302 | 303 | //intro if canvas 1138x640 304 | // let Wall1 = new Wall(1000, 350, 1100, 500); 305 | // let Wall2 = new Wall(700, 50, 800, 50); 306 | // let Ball1 = new Ball(-4000, 400, 80, 10); 307 | // let Ball2 = new Ball(960, 585, 35, 2); 308 | // let Ball3 = new Ball(1005, 610, 10, 2); 309 | // let Ball4 = new Ball(1120, 590, 10, 1); 310 | // let Ball45= new Ball(500, 340, 30, 2); 311 | // Ball1.vel.x = 290; 312 | 313 | 314 | 315 | requestAnimationFrame(mainLoop); 316 | 317 | -------------------------------------------------------------------------------- /11 - Rotation Matrix/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Rotation Matrix 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /11 - Rotation Matrix/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | 7 | let LEFT, UP, RIGHT, DOWN; 8 | let friction = 0.05; 9 | 10 | class Vector{ 11 | constructor(x, y){ 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | add(v){ 17 | return new Vector(this.x+v.x, this.y+v.y); 18 | } 19 | 20 | subtr(v){ 21 | return new Vector(this.x-v.x, this.y-v.y); 22 | } 23 | 24 | mag(){ 25 | return Math.sqrt(this.x**2 + this.y**2); 26 | } 27 | 28 | mult(n){ 29 | return new Vector(this.x*n, this.y*n); 30 | } 31 | 32 | normal(){ 33 | return new Vector(-this.y, this.x).unit(); 34 | } 35 | 36 | unit(){ 37 | if(this.mag() === 0){ 38 | return new Vector(0,0); 39 | } else { 40 | return new Vector(this.x/this.mag(), this.y/this.mag()); 41 | } 42 | } 43 | 44 | drawVec(start_x, start_y, n, color){ 45 | ctx.beginPath(); 46 | ctx.moveTo(start_x, start_y); 47 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 48 | ctx.strokeStyle = color; 49 | ctx.stroke(); 50 | ctx.closePath(); 51 | } 52 | 53 | static dot(v1, v2){ 54 | return v1.x*v2.x + v1.y*v2.y; 55 | } 56 | } 57 | 58 | 59 | //Matrix gets its values set to 0 as default 60 | class Matrix{ 61 | constructor(rows, cols){ 62 | this.rows = rows; 63 | this.cols = cols; 64 | this.data = []; 65 | 66 | for (let i = 0; i 0){ 248 | return w1.start; 249 | } 250 | 251 | let wallEndToBall = b1.pos.subtr(w1.end); 252 | if(Vector.dot(w1.wallUnit(), wallEndToBall) > 0){ 253 | return w1.end; 254 | } 255 | 256 | let closestDist = Vector.dot(w1.wallUnit(), ballToWallStart); 257 | let closestVect = w1.wallUnit().mult(closestDist); 258 | return w1.start.subtr(closestVect); 259 | } 260 | 261 | function coll_det_bb(b1, b2){ 262 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 263 | return true; 264 | } else { 265 | return false; 266 | } 267 | } 268 | 269 | function coll_det_bw(b1, w1){ 270 | let ballToClosest = closestPointBW(b1, w1).subtr(b1.pos); 271 | if (ballToClosest.mag() <= b1.r){ 272 | return true; 273 | } 274 | } 275 | 276 | function pen_res_bb(b1, b2){ 277 | let dist = b1.pos.subtr(b2.pos); 278 | let pen_depth = b1.r + b2.r - dist.mag(); 279 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 280 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 281 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 282 | } 283 | 284 | function pen_res_bw(b1, w1){ 285 | let penVect = b1.pos.subtr(closestPointBW(b1, w1)); 286 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 287 | } 288 | 289 | function coll_res_bb(b1, b2){ 290 | let normal = b1.pos.subtr(b2.pos).unit(); 291 | let relVel = b1.vel.subtr(b2.vel); 292 | let sepVel = Vector.dot(relVel, normal); 293 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 294 | 295 | let vsep_diff = new_sepVel - sepVel; 296 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 297 | let impulseVec = normal.mult(impulse); 298 | 299 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 300 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 301 | } 302 | 303 | function coll_res_bw(b1, w1){ 304 | let normal = b1.pos.subtr(closestPointBW(b1, w1)).unit(); 305 | let sepVel = Vector.dot(b1.vel, normal); 306 | let new_sepVel = -sepVel * b1.elasticity; 307 | let vsep_diff = sepVel - new_sepVel; 308 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 309 | } 310 | 311 | function momentum_display(){ 312 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 313 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 314 | } 315 | 316 | function mainLoop(timestamp) { 317 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 318 | userInput(); 319 | BALLZ.forEach((b, index) => { 320 | b.drawBall(); 321 | if (b.player){ 322 | b.keyControl(); 323 | } 324 | WALLZ.forEach((w) => { 325 | if(coll_det_bw(BALLZ[index], w)){ 326 | pen_res_bw(BALLZ[index], w); 327 | coll_res_bw(BALLZ[index], w); 328 | } 329 | }) 330 | for(let i = index+1; i { 341 | w.drawWall(); 342 | w.keyControl(); 343 | w.reposition(); 344 | }) 345 | 346 | requestAnimationFrame(mainLoop); 347 | } 348 | 349 | let Wall1 = new Wall (200, 200, 400, 200); 350 | 351 | 352 | 353 | requestAnimationFrame(mainLoop); 354 | 355 | -------------------------------------------------------------------------------- /12 - Steering a Capsule/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Steering a Capsule 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /12 - Steering a Capsule/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | const CAPS = []; 7 | 8 | let LEFT, UP, RIGHT, DOWN; 9 | let friction = 0.05; 10 | 11 | class Vector{ 12 | constructor(x, y){ 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | add(v){ 18 | return new Vector(this.x+v.x, this.y+v.y); 19 | } 20 | 21 | subtr(v){ 22 | return new Vector(this.x-v.x, this.y-v.y); 23 | } 24 | 25 | mag(){ 26 | return Math.sqrt(this.x**2 + this.y**2); 27 | } 28 | 29 | mult(n){ 30 | return new Vector(this.x*n, this.y*n); 31 | } 32 | 33 | normal(){ 34 | return new Vector(-this.y, this.x).unit(); 35 | } 36 | 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath(); 52 | } 53 | 54 | static dot(v1, v2){ 55 | return v1.x*v2.x + v1.y*v2.y; 56 | } 57 | 58 | static cross(v1, v2){ 59 | return v1.x*v2.y - v1.y*v2.x; 60 | } 61 | } 62 | 63 | class Matrix{ 64 | constructor(rows, cols){ 65 | this.rows = rows; 66 | this.cols = cols; 67 | this.data = []; 68 | 69 | for (let i = 0; i 0){ 314 | return w1.start; 315 | } 316 | 317 | let wallEndToBall = b1.pos.subtr(w1.end); 318 | if(Vector.dot(w1.wallUnit(), wallEndToBall) > 0){ 319 | return w1.end; 320 | } 321 | 322 | let closestDist = Vector.dot(w1.wallUnit(), ballToWallStart); 323 | let closestVect = w1.wallUnit().mult(closestDist); 324 | return w1.start.subtr(closestVect); 325 | } 326 | 327 | function coll_det_bb(b1, b2){ 328 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 329 | return true; 330 | } else { 331 | return false; 332 | } 333 | } 334 | 335 | function coll_det_bw(b1, w1){ 336 | let ballToClosest = closestPointBW(b1, w1).subtr(b1.pos); 337 | if (ballToClosest.mag() <= b1.r){ 338 | return true; 339 | } 340 | } 341 | 342 | function pen_res_bb(b1, b2){ 343 | let dist = b1.pos.subtr(b2.pos); 344 | let pen_depth = b1.r + b2.r - dist.mag(); 345 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 346 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 347 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 348 | } 349 | 350 | function pen_res_bw(b1, w1){ 351 | let penVect = b1.pos.subtr(closestPointBW(b1, w1)); 352 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 353 | } 354 | 355 | function coll_res_bb(b1, b2){ 356 | let normal = b1.pos.subtr(b2.pos).unit(); 357 | let relVel = b1.vel.subtr(b2.vel); 358 | let sepVel = Vector.dot(relVel, normal); 359 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 360 | 361 | let vsep_diff = new_sepVel - sepVel; 362 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 363 | let impulseVec = normal.mult(impulse); 364 | 365 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 366 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 367 | } 368 | 369 | function coll_res_bw(b1, w1){ 370 | let normal = b1.pos.subtr(closestPointBW(b1, w1)).unit(); 371 | let sepVel = Vector.dot(b1.vel, normal); 372 | let new_sepVel = -sepVel * b1.elasticity; 373 | let vsep_diff = sepVel - new_sepVel; 374 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 375 | } 376 | 377 | function momentum_display(){ 378 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 379 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 380 | } 381 | 382 | function mainLoop(timestamp) { 383 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 384 | userInput(); 385 | BALLZ.forEach((b, index) => { 386 | b.drawBall(); 387 | if (b.player){ 388 | b.keyControl(); 389 | } 390 | WALLZ.forEach((w) => { 391 | if(coll_det_bw(BALLZ[index], w)){ 392 | pen_res_bw(BALLZ[index], w); 393 | coll_res_bw(BALLZ[index], w); 394 | } 395 | }) 396 | for(let i = index+1; i { 407 | w.drawWall(); 408 | w.keyControl(); 409 | w.reposition(); 410 | }) 411 | 412 | CAPS.forEach((c) => { 413 | c.draw(); 414 | c.keyControl(); 415 | c.reposition(); 416 | }) 417 | 418 | requestAnimationFrame(mainLoop); 419 | } 420 | 421 | let Caps1 = new Capsule(200, 200, 300, 300, 40); 422 | 423 | 424 | 425 | requestAnimationFrame(mainLoop); 426 | 427 | -------------------------------------------------------------------------------- /13 - Capsule to Capsule/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Capsule to Capsule 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /13 - Capsule to Capsule/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | const CAPS = []; 7 | 8 | let LEFT, UP, RIGHT, DOWN; 9 | let friction = 0.05; 10 | 11 | class Vector{ 12 | constructor(x, y){ 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | add(v){ 18 | return new Vector(this.x+v.x, this.y+v.y); 19 | } 20 | 21 | subtr(v){ 22 | return new Vector(this.x-v.x, this.y-v.y); 23 | } 24 | 25 | mag(){ 26 | return Math.sqrt(this.x**2 + this.y**2); 27 | } 28 | 29 | mult(n){ 30 | return new Vector(this.x*n, this.y*n); 31 | } 32 | 33 | normal(){ 34 | return new Vector(-this.y, this.x).unit(); 35 | } 36 | 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath(); 52 | } 53 | 54 | static dot(v1, v2){ 55 | return v1.x*v2.x + v1.y*v2.y; 56 | } 57 | 58 | static cross(v1, v2){ 59 | return v1.x*v2.y - v1.y*v2.x; 60 | } 61 | } 62 | 63 | class Matrix{ 64 | constructor(rows, cols){ 65 | this.rows = rows; 66 | this.cols = cols; 67 | this.data = []; 68 | 69 | for (let i = 0; i 0){ 319 | return w1.start; 320 | } 321 | 322 | let wallEndToBall = p.subtr(w1.end); 323 | if(Vector.dot(w1.dir, wallEndToBall) > 0){ 324 | return w1.end; 325 | } 326 | 327 | let closestDist = Vector.dot(w1.dir, ballToWallStart); 328 | let closestVect = w1.dir.mult(closestDist); 329 | return w1.start.subtr(closestVect); 330 | } 331 | 332 | function closestPointsBetweenLS(c1, c2){ 333 | let shortestDist = closestPointOnLS(c1.start, c2).subtr(c1.start).mag(); 334 | let closestPoints = [c1.start, closestPointOnLS(c1.start, c2)]; 335 | if(closestPointOnLS(c1.end, c2).subtr(c1.end).mag() < shortestDist){ 336 | shortestDist = closestPointOnLS(c1.end, c2).subtr(c1.end).mag(); 337 | closestPoints = [c1.end, closestPointOnLS(c1.end, c2)]; 338 | } 339 | if(closestPointOnLS(c2.start, c1).subtr(c2.start).mag() < shortestDist){ 340 | shortestDist = closestPointOnLS(c2.start, c1).subtr(c2.start).mag(); 341 | closestPoints = [closestPointOnLS(c2.start, c1), c2.start]; 342 | } 343 | if(closestPointOnLS(c2.end, c1).subtr(c2.end).mag() < shortestDist){ 344 | shortestDist = closestPointOnLS(c2.end, c1).subtr(c2.end).mag(); 345 | closestPoints = [closestPointOnLS(c2.end, c1), c2.end]; 346 | } 347 | ctx.strokeStyle = "red"; 348 | ctx.beginPath(); 349 | ctx.moveTo(closestPoints[0].x, closestPoints[0].y); 350 | ctx.lineTo(closestPoints[1].x, closestPoints[1].y); 351 | ctx.closePath(); 352 | ctx.stroke(); 353 | ctx.beginPath(); 354 | ctx.arc(closestPoints[0].x, closestPoints[0].y, c1.r, 0, 2*Math.PI); 355 | ctx.closePath(); 356 | ctx.stroke(); 357 | ctx.beginPath(); 358 | ctx.arc(closestPoints[1].x, closestPoints[1].y, c2.r, 0, 2*Math.PI); 359 | ctx.closePath(); 360 | ctx.stroke(); 361 | return closestPoints; 362 | } 363 | 364 | function coll_det_bb(b1, b2){ 365 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 366 | return true; 367 | } else { 368 | return false; 369 | } 370 | } 371 | 372 | function coll_det_bw(b1, w1){ 373 | let ballToClosest = closestPointOnLS(b1.pos, w1).subtr(b1.pos); 374 | if (ballToClosest.mag() <= b1.r){ 375 | return true; 376 | } 377 | } 378 | 379 | //Capsule - Capsule collision detection 380 | function coll_det_cc(c1, c2){ 381 | if(c1.r + c2.r >= closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).mag()){ 382 | return true; 383 | } else { 384 | return false; 385 | } 386 | } 387 | 388 | function pen_res_bb(b1, b2){ 389 | let dist = b1.pos.subtr(b2.pos); 390 | let pen_depth = b1.r + b2.r - dist.mag(); 391 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 392 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 393 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 394 | } 395 | 396 | function pen_res_bw(b1, w1){ 397 | let penVect = b1.pos.subtr(closestPointOnLS(b1.pos, w1)); 398 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 399 | } 400 | 401 | //Capsule - Capsule penetration resolution 402 | function pen_res_cc(c1, c2){ 403 | let dist = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]); 404 | let pen_depth = c1.r + c2.r - dist.mag(); 405 | let pen_res = dist.unit().mult(pen_depth / (c1.inv_m + c2.inv_m)); 406 | c1.pos = c1.pos.add(pen_res.mult(c1.inv_m)); 407 | c2.pos = c2.pos.add(pen_res.mult(-c2.inv_m)); 408 | } 409 | 410 | function coll_res_bb(b1, b2){ 411 | let normal = b1.pos.subtr(b2.pos).unit(); 412 | let relVel = b1.vel.subtr(b2.vel); 413 | let sepVel = Vector.dot(relVel, normal); 414 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 415 | 416 | let vsep_diff = new_sepVel - sepVel; 417 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 418 | let impulseVec = normal.mult(impulse); 419 | 420 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 421 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 422 | } 423 | 424 | function coll_res_bw(b1, w1){ 425 | let normal = b1.pos.subtr(closestPointOnLS(b1.pos, w1)).unit(); 426 | let sepVel = Vector.dot(b1.vel, normal); 427 | let new_sepVel = -sepVel * b1.elasticity; 428 | let vsep_diff = sepVel - new_sepVel; 429 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 430 | } 431 | 432 | //Capsule - Capsule linear collision response 433 | function coll_res_cc(c1, c2){ 434 | let normal = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).unit(); 435 | let relVel = c1.vel.subtr(c2.vel); 436 | let sepVel = Vector.dot(relVel, normal); 437 | let new_sepVel = -sepVel * Math.min(c1.elasticity, c2.elasticity); 438 | 439 | let vsep_diff = new_sepVel - sepVel; 440 | let impulse = vsep_diff / (c1.inv_m + c2.inv_m); 441 | let impulseVec = normal.mult(impulse); 442 | 443 | c1.vel = c1.vel.add(impulseVec.mult(c1.inv_m)); 444 | c2.vel = c2.vel.add(impulseVec.mult(-c2.inv_m)); 445 | } 446 | 447 | function momentum_display(){ 448 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 449 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 450 | } 451 | 452 | function mainLoop(timestamp) { 453 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 454 | userInput(); 455 | BALLZ.forEach((b, index) => { 456 | b.drawBall(); 457 | if (b.player){ 458 | b.keyControl(); 459 | } 460 | WALLZ.forEach((w) => { 461 | if(coll_det_bw(BALLZ[index], w)){ 462 | pen_res_bw(BALLZ[index], w); 463 | coll_res_bw(BALLZ[index], w); 464 | } 465 | }) 466 | for(let i = index+1; i { 477 | w.drawWall(); 478 | w.keyControl(); 479 | w.reposition(); 480 | }) 481 | 482 | CAPS.forEach((c, index) => { 483 | c.draw(); 484 | if(c.player === true){ 485 | c.keyControl(); 486 | } 487 | for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Rotational Collision 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /14 - Rotational Collision/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | const CAPS = []; 7 | 8 | let LEFT, UP, RIGHT, DOWN; 9 | let friction = 0.05; 10 | 11 | class Vector{ 12 | constructor(x, y){ 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | add(v){ 18 | return new Vector(this.x+v.x, this.y+v.y); 19 | } 20 | 21 | subtr(v){ 22 | return new Vector(this.x-v.x, this.y-v.y); 23 | } 24 | 25 | mag(){ 26 | return Math.sqrt(this.x**2 + this.y**2); 27 | } 28 | 29 | mult(n){ 30 | return new Vector(this.x*n, this.y*n); 31 | } 32 | 33 | normal(){ 34 | return new Vector(-this.y, this.x).unit(); 35 | } 36 | 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath(); 52 | } 53 | 54 | static dot(v1, v2){ 55 | return v1.x*v2.x + v1.y*v2.y; 56 | } 57 | 58 | static cross(v1, v2){ 59 | return v1.x*v2.y - v1.y*v2.x; 60 | } 61 | } 62 | 63 | class Matrix{ 64 | constructor(rows, cols){ 65 | this.rows = rows; 66 | this.cols = cols; 67 | this.data = []; 68 | 69 | for (let i = 0; i 0){ 326 | return w1.start; 327 | } 328 | 329 | let wallEndToBall = p.subtr(w1.end); 330 | if(Vector.dot(w1.dir, wallEndToBall) > 0){ 331 | return w1.end; 332 | } 333 | 334 | let closestDist = Vector.dot(w1.dir, ballToWallStart); 335 | let closestVect = w1.dir.mult(closestDist); 336 | return w1.start.subtr(closestVect); 337 | } 338 | 339 | function closestPointsBetweenLS(c1, c2){ 340 | let shortestDist = closestPointOnLS(c1.start, c2).subtr(c1.start).mag(); 341 | let closestPoints = [c1.start, closestPointOnLS(c1.start, c2)]; 342 | if(closestPointOnLS(c1.end, c2).subtr(c1.end).mag() < shortestDist){ 343 | shortestDist = closestPointOnLS(c1.end, c2).subtr(c1.end).mag(); 344 | closestPoints = [c1.end, closestPointOnLS(c1.end, c2)]; 345 | } 346 | if(closestPointOnLS(c2.start, c1).subtr(c2.start).mag() < shortestDist){ 347 | shortestDist = closestPointOnLS(c2.start, c1).subtr(c2.start).mag(); 348 | closestPoints = [closestPointOnLS(c2.start, c1), c2.start]; 349 | } 350 | if(closestPointOnLS(c2.end, c1).subtr(c2.end).mag() < shortestDist){ 351 | shortestDist = closestPointOnLS(c2.end, c1).subtr(c2.end).mag(); 352 | closestPoints = [closestPointOnLS(c2.end, c1), c2.end]; 353 | } 354 | ctx.strokeStyle = "red"; 355 | ctx.beginPath(); 356 | ctx.moveTo(closestPoints[0].x, closestPoints[0].y); 357 | ctx.lineTo(closestPoints[1].x, closestPoints[1].y); 358 | ctx.closePath(); 359 | ctx.stroke(); 360 | ctx.beginPath(); 361 | ctx.arc(closestPoints[0].x, closestPoints[0].y, c1.r, 0, 2*Math.PI); 362 | ctx.closePath(); 363 | ctx.stroke(); 364 | ctx.beginPath(); 365 | ctx.arc(closestPoints[1].x, closestPoints[1].y, c2.r, 0, 2*Math.PI); 366 | ctx.closePath(); 367 | ctx.stroke(); 368 | return closestPoints; 369 | } 370 | 371 | function coll_det_bb(b1, b2){ 372 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 373 | return true; 374 | } else { 375 | return false; 376 | } 377 | } 378 | 379 | function coll_det_bw(b1, w1){ 380 | let ballToClosest = closestPointOnLS(b1.pos, w1).subtr(b1.pos); 381 | if (ballToClosest.mag() <= b1.r){ 382 | return true; 383 | } 384 | } 385 | 386 | function coll_det_cc(c1, c2){ 387 | if(c1.r + c2.r >= closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).mag()){ 388 | return true; 389 | } else { 390 | return false; 391 | } 392 | } 393 | 394 | function pen_res_bb(b1, b2){ 395 | let dist = b1.pos.subtr(b2.pos); 396 | let pen_depth = b1.r + b2.r - dist.mag(); 397 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 398 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 399 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 400 | } 401 | 402 | function pen_res_bw(b1, w1){ 403 | let penVect = b1.pos.subtr(closestPointOnLS(b1.pos, w1)); 404 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 405 | } 406 | 407 | function pen_res_cc(c1, c2){ 408 | let dist = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]); 409 | let pen_depth = c1.r + c2.r - dist.mag(); 410 | let pen_res = dist.unit().mult(pen_depth / (c1.inv_m + c2.inv_m)); 411 | c1.pos = c1.pos.add(pen_res.mult(c1.inv_m)); 412 | c2.pos = c2.pos.add(pen_res.mult(-c2.inv_m)); 413 | } 414 | 415 | function coll_res_bb(b1, b2){ 416 | let normal = b1.pos.subtr(b2.pos).unit(); 417 | let relVel = b1.vel.subtr(b2.vel); 418 | let sepVel = Vector.dot(relVel, normal); 419 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 420 | 421 | let vsep_diff = new_sepVel - sepVel; 422 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 423 | let impulseVec = normal.mult(impulse); 424 | 425 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 426 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 427 | } 428 | 429 | function coll_res_bw(b1, w1){ 430 | let normal = b1.pos.subtr(closestPointOnLS(b1.pos, w1)).unit(); 431 | let sepVel = Vector.dot(b1.vel, normal); 432 | let new_sepVel = -sepVel * b1.elasticity; 433 | let vsep_diff = sepVel - new_sepVel; 434 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 435 | } 436 | 437 | //Capsule - Capsule collision response, rotation included 438 | function coll_res_cc(c1, c2){ 439 | let normal = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).unit(); 440 | 441 | //1. Closing velocity 442 | let collArm1 = closestPointsBetweenLS(c1, c2)[0].subtr(c1.pos).add(normal.mult(c1.r)); 443 | let rotVel1 = new Vector(-c1.angVel * collArm1.y, c1.angVel * collArm1.x); 444 | let closVel1 = c1.vel.add(rotVel1); 445 | let collArm2 = closestPointsBetweenLS(c1, c2)[1].subtr(c2.pos).add(normal.mult(-c2.r)); 446 | let rotVel2= new Vector(-c2.angVel * collArm2.y, c2.angVel * collArm2.x); 447 | let closVel2 = c2.vel.add(rotVel2); 448 | 449 | //2. Impulse augmentation 450 | let impAug1 = Vector.cross(collArm1, normal); 451 | impAug1 = impAug1 * c1.inv_inertia * impAug1; 452 | let impAug2 = Vector.cross(collArm2, normal); 453 | impAug2 = impAug2 * c2.inv_inertia * impAug2; 454 | 455 | let relVel = closVel1.subtr(closVel2); 456 | let sepVel = Vector.dot(relVel, normal); 457 | let new_sepVel = -sepVel * Math.min(c1.elasticity, c2.elasticity); 458 | let vsep_diff = new_sepVel - sepVel; 459 | 460 | let impulse = vsep_diff / (c1.inv_m + c2.inv_m + impAug1 + impAug2); 461 | let impulseVec = normal.mult(impulse); 462 | 463 | //3. Changing the velocities 464 | c1.vel = c1.vel.add(impulseVec.mult(c1.inv_m)); 465 | c2.vel = c2.vel.add(impulseVec.mult(-c2.inv_m)); 466 | 467 | c1.angVel += c1.inv_inertia * Vector.cross(collArm1, impulseVec); 468 | c2.angVel -= c2.inv_inertia * Vector.cross(collArm2, impulseVec); 469 | } 470 | 471 | function momentum_display(){ 472 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 473 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 474 | } 475 | 476 | function mainLoop(timestamp) { 477 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 478 | userInput(); 479 | BALLZ.forEach((b, index) => { 480 | b.drawBall(); 481 | if (b.player){ 482 | b.keyControl(); 483 | } 484 | WALLZ.forEach((w) => { 485 | if(coll_det_bw(BALLZ[index], w)){ 486 | pen_res_bw(BALLZ[index], w); 487 | coll_res_bw(BALLZ[index], w); 488 | } 489 | }) 490 | for(let i = index+1; i { 501 | w.drawWall(); 502 | w.keyControl(); 503 | w.reposition(); 504 | }) 505 | 506 | CAPS.forEach((c, index) => { 507 | c.draw(); 508 | if(c.player === true){ 509 | c.keyControl(); 510 | } 511 | for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Separating Axis Theorem 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /15 - Separating Axis Theorem/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | const CAPS = []; 7 | 8 | let LEFT, UP, RIGHT, DOWN; 9 | let friction = 0.05; 10 | 11 | class Vector{ 12 | constructor(x, y){ 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | add(v){ 18 | return new Vector(this.x+v.x, this.y+v.y); 19 | } 20 | 21 | subtr(v){ 22 | return new Vector(this.x-v.x, this.y-v.y); 23 | } 24 | 25 | mag(){ 26 | return Math.sqrt(this.x**2 + this.y**2); 27 | } 28 | 29 | mult(n){ 30 | return new Vector(this.x*n, this.y*n); 31 | } 32 | 33 | normal(){ 34 | return new Vector(-this.y, this.x).unit(); 35 | } 36 | 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath(); 52 | } 53 | 54 | static dot(v1, v2){ 55 | return v1.x*v2.x + v1.y*v2.y; 56 | } 57 | 58 | static cross(v1, v2){ 59 | return v1.x*v2.y - v1.y*v2.x; 60 | } 61 | } 62 | 63 | class Matrix{ 64 | constructor(rows, cols){ 65 | this.rows = rows; 66 | this.cols = cols; 67 | this.data = []; 68 | 69 | for (let i = 0; i 0){ 418 | return w1.start; 419 | } 420 | 421 | let wallEndToBall = p.subtr(w1.end); 422 | if(Vector.dot(w1.dir, wallEndToBall) > 0){ 423 | return w1.end; 424 | } 425 | 426 | let closestDist = Vector.dot(w1.dir, ballToWallStart); 427 | let closestVect = w1.dir.mult(closestDist); 428 | return w1.start.subtr(closestVect); 429 | } 430 | 431 | function closestPointsBetweenLS(c1, c2){ 432 | let shortestDist = closestPointOnLS(c1.start, c2).subtr(c1.start).mag(); 433 | let closestPoints = [c1.start, closestPointOnLS(c1.start, c2)]; 434 | if(closestPointOnLS(c1.end, c2).subtr(c1.end).mag() < shortestDist){ 435 | shortestDist = closestPointOnLS(c1.end, c2).subtr(c1.end).mag(); 436 | closestPoints = [c1.end, closestPointOnLS(c1.end, c2)]; 437 | } 438 | if(closestPointOnLS(c2.start, c1).subtr(c2.start).mag() < shortestDist){ 439 | shortestDist = closestPointOnLS(c2.start, c1).subtr(c2.start).mag(); 440 | closestPoints = [closestPointOnLS(c2.start, c1), c2.start]; 441 | } 442 | if(closestPointOnLS(c2.end, c1).subtr(c2.end).mag() < shortestDist){ 443 | shortestDist = closestPointOnLS(c2.end, c1).subtr(c2.end).mag(); 444 | closestPoints = [closestPointOnLS(c2.end, c1), c2.end]; 445 | } 446 | ctx.strokeStyle = "red"; 447 | ctx.beginPath(); 448 | ctx.moveTo(closestPoints[0].x, closestPoints[0].y); 449 | ctx.lineTo(closestPoints[1].x, closestPoints[1].y); 450 | ctx.closePath(); 451 | ctx.stroke(); 452 | ctx.beginPath(); 453 | ctx.arc(closestPoints[0].x, closestPoints[0].y, c1.r, 0, 2*Math.PI); 454 | ctx.closePath(); 455 | ctx.stroke(); 456 | ctx.beginPath(); 457 | ctx.arc(closestPoints[1].x, closestPoints[1].y, c2.r, 0, 2*Math.PI); 458 | ctx.closePath(); 459 | ctx.stroke(); 460 | return closestPoints; 461 | } 462 | 463 | function coll_det_bb(b1, b2){ 464 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 465 | return true; 466 | } else { 467 | return false; 468 | } 469 | } 470 | 471 | function coll_det_bw(b1, w1){ 472 | let ballToClosest = closestPointOnLS(b1.pos, w1).subtr(b1.pos); 473 | if (ballToClosest.mag() <= b1.r){ 474 | return true; 475 | } 476 | } 477 | 478 | function coll_det_cc(c1, c2){ 479 | if(c1.r + c2.r >= closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).mag()){ 480 | return true; 481 | } else { 482 | return false; 483 | } 484 | } 485 | 486 | function pen_res_bb(b1, b2){ 487 | let dist = b1.pos.subtr(b2.pos); 488 | let pen_depth = b1.r + b2.r - dist.mag(); 489 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 490 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 491 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 492 | } 493 | 494 | function pen_res_bw(b1, w1){ 495 | let penVect = b1.pos.subtr(closestPointOnLS(b1.pos, w1)); 496 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 497 | } 498 | 499 | function pen_res_cc(c1, c2){ 500 | let dist = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]); 501 | let pen_depth = c1.r + c2.r - dist.mag(); 502 | let pen_res = dist.unit().mult(pen_depth / (c1.inv_m + c2.inv_m)); 503 | c1.pos = c1.pos.add(pen_res.mult(c1.inv_m)); 504 | c2.pos = c2.pos.add(pen_res.mult(-c2.inv_m)); 505 | } 506 | 507 | function coll_res_bb(b1, b2){ 508 | let normal = b1.pos.subtr(b2.pos).unit(); 509 | let relVel = b1.vel.subtr(b2.vel); 510 | let sepVel = Vector.dot(relVel, normal); 511 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 512 | 513 | let vsep_diff = new_sepVel - sepVel; 514 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 515 | let impulseVec = normal.mult(impulse); 516 | 517 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 518 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 519 | } 520 | 521 | function coll_res_bw(b1, w1){ 522 | let normal = b1.pos.subtr(closestPointOnLS(b1.pos, w1)).unit(); 523 | let sepVel = Vector.dot(b1.vel, normal); 524 | let new_sepVel = -sepVel * b1.elasticity; 525 | let vsep_diff = sepVel - new_sepVel; 526 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 527 | } 528 | 529 | //Capsule - Capsule collision response, rotation included 530 | function coll_res_cc(c1, c2){ 531 | let normal = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).unit(); 532 | 533 | //1. Closing velocity 534 | let collArm1 = closestPointsBetweenLS(c1, c2)[0].subtr(c1.pos).add(normal.mult(c1.r)); 535 | let rotVel1 = new Vector(-c1.angVel * collArm1.y, c1.angVel * collArm1.x); 536 | let closVel1 = c1.vel.add(rotVel1); 537 | let collArm2 = closestPointsBetweenLS(c1, c2)[1].subtr(c2.pos).add(normal.mult(-c2.r)); 538 | let rotVel2= new Vector(-c2.angVel * collArm2.y, c2.angVel * collArm2.x); 539 | let closVel2 = c2.vel.add(rotVel2); 540 | 541 | //2. Impulse augmentation 542 | let impAug1 = Vector.cross(collArm1, normal); 543 | impAug1 = impAug1 * c1.inv_inertia * impAug1; 544 | let impAug2 = Vector.cross(collArm2, normal); 545 | impAug2 = impAug2 * c2.inv_inertia * impAug2; 546 | 547 | let relVel = closVel1.subtr(closVel2); 548 | let sepVel = Vector.dot(relVel, normal); 549 | let new_sepVel = -sepVel * Math.min(c1.elasticity, c2.elasticity); 550 | let vsep_diff = new_sepVel - sepVel; 551 | 552 | let impulse = vsep_diff / (c1.inv_m + c2.inv_m + impAug1 + impAug2); 553 | let impulseVec = normal.mult(impulse); 554 | 555 | //3. Changing the velocities 556 | c1.vel = c1.vel.add(impulseVec.mult(c1.inv_m)); 557 | c2.vel = c2.vel.add(impulseVec.mult(-c2.inv_m)); 558 | 559 | c1.angVel += c1.inv_inertia * Vector.cross(collArm1, impulseVec); 560 | c2.angVel -= c2.inv_inertia * Vector.cross(collArm2, impulseVec); 561 | } 562 | 563 | //applying the separating axis theorem on two objects 564 | function sat(o1, o2){ 565 | axes1 = []; 566 | axes2 = []; 567 | axes1.push(o1.dir.normal()); 568 | axes1.push(o1.dir); 569 | axes2.push(o2.dir.normal()); 570 | axes2.push(o2.dir); 571 | let proj1, proj2 = 0; 572 | 573 | for(let i=0; imax){ 604 | max = p; 605 | } 606 | } 607 | return { 608 | min: min, 609 | max: max 610 | } 611 | } 612 | 613 | function momentum_display(){ 614 | let momentum = Ball1.vel.add(Ball2.vel).mag(); 615 | ctx.fillText("Momentum: "+round(momentum, 4), 500, 330); 616 | } 617 | 618 | function mainLoop(timestamp) { 619 | ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight); 620 | userInput(); 621 | // BALLZ.forEach((b, index) => { 622 | // b.drawBall(); 623 | // if (b.player){ 624 | // b.keyControl(); 625 | // } 626 | // WALLZ.forEach((w) => { 627 | // if(coll_det_bw(BALLZ[index], w)){ 628 | // pen_res_bw(BALLZ[index], w); 629 | // coll_res_bw(BALLZ[index], w); 630 | // } 631 | // }) 632 | // for(let i = index+1; i { 643 | // w.drawWall(); 644 | // w.keyControl(); 645 | // w.reposition(); 646 | // }) 647 | 648 | // CAPS.forEach((c, index) => { 649 | // c.draw(); 650 | // if(c.player === true){ 651 | // c.keyControl(); 652 | // } 653 | // for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Minimum Translation Vector 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /16 - Minimum Translation Vector/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | const CAPS = []; 7 | 8 | let LEFT, UP, RIGHT, DOWN; 9 | let friction = 0.05; 10 | 11 | class Vector{ 12 | constructor(x, y){ 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | add(v){ 18 | return new Vector(this.x+v.x, this.y+v.y); 19 | } 20 | 21 | subtr(v){ 22 | return new Vector(this.x-v.x, this.y-v.y); 23 | } 24 | 25 | mag(){ 26 | return Math.sqrt(this.x**2 + this.y**2); 27 | } 28 | 29 | mult(n){ 30 | return new Vector(this.x*n, this.y*n); 31 | } 32 | 33 | normal(){ 34 | return new Vector(-this.y, this.x).unit(); 35 | } 36 | 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath(); 52 | } 53 | 54 | static dot(v1, v2){ 55 | return v1.x*v2.x + v1.y*v2.y; 56 | } 57 | 58 | static cross(v1, v2){ 59 | return v1.x*v2.y - v1.y*v2.x; 60 | } 61 | } 62 | 63 | class Matrix{ 64 | constructor(rows, cols){ 65 | this.rows = rows; 66 | this.cols = cols; 67 | this.data = []; 68 | 69 | for (let i = 0; i 0){ 419 | return w1.start; 420 | } 421 | 422 | let wallEndToBall = p.subtr(w1.end); 423 | if(Vector.dot(w1.dir, wallEndToBall) > 0){ 424 | return w1.end; 425 | } 426 | 427 | let closestDist = Vector.dot(w1.dir, ballToWallStart); 428 | let closestVect = w1.dir.mult(closestDist); 429 | return w1.start.subtr(closestVect); 430 | } 431 | 432 | function closestPointsBetweenLS(c1, c2){ 433 | let shortestDist = closestPointOnLS(c1.start, c2).subtr(c1.start).mag(); 434 | let closestPoints = [c1.start, closestPointOnLS(c1.start, c2)]; 435 | if(closestPointOnLS(c1.end, c2).subtr(c1.end).mag() < shortestDist){ 436 | shortestDist = closestPointOnLS(c1.end, c2).subtr(c1.end).mag(); 437 | closestPoints = [c1.end, closestPointOnLS(c1.end, c2)]; 438 | } 439 | if(closestPointOnLS(c2.start, c1).subtr(c2.start).mag() < shortestDist){ 440 | shortestDist = closestPointOnLS(c2.start, c1).subtr(c2.start).mag(); 441 | closestPoints = [closestPointOnLS(c2.start, c1), c2.start]; 442 | } 443 | if(closestPointOnLS(c2.end, c1).subtr(c2.end).mag() < shortestDist){ 444 | shortestDist = closestPointOnLS(c2.end, c1).subtr(c2.end).mag(); 445 | closestPoints = [closestPointOnLS(c2.end, c1), c2.end]; 446 | } 447 | ctx.strokeStyle = "red"; 448 | ctx.beginPath(); 449 | ctx.moveTo(closestPoints[0].x, closestPoints[0].y); 450 | ctx.lineTo(closestPoints[1].x, closestPoints[1].y); 451 | ctx.closePath(); 452 | ctx.stroke(); 453 | ctx.beginPath(); 454 | ctx.arc(closestPoints[0].x, closestPoints[0].y, c1.r, 0, 2*Math.PI); 455 | ctx.closePath(); 456 | ctx.stroke(); 457 | ctx.beginPath(); 458 | ctx.arc(closestPoints[1].x, closestPoints[1].y, c2.r, 0, 2*Math.PI); 459 | ctx.closePath(); 460 | ctx.stroke(); 461 | return closestPoints; 462 | } 463 | 464 | function coll_det_bb(b1, b2){ 465 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 466 | return true; 467 | } else { 468 | return false; 469 | } 470 | } 471 | 472 | function coll_det_bw(b1, w1){ 473 | let ballToClosest = closestPointOnLS(b1.pos, w1).subtr(b1.pos); 474 | if (ballToClosest.mag() <= b1.r){ 475 | return true; 476 | } 477 | } 478 | 479 | function coll_det_cc(c1, c2){ 480 | if(c1.r + c2.r >= closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).mag()){ 481 | return true; 482 | } else { 483 | return false; 484 | } 485 | } 486 | 487 | function pen_res_bb(b1, b2){ 488 | let dist = b1.pos.subtr(b2.pos); 489 | let pen_depth = b1.r + b2.r - dist.mag(); 490 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 491 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 492 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 493 | } 494 | 495 | function pen_res_bw(b1, w1){ 496 | let penVect = b1.pos.subtr(closestPointOnLS(b1.pos, w1)); 497 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 498 | } 499 | 500 | function pen_res_cc(c1, c2){ 501 | let dist = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]); 502 | let pen_depth = c1.r + c2.r - dist.mag(); 503 | let pen_res = dist.unit().mult(pen_depth / (c1.inv_m + c2.inv_m)); 504 | c1.pos = c1.pos.add(pen_res.mult(c1.inv_m)); 505 | c2.pos = c2.pos.add(pen_res.mult(-c2.inv_m)); 506 | } 507 | 508 | function coll_res_bb(b1, b2){ 509 | let normal = b1.pos.subtr(b2.pos).unit(); 510 | let relVel = b1.vel.subtr(b2.vel); 511 | let sepVel = Vector.dot(relVel, normal); 512 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 513 | 514 | let vsep_diff = new_sepVel - sepVel; 515 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 516 | let impulseVec = normal.mult(impulse); 517 | 518 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 519 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 520 | } 521 | 522 | function coll_res_bw(b1, w1){ 523 | let normal = b1.pos.subtr(closestPointOnLS(b1.pos, w1)).unit(); 524 | let sepVel = Vector.dot(b1.vel, normal); 525 | let new_sepVel = -sepVel * b1.elasticity; 526 | let vsep_diff = sepVel - new_sepVel; 527 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 528 | } 529 | 530 | function coll_res_cc(c1, c2){ 531 | let normal = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).unit(); 532 | 533 | //1. Closing velocity 534 | let collArm1 = closestPointsBetweenLS(c1, c2)[0].subtr(c1.pos).add(normal.mult(c1.r)); 535 | let rotVel1 = new Vector(-c1.angVel * collArm1.y, c1.angVel * collArm1.x); 536 | let closVel1 = c1.vel.add(rotVel1); 537 | let collArm2 = closestPointsBetweenLS(c1, c2)[1].subtr(c2.pos).add(normal.mult(-c2.r)); 538 | let rotVel2= new Vector(-c2.angVel * collArm2.y, c2.angVel * collArm2.x); 539 | let closVel2 = c2.vel.add(rotVel2); 540 | 541 | //2. Impulse augmentation 542 | let impAug1 = Vector.cross(collArm1, normal); 543 | impAug1 = impAug1 * c1.inv_inertia * impAug1; 544 | let impAug2 = Vector.cross(collArm2, normal); 545 | impAug2 = impAug2 * c2.inv_inertia * impAug2; 546 | 547 | let relVel = closVel1.subtr(closVel2); 548 | let sepVel = Vector.dot(relVel, normal); 549 | let new_sepVel = -sepVel * Math.min(c1.elasticity, c2.elasticity); 550 | let vsep_diff = new_sepVel - sepVel; 551 | 552 | let impulse = vsep_diff / (c1.inv_m + c2.inv_m + impAug1 + impAug2); 553 | let impulseVec = normal.mult(impulse); 554 | 555 | //3. Changing the velocities 556 | c1.vel = c1.vel.add(impulseVec.mult(c1.inv_m)); 557 | c2.vel = c2.vel.add(impulseVec.mult(-c2.inv_m)); 558 | 559 | c1.angVel += c1.inv_inertia * Vector.cross(collArm1, impulseVec); 560 | c2.angVel -= c2.inv_inertia * Vector.cross(collArm2, impulseVec); 561 | } 562 | 563 | //applying the separating axis theorem on two objects 564 | function sat(o1, o2){ 565 | let minOverlap = null; 566 | let smallestAxis; 567 | let vertexObj; 568 | 569 | let axes = findAxes(o1, o2); 570 | let proj1, proj2 = 0; 571 | let firstShapeAxes = getFirstShapeAxes(o1); 572 | 573 | for(let i=0; i proj2.max && proj1.min < proj2.min) || 582 | (proj1.max < proj2.max && proj1.min > proj2.min)){ 583 | let mins = Math.abs(proj1.min - proj2.min); 584 | let maxs = Math.abs(proj1.max - proj2.max); 585 | if (mins < maxs){ 586 | overlap += mins; 587 | } else { 588 | overlap += maxs; 589 | axes[i] = axes[i].mult(-1); 590 | } 591 | } 592 | 593 | if (overlap < minOverlap || minOverlap === null){ 594 | minOverlap = overlap; 595 | smallestAxis = axes[i]; 596 | if (i proj2.max){ 599 | smallestAxis = axes[i].mult(-1); 600 | } 601 | } else { 602 | vertexObj = o1; 603 | if(proj1.max < proj2.max){ 604 | smallestAxis = axes[i].mult(-1); 605 | } 606 | } 607 | } 608 | }; 609 | 610 | let contactVertex = projShapeOntoAxis(smallestAxis, vertexObj).collVertex; 611 | smallestAxis.drawVec(contactVertex.x, contactVertex.y, minOverlap, "blue"); 612 | 613 | return true; 614 | } 615 | 616 | //returns the min and max projection values of a shape onto an axis 617 | function projShapeOntoAxis(axis, obj){ 618 | setBallVerticesAlongAxis(obj, axis); 619 | let min = Vector.dot(axis, obj.vertex[0]); 620 | let max = min; 621 | let collVertex = obj.vertex[0]; 622 | for(let i=0; imax){ 629 | max = p; 630 | } 631 | } 632 | return { 633 | min: min, 634 | max: max, 635 | collVertex: collVertex 636 | } 637 | } 638 | 639 | //finds the projection axes for the two objects 640 | function findAxes(o1, o2){ 641 | let axes = []; 642 | if(o1 instanceof Ball && o2 instanceof Ball){ 643 | axes.push(o2.pos.subtr(o1.pos).unit()); 644 | return axes; 645 | } 646 | if(o1 instanceof Ball){ 647 | axes.push(closestVertexToPoint(o2, o1.pos).subtr(o1.pos).unit()); 648 | axes.push(o2.dir.normal()); 649 | if (o2 instanceof Box){ 650 | axes.push(o2.dir); 651 | } 652 | return axes; 653 | } 654 | if(o2 instanceof Ball){ 655 | axes.push(closestVertexToPoint(o1, o2.pos).subtr(o2.pos).unit()); 656 | axes.push(o1.dir.normal()); 657 | if (o1 instanceof Box){ 658 | axes.push(o1.dir); 659 | } 660 | return axes; 661 | } 662 | axes.push(o1.dir.normal()); 663 | axes.push(o1.dir); 664 | axes.push(o2.dir.normal()); 665 | axes.push(o2.dir); 666 | return axes; 667 | } 668 | 669 | //iterates through an objects vertices and returns the one that is the closest to the given point 670 | function closestVertexToPoint(obj, p){ 671 | let closestVertex; 672 | let minDist = null; 673 | for(let i=0; i { 708 | // b.drawBall(); 709 | // if (b.player){ 710 | // b.keyControl(); 711 | // } 712 | // WALLZ.forEach((w) => { 713 | // if(coll_det_bw(BALLZ[index], w)){ 714 | // pen_res_bw(BALLZ[index], w); 715 | // coll_res_bw(BALLZ[index], w); 716 | // } 717 | // }) 718 | // for(let i = index+1; i { 729 | // w.drawWall(); 730 | // w.keyControl(); 731 | // w.reposition(); 732 | // }) 733 | 734 | // CAPS.forEach((c, index) => { 735 | // c.draw(); 736 | // if(c.player === true){ 737 | // c.keyControl(); 738 | // } 739 | // for(let i = index+1; i 2 | 3 | 4 | 5 | 6 | 14 | Bodies and Shapes 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /17 - Bodies and Shapes/script.js: -------------------------------------------------------------------------------- 1 | const canvas = document.getElementById('canvas'); 2 | const ctx = canvas.getContext('2d'); 3 | 4 | const BALLZ = []; 5 | const WALLZ = []; 6 | const CAPS = []; 7 | 8 | let LEFT, UP, RIGHT, DOWN; 9 | let friction = 0.05; 10 | 11 | class Vector{ 12 | constructor(x, y){ 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | add(v){ 18 | return new Vector(this.x+v.x, this.y+v.y); 19 | } 20 | 21 | subtr(v){ 22 | return new Vector(this.x-v.x, this.y-v.y); 23 | } 24 | 25 | mag(){ 26 | return Math.sqrt(this.x**2 + this.y**2); 27 | } 28 | 29 | mult(n){ 30 | return new Vector(this.x*n, this.y*n); 31 | } 32 | 33 | normal(){ 34 | return new Vector(-this.y, this.x).unit(); 35 | } 36 | 37 | unit(){ 38 | if(this.mag() === 0){ 39 | return new Vector(0,0); 40 | } else { 41 | return new Vector(this.x/this.mag(), this.y/this.mag()); 42 | } 43 | } 44 | 45 | drawVec(start_x, start_y, n, color){ 46 | ctx.beginPath(); 47 | ctx.moveTo(start_x, start_y); 48 | ctx.lineTo(start_x + this.x * n, start_y + this.y * n); 49 | ctx.strokeStyle = color; 50 | ctx.stroke(); 51 | ctx.closePath(); 52 | } 53 | 54 | static dot(v1, v2){ 55 | return v1.x*v2.x + v1.y*v2.y; 56 | } 57 | 58 | static cross(v1, v2){ 59 | return v1.x*v2.y - v1.y*v2.x; 60 | } 61 | } 62 | 63 | class Matrix{ 64 | constructor(rows, cols){ 65 | this.rows = rows; 66 | this.cols = cols; 67 | this.data = []; 68 | 69 | for (let i = 0; i 0){ 450 | return w1.start; 451 | } 452 | 453 | let wallEndToBall = p.subtr(w1.end); 454 | if(Vector.dot(w1.dir, wallEndToBall) > 0){ 455 | return w1.end; 456 | } 457 | 458 | let closestDist = Vector.dot(w1.dir, ballToWallStart); 459 | let closestVect = w1.dir.mult(closestDist); 460 | return w1.start.subtr(closestVect); 461 | } 462 | 463 | function closestPointsBetweenLS(c1, c2){ 464 | let shortestDist = closestPointOnLS(c1.start, c2).subtr(c1.start).mag(); 465 | let closestPoints = [c1.start, closestPointOnLS(c1.start, c2)]; 466 | if(closestPointOnLS(c1.end, c2).subtr(c1.end).mag() < shortestDist){ 467 | shortestDist = closestPointOnLS(c1.end, c2).subtr(c1.end).mag(); 468 | closestPoints = [c1.end, closestPointOnLS(c1.end, c2)]; 469 | } 470 | if(closestPointOnLS(c2.start, c1).subtr(c2.start).mag() < shortestDist){ 471 | shortestDist = closestPointOnLS(c2.start, c1).subtr(c2.start).mag(); 472 | closestPoints = [closestPointOnLS(c2.start, c1), c2.start]; 473 | } 474 | if(closestPointOnLS(c2.end, c1).subtr(c2.end).mag() < shortestDist){ 475 | shortestDist = closestPointOnLS(c2.end, c1).subtr(c2.end).mag(); 476 | closestPoints = [closestPointOnLS(c2.end, c1), c2.end]; 477 | } 478 | ctx.strokeStyle = "red"; 479 | ctx.beginPath(); 480 | ctx.moveTo(closestPoints[0].x, closestPoints[0].y); 481 | ctx.lineTo(closestPoints[1].x, closestPoints[1].y); 482 | ctx.closePath(); 483 | ctx.stroke(); 484 | ctx.beginPath(); 485 | ctx.arc(closestPoints[0].x, closestPoints[0].y, c1.r, 0, 2*Math.PI); 486 | ctx.closePath(); 487 | ctx.stroke(); 488 | ctx.beginPath(); 489 | ctx.arc(closestPoints[1].x, closestPoints[1].y, c2.r, 0, 2*Math.PI); 490 | ctx.closePath(); 491 | ctx.stroke(); 492 | return closestPoints; 493 | } 494 | 495 | function coll_det_bb(b1, b2){ 496 | if(b1.r + b2.r >= b2.pos.subtr(b1.pos).mag()){ 497 | return true; 498 | } else { 499 | return false; 500 | } 501 | } 502 | 503 | function coll_det_bw(b1, w1){ 504 | let ballToClosest = closestPointOnLS(b1.pos, w1).subtr(b1.pos); 505 | if (ballToClosest.mag() <= b1.r){ 506 | return true; 507 | } 508 | } 509 | 510 | function coll_det_cc(c1, c2){ 511 | if(c1.r + c2.r >= closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).mag()){ 512 | return true; 513 | } else { 514 | return false; 515 | } 516 | } 517 | 518 | function pen_res_bb(b1, b2){ 519 | let dist = b1.pos.subtr(b2.pos); 520 | let pen_depth = b1.r + b2.r - dist.mag(); 521 | let pen_res = dist.unit().mult(pen_depth / (b1.inv_m + b2.inv_m)); 522 | b1.pos = b1.pos.add(pen_res.mult(b1.inv_m)); 523 | b2.pos = b2.pos.add(pen_res.mult(-b2.inv_m)); 524 | } 525 | 526 | function pen_res_bw(b1, w1){ 527 | let penVect = b1.pos.subtr(closestPointOnLS(b1.pos, w1)); 528 | b1.pos = b1.pos.add(penVect.unit().mult(b1.r-penVect.mag())); 529 | } 530 | 531 | function pen_res_cc(c1, c2){ 532 | let dist = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]); 533 | let pen_depth = c1.r + c2.r - dist.mag(); 534 | let pen_res = dist.unit().mult(pen_depth / (c1.inv_m + c2.inv_m)); 535 | c1.pos = c1.pos.add(pen_res.mult(c1.inv_m)); 536 | c2.pos = c2.pos.add(pen_res.mult(-c2.inv_m)); 537 | } 538 | 539 | function coll_res_bb(b1, b2){ 540 | let normal = b1.pos.subtr(b2.pos).unit(); 541 | let relVel = b1.vel.subtr(b2.vel); 542 | let sepVel = Vector.dot(relVel, normal); 543 | let new_sepVel = -sepVel * Math.min(b1.elasticity, b2.elasticity); 544 | 545 | let vsep_diff = new_sepVel - sepVel; 546 | let impulse = vsep_diff / (b1.inv_m + b2.inv_m); 547 | let impulseVec = normal.mult(impulse); 548 | 549 | b1.vel = b1.vel.add(impulseVec.mult(b1.inv_m)); 550 | b2.vel = b2.vel.add(impulseVec.mult(-b2.inv_m)); 551 | } 552 | 553 | function coll_res_bw(b1, w1){ 554 | let normal = b1.pos.subtr(closestPointOnLS(b1.pos, w1)).unit(); 555 | let sepVel = Vector.dot(b1.vel, normal); 556 | let new_sepVel = -sepVel * b1.elasticity; 557 | let vsep_diff = sepVel - new_sepVel; 558 | b1.vel = b1.vel.add(normal.mult(-vsep_diff)); 559 | } 560 | 561 | function coll_res_cc(c1, c2){ 562 | let normal = closestPointsBetweenLS(c1, c2)[0].subtr(closestPointsBetweenLS(c1, c2)[1]).unit(); 563 | 564 | //1. Closing velocity 565 | let collArm1 = closestPointsBetweenLS(c1, c2)[0].subtr(c1.pos).add(normal.mult(c1.r)); 566 | let rotVel1 = new Vector(-c1.angVel * collArm1.y, c1.angVel * collArm1.x); 567 | let closVel1 = c1.vel.add(rotVel1); 568 | let collArm2 = closestPointsBetweenLS(c1, c2)[1].subtr(c2.pos).add(normal.mult(-c2.r)); 569 | let rotVel2= new Vector(-c2.angVel * collArm2.y, c2.angVel * collArm2.x); 570 | let closVel2 = c2.vel.add(rotVel2); 571 | 572 | //2. Impulse augmentation 573 | let impAug1 = Vector.cross(collArm1, normal); 574 | impAug1 = impAug1 * c1.inv_inertia * impAug1; 575 | let impAug2 = Vector.cross(collArm2, normal); 576 | impAug2 = impAug2 * c2.inv_inertia * impAug2; 577 | 578 | let relVel = closVel1.subtr(closVel2); 579 | let sepVel = Vector.dot(relVel, normal); 580 | let new_sepVel = -sepVel * Math.min(c1.elasticity, c2.elasticity); 581 | let vsep_diff = new_sepVel - sepVel; 582 | 583 | let impulse = vsep_diff / (c1.inv_m + c2.inv_m + impAug1 + impAug2); 584 | let impulseVec = normal.mult(impulse); 585 | 586 | //3. Changing the velocities 587 | c1.vel = c1.vel.add(impulseVec.mult(c1.inv_m)); 588 | c2.vel = c2.vel.add(impulseVec.mult(-c2.inv_m)); 589 | 590 | c1.angVel += c1.inv_inertia * Vector.cross(collArm1, impulseVec); 591 | c2.angVel -= c2.inv_inertia * Vector.cross(collArm2, impulseVec); 592 | } 593 | 594 | //applying the separating axis theorem on two objects 595 | function sat(o1, o2){ 596 | let minOverlap = null; 597 | let smallestAxis; 598 | let vertexObj; 599 | 600 | let axes = findAxes(o1, o2); 601 | let proj1, proj2 = 0; 602 | let firstShapeAxes = getShapeAxes(o1); 603 | 604 | for(let i=0; i proj2.max && proj1.min < proj2.min) || 613 | (proj1.max < proj2.max && proj1.min > proj2.min)){ 614 | let mins = Math.abs(proj1.min - proj2.min); 615 | let maxs = Math.abs(proj1.max - proj2.max); 616 | if (mins < maxs){ 617 | overlap += mins; 618 | } else { 619 | overlap += maxs; 620 | axes[i] = axes[i].mult(-1); 621 | } 622 | } 623 | 624 | if (overlap < minOverlap || minOverlap === null){ 625 | minOverlap = overlap; 626 | smallestAxis = axes[i]; 627 | if (i proj2.max){ 630 | smallestAxis = axes[i].mult(-1); 631 | } 632 | } else { 633 | vertexObj = o1; 634 | if(proj1.max < proj2.max){ 635 | smallestAxis = axes[i].mult(-1); 636 | } 637 | } 638 | } 639 | }; 640 | 641 | let contactVertex = projShapeOntoAxis(smallestAxis, vertexObj).collVertex; 642 | //smallestAxis.drawVec(contactVertex.x, contactVertex.y, minOverlap, "blue"); 643 | 644 | return { 645 | pen: minOverlap, 646 | axis: smallestAxis, 647 | vertex: contactVertex 648 | } 649 | } 650 | 651 | //returns the min and max projection values of a shape onto an axis 652 | function projShapeOntoAxis(axis, obj){ 653 | setBallVerticesAlongAxis(obj, axis); 654 | let min = Vector.dot(axis, obj.vertex[0]); 655 | let max = min; 656 | let collVertex = obj.vertex[0]; 657 | for(let i=0; imax){ 664 | max = p; 665 | } 666 | } 667 | return { 668 | min: min, 669 | max: max, 670 | collVertex: collVertex 671 | } 672 | } 673 | 674 | //finds the projection axes for the two objects 675 | function findAxes(o1, o2){ 676 | let axes = []; 677 | if(o1 instanceof Circle && o2 instanceof Circle){ 678 | axes.push(o2.pos.subtr(o1.pos).unit()); 679 | return axes; 680 | } 681 | if(o1 instanceof Circle){ 682 | axes.push(closestVertexToPoint(o2, o1.pos).subtr(o1.pos).unit()); 683 | axes.push(o2.dir.normal()); 684 | if (o2 instanceof Rectangle){ 685 | axes.push(o2.dir); 686 | } 687 | return axes; 688 | } 689 | if(o2 instanceof Circle){ 690 | axes.push(o1.dir.normal()); 691 | if (o1 instanceof Rectangle){ 692 | axes.push(o1.dir); 693 | } 694 | axes.push(closestVertexToPoint(o1, o2.pos).subtr(o2.pos).unit()); 695 | return axes; 696 | } 697 | axes.push(o1.dir.normal()); 698 | if (o1 instanceof Rectangle){ 699 | axes.push(o1.dir); 700 | } 701 | axes.push(o2.dir.normal()); 702 | if (o2 instanceof Rectangle){ 703 | axes.push(o2.dir); 704 | } 705 | return axes; 706 | } 707 | 708 | //iterates through an objects vertices and returns the one that is the closest to the given point 709 | function closestVertexToPoint(obj, p){ 710 | let closestVertex; 711 | let minDist = null; 712 | for(let i=0; i { 748 | // b.drawBall(); 749 | // if (b.player){ 750 | // b.keyControl(); 751 | // } 752 | // WALLZ.forEach((w) => { 753 | // if(coll_det_bw(BALLZ[index], w)){ 754 | // pen_res_bw(BALLZ[index], w); 755 | // coll_res_bw(BALLZ[index], w); 756 | // } 757 | // }) 758 | // for(let i = index+1; i { 769 | // w.drawWall(); 770 | // w.keyControl(); 771 | // w.reposition(); 772 | // }) 773 | 774 | // CAPS.forEach((c, index) => { 775 | // c.draw(); 776 | // if(c.player === true){ 777 | // c.keyControl(); 778 | // } 779 | // for(let i = index+1; i bestSat.pen){ 804 | bestSat = sat(testCaps.comp[o1comp], Caps1.comp[o2comp]); 805 | ctx.fillText("COLLISION", 500, 400); 806 | } 807 | } 808 | } 809 | 810 | if(bestSat.pen !== null){ 811 | bestSat.axis.drawVec(bestSat.vertex.x, bestSat.vertex.y, bestSat.pen, "blue"); 812 | } 813 | 814 | requestAnimationFrame(mainLoop); 815 | } 816 | 817 | let testCaps = new Capsule(200, 100, 100, 200, 50, 3); 818 | //let testBall = new Ball(200, 200, 80, 6); 819 | //let Box2 = new Box(300, 180, 250, 50, 80, 5); 820 | //let Ball1 = new Ball(300, 100, 50, 5); 821 | //let Wall1 = new Wall(300, 100, 250, 400); 822 | let Caps1 = new Capsule(350, 100, 300, 400, 60, 3); 823 | 824 | //Caps1.player = true; 825 | 826 | 827 | 828 | requestAnimationFrame(mainLoop); 829 | 830 | -------------------------------------------------------------------------------- /18 - Collision Manifold/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Collision Manifold 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /19 - A Star Is Born/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | A Star Is Born 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /20 - Ready for Games/game.js: -------------------------------------------------------------------------------- 1 | //STEP 1: setting up the environment 2 | const canvas = document.getElementById('canvas'); 3 | const ctx = canvas.getContext('2d'); 4 | 5 | putWallsAround(0, 0, canvas.clientWidth, canvas.clientHeight); 6 | let randomObjects = []; 7 | 8 | //Creating 10 body object with random arguments 9 | for(let addBody = 0; addBody < 10; addBody++){ 10 | let x0 = randInt(100, canvas.clientWidth-100); 11 | let y0 = randInt(100, canvas.clientHeight-100); 12 | let x1 = x0 + randInt(-50, 50); 13 | let y1 = y0 + randInt(-50, 50); 14 | let r = randInt(10, 30); 15 | let m = randInt(0, 10); 16 | if(addBody%4 === 0){ 17 | let ballObj = new Ball(x0, y0, r, m); 18 | ballObj.setPosition(100, 100); 19 | ballObj.color = "red"; 20 | ballObj.layer = 1; 21 | randomObjects.push(ballObj); 22 | } 23 | if(addBody%4 === 1){ 24 | let boxObj = new Box(x0, y0, x1, y1, r, m); 25 | boxObj.setPosition(200, 200); 26 | boxObj.color = "blue"; 27 | boxObj.layer = 2; 28 | randomObjects.push(boxObj); 29 | } 30 | if(addBody%4 === 2){ 31 | let capsObj = new Capsule(x0, y0, x1, y1, r, m); 32 | capsObj.setPosition(300, 300); 33 | capsObj.color = "lightgreen"; 34 | capsObj.layer = 3; 35 | randomObjects.push(capsObj); 36 | } 37 | if(addBody%4 === 3){ 38 | let starObj = new Star(x0, y0, r+20, m); 39 | starObj.setPosition(400, 400); 40 | starObj.color = "yellow"; 41 | starObj.layer = 4; 42 | randomObjects.push(starObj); 43 | } 44 | }; 45 | 46 | //setting the initial velocities 47 | for (let i in randomObjects){ 48 | if(randomObjects[i].m !== 0){ 49 | randomObjects[i].vel.set(Math.random()*4-2, Math.random()*4-2); 50 | } 51 | } 52 | 53 | //creating the player 54 | let playerBall = new Ball(320, 240, 10, 5); 55 | playerBall.player = true; 56 | playerBall.maxSpeed = 3; 57 | playerBall.color = "#5B2C6F"; 58 | 59 | 60 | //STEP 2: defining the game logic 61 | function gameLogic(){ 62 | for (let i in randomObjects){ 63 | if(collide(randomObjects[i], playerBall)){ 64 | //playerBall.remove(); 65 | randomObjects[i].remove(); 66 | randomObjects.splice(i, 1); 67 | } 68 | } 69 | } 70 | 71 | //STEP 3: handling the user input and the game loop 72 | userInput(playerBall); 73 | requestAnimationFrame(mainLoop); -------------------------------------------------------------------------------- /20 - Ready for Games/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Ready for Games 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /20 - Ready for Games/userInput.js: -------------------------------------------------------------------------------- 1 | //Event listeners for the arrow keys 2 | function userInput(obj){ 3 | canvas.addEventListener('keydown', function(e){ 4 | if(e.keyCode === 37){ 5 | obj.left = true; 6 | } 7 | if(e.keyCode === 38){ 8 | obj.up = true; 9 | } 10 | if(e.keyCode === 39){ 11 | obj.right = true; 12 | } 13 | if(e.keyCode === 40){ 14 | obj.down = true; 15 | } 16 | if(e.keyCode === 32){ 17 | obj.action = true; 18 | } 19 | }); 20 | 21 | canvas.addEventListener('keyup', function(e){ 22 | if(e.keyCode === 37){ 23 | obj.left = false; 24 | } 25 | if(e.keyCode === 38){ 26 | obj.up = false; 27 | } 28 | if(e.keyCode === 39){ 29 | obj.right = false; 30 | } 31 | if(e.keyCode === 40){ 32 | obj.down = false; 33 | } 34 | if(e.keyCode === 32){ 35 | obj.action = false; 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /Game Template/game_template.js: -------------------------------------------------------------------------------- 1 | // STEP 1: setting up the environment 2 | // creating the starting objects and variables before starting the main loop 3 | // for example: 4 | const canvas = document.getElementById('canvas'); 5 | const ctx = canvas.getContext('2d'); 6 | canvas.focus(); 7 | 8 | putWallsAround(0, 0, canvas.clientWidth, canvas.clientHeight); 9 | let player = new Ball(100, 100, 30, 5); 10 | player.setColor("red"); 11 | player.maxSpeed = 5; 12 | 13 | // STEP 2: defining the game logic 14 | function gameLogic(){ 15 | // this gets called periodically as part of the main loop 16 | // define the rules here 17 | } 18 | 19 | // handling the user input and the game loop 20 | userInput(player); 21 | requestAnimationFrame(mainLoop); -------------------------------------------------------------------------------- /Game Template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | Game Template 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Game Template/userInput.js: -------------------------------------------------------------------------------- 1 | //Event listeners for the arrow keys 2 | function userInput(obj){ 3 | canvas.addEventListener('keydown', function(e){ 4 | if(e.code === "ArrowLeft"){ 5 | obj.left = true; 6 | } 7 | if(e.code === "ArrowUp"){ 8 | obj.up = true; 9 | } 10 | if(e.code === "ArrowRight"){ 11 | obj.right = true; 12 | } 13 | if(e.code === "ArrowDown"){ 14 | obj.down = true; 15 | } 16 | if(e.code === "Space"){ 17 | obj.action = true; 18 | } 19 | }); 20 | 21 | canvas.addEventListener('keyup', function(e){ 22 | if(e.code === "ArrowLeft"){ 23 | obj.left = false; 24 | } 25 | if(e.code === "ArrowUp"){ 26 | obj.up = false; 27 | } 28 | if(e.code === "ArrowRight"){ 29 | obj.right = false; 30 | } 31 | if(e.code === "ArrowDown"){ 32 | obj.down = false; 33 | } 34 | if(e.code === "Space"){ 35 | obj.action = false; 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 danielszabo88 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mocorgo 2 | Building a physics engine\ 3 | Youtube - https://www.youtube.com/channel/UCORSpAcEfLWPRnbD2AAizSg 4 | 5 | Newest version is always in the Game Template folder 6 | --------------------------------------------------------------------------------