diff --git a/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java new file mode 100644 index 000000000000..399c3f1e041f --- /dev/null +++ b/src/main/java/com/thealgorithms/physics/ElasticCollision2D.java @@ -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; + } +} diff --git a/src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java b/src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java new file mode 100644 index 000000000000..480e5da23db1 --- /dev/null +++ b/src/test/java/com/thealgorithms/physics/ElasticCollision2DTest.java @@ -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); + } +}