let myFont; function preload() { myFont = loadFont('assets/fonts/nabla.ttf'); } function setup() { var canvas = createCanvas(1200, 400); canvas.parent('shrinkin-sketch') createLoop({duration:120, gif:true}) } function windowResized() { if (windowWidth <= 480) { var canvas = resizeCanvas(windowWidth, 200); } else { var canvas = resizeCanvas(windowWidth, 300); } } class Ball { constructor(x, y, r) { this.position = new p5.Vector(x, y); this.velocity = p5.Vector.random2D(); this.velocity.mult(3); this.r = r; this.m = r * 0.1; } update() { this.position.add(this.velocity); } checkBoundaryCollision() { if (this.position.x > width - this.r) { this.position.x = width - this.r; this.velocity.x *= -1; } else if (this.position.x < this.r) { this.position.x = this.r; this.velocity.x *= -1; } else if (this.position.y > height - this.r) { this.position.y = height - this.r; this.velocity.y *= -1; } else if (this.position.y < this.r) { this.position.y = this.r; this.velocity.y *= -1; } } checkCollision(other) { // Get distances between the balls components let distanceVect = p5.Vector.sub(other.position, this.position); // Calculate magnitude of the vector separating the balls let distanceVectMag = distanceVect.mag(); // Minimum distance before they are touching let minDistance = this.r + other.r; if (distanceVectMag < minDistance) { let distanceCorrection = (minDistance - distanceVectMag) / 2.0; let d = distanceVect.copy(); let correctionVector = d.normalize().mult(distanceCorrection); other.position.add(correctionVector); this.position.sub(correctionVector); // get angle of distanceVect let theta = distanceVect.heading(); // precalculate trig values let sine = sin(theta); let cosine = cos(theta); /* bTemp will hold rotated ball this.positions. You just need to worry about bTemp[1] this.position*/ let bTemp = [new p5.Vector(), new p5.Vector()]; /* this ball's this.position is relative to the other so you can use the vector between them (bVect) as the reference point in the rotation expressions. bTemp[0].this.position.x and bTemp[0].this.position.y will initialize automatically to 0.0, which is what you want since b[1] will rotate around b[0] */ bTemp[1].x = cosine * distanceVect.x + sine * distanceVect.y; bTemp[1].y = cosine * distanceVect.y - sine * distanceVect.x; // rotate Temporary velocities let vTemp = [new p5.Vector(), new p5.Vector()]; vTemp[0].x = cosine * this.velocity.x + sine * this.velocity.y; vTemp[0].y = cosine * this.velocity.y - sine * this.velocity.x; vTemp[1].x = cosine * other.velocity.x + sine * other.velocity.y; vTemp[1].y = cosine * other.velocity.y - sine * other.velocity.x; /* Now that velocities are rotated, you can use 1D conservation of momentum equations to calculate the final this.velocity along the x-axis. */ let vFinal = [new p5.Vector(), new p5.Vector()]; // final rotated this.velocity for b[0] vFinal[0].x = ((this.m - other.m) * vTemp[0].x + 2 * other.m * vTemp[1].x) / (this.m + other.m); vFinal[0].y = vTemp[0].y; // final rotated this.velocity for b[0] vFinal[1].x = ((other.m - this.m) * vTemp[1].x + 2 * this.m * vTemp[0].x) / (this.m + other.m); vFinal[1].y = vTemp[1].y; // hack to avoid clumping bTemp[0].x += vFinal[0].x; bTemp[1].x += vFinal[1].x; /* Rotate ball this.positions and velocities back Reverse signs in trig expressions to rotate in the opposite direction */ // rotate balls let bFinal = [new p5.Vector(), new p5.Vector()]; bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y; bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x; bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y; bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x; // update balls to screen this.position other.position.x = this.position.x + bFinal[1].x; other.position.y = this.position.y + bFinal[1].y; this.position.add(bFinal[0]); // update velocities this.velocity.x = cosine * vFinal[0].x - sine * vFinal[0].y; this.velocity.y = cosine * vFinal[0].y + sine * vFinal[0].x; other.velocity.x = cosine * vFinal[1].x - sine * vFinal[1].y; other.velocity.y = cosine * vFinal[1].y + sine * vFinal[1].x; } } display(friend1, friend2) { this.update() noStroke(); fill('#ff0000'); ellipse(this.position.x, this.position.y, this.r * 2, this.r * 2); this.checkBoundaryCollision() this.checkCollision(friend1) this.checkCollision(friend2) } } class Logo { constructor() { this.yOffset = 0 this.up = false this.textSize = 36 this.bounceDepth = 10 this.speed = 0.2 } display() { fill('#20d582'); textFont(myFont); if (windowWidth/6 > 120) { textSize(120); } else { textSize(windowWidth/6) } if (windowWidth <= 480) { text('shrinkin', width/2 - 120, 20 + 60 + this.yOffset); text('-minkin', width/2 - 120, 100 + 60 + this.yOffset); } else { text('shrinkin', width/2 - 120, 50 + 90 + this.yOffset); text('-minkin', width/2 - 120, 150 + 90 + this.yOffset); } this.bounce() } bounce() { if (!this.up) { if (this.yOffset <= this.bounceDepth) { this.yOffset = this.yOffset + this.speed } if (this.yOffset > this.bounceDepth) { this.up = true } } else { if (this.yOffset > 0) { this.yOffset = this.yOffset - this.speed } if (this.yOffset < 0) { this.up = false } } } } let ball1 = new Ball(100, 400, 20) let ball2 = new Ball(700, 400, 80) let ball3 = new Ball(0, 200, 60) let logo = new Logo() function draw() { background('#209bd0'); ball1.display(ball2, ball3) ball2.display(ball1, ball3) ball3.display(ball1, ball2) if (windowWidth <= 480) { ball2.r = 40; ball3.r = 30; } else { ball2.r = 80; ball3.r = 60; } logo.display() }