diff --git a/src/main/java/com/thealgorithms/physics/ProjectileMotion.java b/src/main/java/com/thealgorithms/physics/ProjectileMotion.java
new file mode 100644
index 000000000000..3651e534f9d1
--- /dev/null
+++ b/src/main/java/com/thealgorithms/physics/ProjectileMotion.java
@@ -0,0 +1,96 @@
+package com.thealgorithms.physics;
+
+/**
+ *
+ * This implementation calculates the flight path of a projectile launched from any INITIAL HEIGHT.
+ * It is a more flexible version of the ground-to-ground model.
+ *
+ * @see Wikipedia - Projectile Motion
+ * @author [Priyanshu Kumar Singh](https://github.com/Priyanshu1303d)
+ */
+public final class ProjectileMotion {
+
+ private ProjectileMotion() {
+ }
+
+ /** Standard Earth gravity constant*/
+ private static final double GRAVITY = 9.80665;
+
+ /**
+ * A simple container for the results of a projectile motion calculation.
+ */
+ public static final class Result {
+ private final double timeOfFlight;
+ private final double horizontalRange;
+ private final double maxHeight;
+
+ public Result(double timeOfFlight, double horizontalRange, double maxHeight) {
+ this.timeOfFlight = timeOfFlight;
+ this.horizontalRange = horizontalRange;
+ this.maxHeight = maxHeight;
+ }
+
+ /** @return The total time the projectile is in the air (seconds). */
+ public double getTimeOfFlight() {
+ return timeOfFlight;
+ }
+
+ /** @return The total horizontal distance traveled (meters). */
+ public double getHorizontalRange() {
+ return horizontalRange;
+ }
+
+ /** @return The maximum vertical height from the ground (meters). */
+ public double getMaxHeight() {
+ return maxHeight;
+ }
+ }
+
+ /**
+ * Calculates projectile trajectory using standard Earth gravity.
+ *
+ * @param initialVelocity Initial speed of the projectile (m/s).
+ * @param launchAngleDegrees Launch angle from the horizontal (degrees).
+ * @param initialHeight Starting height of the projectile (m).
+ * @return A {@link Result} object with the trajectory data.
+ */
+ public static Result calculateTrajectory(double initialVelocity, double launchAngleDegrees, double initialHeight) {
+ return calculateTrajectory(initialVelocity, launchAngleDegrees, initialHeight, GRAVITY);
+ }
+
+ /**
+ * Calculates projectile trajectory with a custom gravity value.
+ *
+ * @param initialVelocity Initial speed (m/s). Must be non-negative.
+ * @param launchAngleDegrees Launch angle (degrees).
+ * @param initialHeight Starting height (m). Must be non-negative.
+ * @param gravity Acceleration due to gravity (m/s^2). Must be positive.
+ * @return A {@link Result} object with the trajectory data.
+ */
+ public static Result calculateTrajectory(double initialVelocity, double launchAngleDegrees, double initialHeight, double gravity) {
+ if (initialVelocity < 0 || initialHeight < 0 || gravity <= 0) {
+ throw new IllegalArgumentException("Velocity, height, and gravity must be non-negative, and gravity must be positive.");
+ }
+
+ double launchAngleRadians = Math.toRadians(launchAngleDegrees);
+ double v_iy = initialVelocity * Math.sin(launchAngleRadians); // Initial vertical velocity
+ double v_ix = initialVelocity * Math.cos(launchAngleRadians); // Initial horizontal velocity
+
+ // Correctly calculate total time of flight using the quadratic formula for vertical motion.
+ // y(t) = y0 + v_iy*t - 0.5*g*t^2. We solve for t when y(t) = 0.
+ double totalTimeOfFlight = (v_iy + Math.sqrt(v_iy * v_iy + 2 * gravity * initialHeight)) / gravity;
+
+ // Calculate max height. If launched downwards, max height is the initial height.
+ double maxHeight;
+ if (v_iy > 0) {
+ double heightGained = (v_iy * v_iy) / (2 * gravity);
+ maxHeight = initialHeight + heightGained;
+ } else {
+ maxHeight = initialHeight;
+ }
+
+ double horizontalRange = v_ix * totalTimeOfFlight;
+
+ return new Result(totalTimeOfFlight, horizontalRange, maxHeight);
+ }
+}
diff --git a/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java b/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java
new file mode 100644
index 000000000000..25e7cfa87f9e
--- /dev/null
+++ b/src/test/java/com/thealgorithms/physics/ProjectileMotionTest.java
@@ -0,0 +1,69 @@
+package com.thealgorithms.physics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test class for the general-purpose ProjectileMotion calculator.
+ *
+ */
+final class ProjectileMotionTest {
+
+ private static final double DELTA = 1e-4; // Tolerance for comparing double values
+
+ @Test
+ @DisplayName("Test ground-to-ground launch (initial height is zero)")
+ void testGroundToGroundLaunch() {
+ ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(50, 30, 0);
+ assertEquals(5.0986, result.getTimeOfFlight(), DELTA);
+ assertEquals(220.7750, result.getHorizontalRange(), DELTA);
+ assertEquals(31.8661, result.getMaxHeight(), DELTA);
+ }
+
+ @Test
+ @DisplayName("Test launch from an elevated position")
+ void testElevatedLaunch() {
+ ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(30, 45, 100);
+ assertEquals(7.1705, result.getTimeOfFlight(), DELTA);
+ assertEquals(152.1091, result.getHorizontalRange(), DELTA);
+ assertEquals(122.9436, result.getMaxHeight(), DELTA); // Final corrected value
+ }
+
+ @Test
+ @DisplayName("Test launch straight up (90 degrees)")
+ void testVerticalLaunch() {
+ ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(40, 90, 20);
+ assertEquals(8.6303, result.getTimeOfFlight(), DELTA);
+ assertEquals(0.0, result.getHorizontalRange(), DELTA);
+ assertEquals(101.5773, result.getMaxHeight(), DELTA);
+ }
+
+ @Test
+ @DisplayName("Test horizontal launch from a height (0 degrees)")
+ void testHorizontalLaunch() {
+ ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(25, 0, 80);
+ assertEquals(4.0392, result.getTimeOfFlight(), DELTA);
+ assertEquals(100.9809, result.getHorizontalRange(), DELTA);
+ assertEquals(80.0, result.getMaxHeight(), DELTA);
+ }
+
+ @Test
+ @DisplayName("Test downward launch from a height (negative angle)")
+ void testDownwardLaunchFromHeight() {
+ ProjectileMotion.Result result = ProjectileMotion.calculateTrajectory(20, -30, 100);
+ assertEquals(3.6100, result.getTimeOfFlight(), DELTA);
+ assertEquals(62.5268, result.getHorizontalRange(), DELTA);
+ assertEquals(100.0, result.getMaxHeight(), DELTA);
+ }
+
+ @Test
+ @DisplayName("Test invalid arguments throw an exception")
+ void testInvalidInputs() {
+ assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(-10, 45, 100));
+ assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(10, 45, -100));
+ assertThrows(IllegalArgumentException.class, () -> ProjectileMotion.calculateTrajectory(10, 45, 100, 0));
+ }
+}
diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java
index 2a3a84b5ceea..ed8b7725f379 100644
--- a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java
+++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java
@@ -56,8 +56,9 @@ void testReproducibility() {
void testNegativeInterval() {
// Integral of f(x) = x from -1 to 1 is 0
Function linear = Function.identity();
- double result = approximate(linear, -1, 1, 10000);
- assertEquals(0.0, result, EPSILON);
+ // The integral of x from 2 to 1 is -1.5
+ double result = approximate(linear, 2, 1, 10000);
+ assertEquals(-1.5, result, EPSILON);
}
@Test