Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/main/java/com/thealgorithms/physics/ElasticCollision2D.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.thealgorithms.physics;

/**
* 2D Elastic collision between two circular bodies
* Based on principles of conservation of momentum and kinetic energy.
*
* @author [Yash Rajput](https://github.com/the-yash-rajput)
*/
public final class ElasticCollision2D {

private ElasticCollision2D() {
throw new AssertionError("No instances. Utility class");
}

public static class Body {
public double x;
public double y;
public double vx;
public double vy;
public double mass;
public double radius;

public Body(double x, double y, double vx, double vy, double mass, double radius) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.mass = mass;
this.radius = radius;
}
}

/**
* Resolve instantaneous elastic collision between two circular bodies.
*
* @param a first body
* @param b second body
*/
public static void resolveCollision(Body a, Body b) {
double dx = b.x - a.x;
double dy = b.y - a.y;
double dist = Math.hypot(dx, dy);

if (dist == 0) {
return; // overlapping
}

double nx = dx / dist;
double ny = dy / dist;

// relative velocity along normal
double rv = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;

if (rv > 0) {
return; // moving apart
}

// impulse with masses
double m1 = a.mass;
double m2 = b.mass;

double j = -(1 + 1.0) * rv / (1.0 / m1 + 1.0 / m2);

// impulse vector
double impulseX = j * nx;
double impulseY = j * ny;

a.vx -= impulseX / m1;
a.vy -= impulseY / m1;
b.vx += impulseX / m2;
b.vy += impulseY / m2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.thealgorithms.physics;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

class ElasticCollision2DTest {

@Test
void testEqualMassHeadOnCollision() {
ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 1, 0, 1.0, 0.5);
ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, -1, 0, 1.0, 0.5);

ElasticCollision2D.resolveCollision(a, b);

assertEquals(-1.0, a.vx, 1e-6);
assertEquals(0.0, a.vy, 1e-6);
assertEquals(1.0, b.vx, 1e-6);
assertEquals(0.0, b.vy, 1e-6);
}

@Test
void testUnequalMassHeadOnCollision() {
ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 2, 0, 2.0, 0.5);
ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, -1, 0, 1.0, 0.5);

ElasticCollision2D.resolveCollision(a, b);

// 1D head-on collision results
assertEquals(0.0, a.vx, 1e-6);
assertEquals(0.0, a.vy, 1e-6);
assertEquals(3.0, b.vx, 1e-6);
assertEquals(0.0, b.vy, 1e-6);
}

@Test
void testMovingApartNoCollision() {
ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, -1, 0, 1.0, 0.5);
ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, 1, 0, 1.0, 0.5);

ElasticCollision2D.resolveCollision(a, b);

assertEquals(-1.0, a.vx, 1e-6);
assertEquals(0.0, a.vy, 1e-6);
assertEquals(1.0, b.vx, 1e-6);
assertEquals(0.0, b.vy, 1e-6);
}

@Test
void testGlancingCollision() {
ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 1, 1, 1.0, 0.5);
ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 1, -1, -0.5, 1.0, 0.5);

ElasticCollision2D.resolveCollision(a, b);

// Ensure relative velocity along normal is reversed
double nx = (b.x - a.x) / Math.hypot(b.x - a.x, b.y - a.y);
double ny = (b.y - a.y) / Math.hypot(b.x - a.x, b.y - a.y);
double relVelAfter = (b.vx - a.vx) * nx + (b.vy - a.vy) * ny;

assertTrue(relVelAfter > 0);
}

@Test
void testOverlappingBodies() {
ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 1, 0, 1.0, 0.5);
ElasticCollision2D.Body b = new ElasticCollision2D.Body(0, 0, -1, 0, 1.0, 0.5);

ElasticCollision2D.resolveCollision(a, b);

// Should not crash, velocities may remain unchanged
assertEquals(1.0, a.vx, 1e-6);
assertEquals(0.0, a.vy, 1e-6);
assertEquals(-1.0, b.vx, 1e-6);
assertEquals(0.0, b.vy, 1e-6);
}

@Test
void testStationaryBodyHit() {
ElasticCollision2D.Body a = new ElasticCollision2D.Body(0, 0, 2, 0, 1.0, 0.5);
ElasticCollision2D.Body b = new ElasticCollision2D.Body(1, 0, 0, 0, 1.0, 0.5);

ElasticCollision2D.resolveCollision(a, b);

assertEquals(0.0, a.vx, 1e-6);
assertEquals(0.0, a.vy, 1e-6);
assertEquals(2.0, b.vx, 1e-6);
assertEquals(0.0, b.vy, 1e-6);
}
}