diff --git a/src/main/java/math/Vector4f.java b/src/main/java/math/Vector4f.java
new file mode 100644
index 00000000..ce065e19
--- /dev/null
+++ b/src/main/java/math/Vector4f.java
@@ -0,0 +1,553 @@
+package math;
+
+/**
+ * A 4D vector represented by four floating-point values: x, y, z, and w. This class provides a set
+ * of methods for performing various mathematical operations such as addition, subtraction, dot
+ * product, normalization, etc., on 4D vectors.
+ */
+public class Vector4f {
+
+ private float x;
+
+ private float y;
+
+ private float z;
+
+ private float w;
+
+ /** Default constructor. Initializes the vector to (0, 0, 0, 0). */
+ public Vector4f() {
+ this(0, 0, 0, 0);
+ }
+
+ /**
+ * Constructs a vector with the specified x, y, z, and w components.
+ *
+ * @param x The x component of the vector.
+ * @param y The y component of the vector.
+ * @param z The z component of the vector.
+ * @param w The w component of the vector.
+ */
+ public Vector4f(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ /**
+ * Copy constructor. Creates a new vector that is a copy of the given vector.
+ *
+ * @param other The vector to copy.
+ */
+ public Vector4f(Vector4f other) {
+ this.x = other.x;
+ this.y = other.y;
+ this.z = other.z;
+ this.w = other.w;
+ }
+
+ /**
+ * Adds the specified vector to this vector and returns the result.
+ *
+ * @param other The vector to add.
+ * @return A new vector representing the sum of the two vectors.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public Vector4f add(Vector4f other) {
+ return new Vector4f(this).addLocal(other);
+ }
+
+ /**
+ * Adds the specified vector to this vector in-place and returns the updated vector.
+ *
+ * @param other The vector to add.
+ * @return The current vector after addition.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public Vector4f addLocal(Vector4f other) {
+ if (other == null) {
+ throw new IllegalArgumentException("Other vector cannot be null.");
+ }
+ x += other.x;
+ y += other.y;
+ z += other.z;
+ w += other.w;
+ return this;
+ }
+
+ /**
+ * Subtracts the specified vector from this vector and returns the result.
+ *
+ * @param other The vector to subtract.
+ * @return A new vector representing the difference of the two vectors.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public Vector4f subtract(Vector4f other) {
+ return new Vector4f(this).subtractLocal(other);
+ }
+
+ /**
+ * Subtracts the specified vector from this vector in-place and returns the updated vector.
+ *
+ * @param other The vector to subtract.
+ * @return The current vector after subtraction.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public Vector4f subtractLocal(Vector4f other) {
+ if (other == null) {
+ throw new IllegalArgumentException("Other vector cannot be null.");
+ }
+ x -= other.x;
+ y -= other.y;
+ z -= other.z;
+ w -= other.w;
+ return this;
+ }
+
+ /**
+ * Multiplies this vector by a scalar and returns the result.
+ *
+ * @param scalar The scalar to multiply the vector by.
+ * @return A new vector representing the result of the multiplication.
+ */
+ public Vector4f multiply(float scalar) {
+ return new Vector4f(this).multiplyLocal(scalar);
+ }
+
+ /**
+ * Multiplies this vector by a scalar in-place and returns the updated vector.
+ *
+ * @param scalar The scalar to multiply the vector by.
+ * @return The current vector after multiplication.
+ */
+ public Vector4f multiplyLocal(float scalar) {
+ x *= scalar;
+ y *= scalar;
+ z *= scalar;
+ w *= scalar;
+ return this;
+ }
+
+ /**
+ * Divides this vector by a scalar and returns the result.
+ *
+ * @param scalar The scalar to divide the vector by.
+ * @return A new vector representing the result of the division.
+ * @throws ArithmeticException If the scalar is 0.
+ */
+ public Vector4f divide(float scalar) {
+ return new Vector4f(this).divideLocal(scalar);
+ }
+
+ /**
+ * Divides this vector by a scalar in-place and returns the updated vector.
+ *
+ * @param scalar The scalar to divide the vector by.
+ * @return The current vector after division.
+ * @throws ArithmeticException If the scalar is 0.
+ */
+ public Vector4f divideLocal(float scalar) {
+ if (scalar == 0) {
+ throw new ArithmeticException("Scalar cannot be 0.");
+ }
+ x /= scalar;
+ y /= scalar;
+ z /= scalar;
+ w /= scalar;
+ return this;
+ }
+
+ /**
+ * Negates this vector and returns the result.
+ *
+ * @return A new vector representing the negated vector.
+ */
+ public Vector4f negate() {
+ return new Vector4f(x, y, z, w).negateLocal();
+ }
+
+ /**
+ * Negates this vector in-place and returns the updated vector.
+ *
+ * @return The current vector after negation.
+ */
+ public Vector4f negateLocal() {
+ x = x == 0 ? 0 : -x;
+ y = y == 0 ? 0 : -y;
+ z = z == 0 ? 0 : -z;
+ w = w == 0 ? 0 : -w;
+ return this;
+ }
+
+ /**
+ * Returns the length (magnitude) of this vector.
+ *
+ * @return The length of the vector.
+ */
+ public float length() {
+ return Mathf.sqrt(lengthSquared());
+ }
+
+ /**
+ * Returns the squared length (magnitude) of this vector.
+ *
+ * @return The squared length of the vector.
+ */
+ public float lengthSquared() {
+ return x * x + y * y + z * z + w * w;
+ }
+
+ /**
+ * Normalizes this vector and returns the result.
+ *
+ * @return A new vector representing the normalized vector.
+ */
+ public Vector4f normalize() {
+ return new Vector4f(this).normalizeLocal();
+ }
+
+ /**
+ * Normalizes this vector in-place and returns the updated vector.
+ *
+ * @return The current vector after normalization.
+ */
+ public Vector4f normalizeLocal() {
+ float length = length();
+ if (length == 0) {
+ set(0, 0, 0, 0);
+ } else {
+ divideLocal(length);
+ }
+ return this;
+ }
+
+ /**
+ * Checks if the vector is a zero vector (x, y, z, w all equal to 0).
+ *
+ * @return True if the vector is zero, false otherwise.
+ */
+ public boolean isZero() {
+ return x == 0 && y == 0 && z == 0 && w == 0;
+ }
+
+ /**
+ * Returns the dot product of this vector and another vector.
+ *
+ * @param other The other vector.
+ * @return The dot product of the two vectors.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public float dot(Vector4f other) {
+ if (other == null) {
+ throw new IllegalArgumentException("Other vector cannot be null.");
+ }
+ return x * other.x + y * other.y + z * other.z + w * other.w;
+ }
+
+ /**
+ * Checks if this vector is approximately equal to another vector within a given tolerance.
+ *
+ * @param other The other vector.
+ * @param tolerance The tolerance for comparison.
+ * @return True if the vectors are equal within the tolerance, false otherwise.
+ */
+ public boolean isEqual(Vector4f other, float tolerance) {
+ if (other == null) return false;
+ if (other == this) return true;
+
+ return Mathf.abs(x - other.x) <= tolerance
+ && Mathf.abs(y - other.y) <= tolerance
+ && Mathf.abs(z - other.z) <= tolerance
+ && Mathf.abs(w - other.w) <= tolerance;
+ }
+
+ /**
+ * Linearly interpolates between this vector and another vector by a factor t.
+ *
+ * @param other The other vector.
+ * @param t The interpolation factor (0.0 <= t <= 1.0).
+ * @return A new vector representing the result of the interpolation.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public Vector4f lerp(Vector4f other, float t) {
+ if (other == null) {
+ throw new IllegalArgumentException("Other vector cannot be null.");
+ }
+ float lerpedX = Mathf.lerpUnclamped(x, other.x, t);
+ float lerpedY = Mathf.lerpUnclamped(y, other.y, t);
+ float lerpedZ = Mathf.lerpUnclamped(z, other.z, t);
+ float lerpedW = Mathf.lerpUnclamped(w, other.w, t);
+ return new Vector4f(lerpedX, lerpedY, lerpedZ, lerpedW);
+ }
+
+ /**
+ * Returns the distance between this vector and another vector.
+ *
+ * @param other The other vector.
+ * @return The distance between the two vectors.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public float distanceTo(Vector4f other) {
+ return Mathf.sqrt(distanceSquaredTo(other));
+ }
+
+ /**
+ * Returns the squared distance between this vector and another vector.
+ *
+ * @param other The other vector.
+ * @return The squared distance between the two vectors.
+ * @throws IllegalArgumentException If the given vector is null.
+ */
+ public float distanceSquaredTo(Vector4f other) {
+ if (other == null) {
+ throw new IllegalArgumentException("Other vector cannot be null.");
+ }
+ return (x - other.x) * (x - other.x)
+ + (y - other.y) * (y - other.y)
+ + (z - other.z) * (z - other.z)
+ + (w - other.w) * (w - other.w);
+ }
+
+ /**
+ * Returns the angle (in radians) between this vector and another vector.
+ *
+ * @param other The other vector.
+ * @return The angle between the two vectors in radians.
+ * @throws IllegalArgumentException If the given vector is null or if either vector has zero
+ * magnitude.
+ */
+ public float angleBetween(Vector4f other) {
+ // For angleBetween might be alternative mathematical approaches
+ // that could be explored for efficiency or numerical stability.
+
+ if (other == null) {
+ throw new IllegalArgumentException("Cannot calculate angle with null vector.");
+ }
+
+ float magnitude1 = this.length();
+ float magnitude2 = other.length();
+
+ if (magnitude1 == 0 || magnitude2 == 0) {
+ throw new IllegalArgumentException("Cannot calculate angle with magnitude equals to zero.");
+ }
+
+ float dotProduct = this.dot(other);
+ float cosTheta = dotProduct / (magnitude1 * magnitude2);
+
+ return Mathf.acos(Mathf.clamp(cosTheta, -1.0f, 1.0f));
+ }
+
+ /**
+ * Projects this vector onto another vector. The projection of vector A onto vector B is
+ * calculated using the formula: (A . B) / (B . B) * B, where A is the current vector and B is the
+ * vector onto which the projection is being made. This method returns a new vector that
+ * represents the result of the projection.
+ *
+ * @param other The vector onto which the current vector will be projected.
+ * @return A new vector representing the projection of this vector onto the other vector.
+ * @throws IllegalArgumentException If the given vector is null or if the other vector has zero
+ * length.
+ */
+ public Vector4f projectOnto(Vector4f other) {
+ if (other == null) {
+ throw new IllegalArgumentException("Cannot project onto a null vector.");
+ }
+
+ float dotProduct = this.dot(other);
+ float magnitudeSquared = other.lengthSquared();
+
+ if (magnitudeSquared == 0) {
+ throw new IllegalArgumentException("Cannot project onto a zero vector.");
+ }
+
+ float scalar = dotProduct / magnitudeSquared;
+ return new Vector4f(other).multiplyLocal(scalar);
+ }
+
+ /**
+ * Reflects this vector around the given normal vector.
+ *
+ *
Reflection is the process of "bouncing" a vector off a surface defined by the normal vector,
+ * such that the angle of incidence equals the angle of reflection. This is commonly used in
+ * physics simulations, computer graphics, and geometric calculations.
+ *
+ *
The reflection formula is:
+ *
+ *
+ * R = V - 2 * (V ⋅ N) * N
+ *
+ *
+ * where:
+ *
+ *
+ * - R: Reflected vector
+ *
- V: Original vector (this vector)
+ *
- N: Normal vector (should be normalized to ensure accurate results)
+ *
+ *
+ * This method computes the reflection of the current vector based on the specified normal and
+ * returns the resulting vector.
+ *
+ * @param normal the normal vector around which this vector will be reflected. Must not be {@code
+ * null} or a zero vector.
+ * @return a new {@code Vector4f} instance representing the reflected vector.
+ * @throws IllegalArgumentException if the {@code normal} vector is {@code null} or a zero vector.
+ */
+ public Vector4f reflect(Vector4f normal) {
+ if (normal == null) {
+ throw new IllegalArgumentException("Normal cannot be null.");
+ }
+ if (normal.isZero()) {
+ throw new IllegalArgumentException("Normal cannot be zero vertor.");
+ }
+ return this.subtract(normal.multiply(2 * this.dot(normal)));
+ }
+
+ /**
+ * Returns the x component of the vector.
+ *
+ * @return The x component of the vector.
+ */
+ public float getX() {
+ return x;
+ }
+
+ /**
+ * Sets the x component of the vector.
+ *
+ * @param x The new value for the x component.
+ */
+ public void setX(float x) {
+ this.x = x;
+ }
+
+ /**
+ * Returns the y component of the vector.
+ *
+ * @return The y component of the vector.
+ */
+ public float getY() {
+ return y;
+ }
+
+ /**
+ * Sets the y component of the vector.
+ *
+ * @param y The new value for the y component.
+ */
+ public void setY(float y) {
+ this.y = y;
+ }
+
+ /**
+ * Returns the z component of the vector.
+ *
+ * @return The z component of the vector.
+ */
+ public float getZ() {
+ return z;
+ }
+
+ /**
+ * Sets the z component of the vector.
+ *
+ * @param z The new value for the z component.
+ */
+ public void setZ(float z) {
+ this.z = z;
+ }
+
+ /**
+ * Returns the w component of the vector.
+ *
+ * @return The w component of the vector.
+ */
+ public float getW() {
+ return w;
+ }
+
+ /**
+ * Sets the w component of the vector.
+ *
+ * @param w The new value for the w component.
+ */
+ public void setW(float w) {
+ this.w = w;
+ }
+
+ /**
+ * Sets the components of this vector to the specified values.
+ *
+ * @param x The new x component.
+ * @param y The new y component.
+ * @param z The new z component.
+ * @param w The new w component.
+ */
+ public void set(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ /**
+ * Creates and returns a new copy of this vector. The clone method creates a new instance of the
+ * vector, with the same component values. This allows for independent modification of the cloned
+ * vector without affecting the original.
+ *
+ * @return A new instance of the vector with the same component values as the current vector.
+ */
+ @Override
+ public Vector4f clone() {
+ return new Vector4f(this);
+ }
+
+ /**
+ * Computes and returns the hash code for this vector. The hash code is calculated based on the
+ * vector's components using the `Float.floatToIntBits()` method to avoid precision issues with
+ * floating-point numbers. This ensures that vectors with the same values produce the same hash
+ * code.
+ *
+ * @return The hash code for this vector.
+ */
+ @Override
+ public int hashCode() {
+ int result = Float.floatToIntBits(x);
+ result = 31 * result + Float.floatToIntBits(y);
+ result = 31 * result + Float.floatToIntBits(z);
+ result = 31 * result + Float.floatToIntBits(w);
+ return result;
+ }
+
+ /**
+ * Compares this vector to another object for equality. Two vectors are considered equal if they
+ * have the same class type and their corresponding components (x, y, z, and w) are equal, taking
+ * into account floating-point precision. This method uses `Float.floatToIntBits()` to compare the
+ * components to avoid issues with floating-point comparison.
+ *
+ * @param obj The object to compare this vector with.
+ * @return {@code true} if the object is equal to this vector; {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (getClass() != obj.getClass()) return false;
+ Vector4f other = (Vector4f) obj;
+ return Float.floatToIntBits(x) == Float.floatToIntBits(other.x)
+ && Float.floatToIntBits(y) == Float.floatToIntBits(other.y)
+ && Float.floatToIntBits(z) == Float.floatToIntBits(other.z)
+ && Float.floatToIntBits(w) == Float.floatToIntBits(other.w);
+ }
+
+ /**
+ * Returns a string representation of this vector.
+ *
+ * @return A string representing the vector.".
+ */
+ @Override
+ public String toString() {
+ return "Vector4f [x=" + x + ", y=" + y + ", z=" + z + ", w=" + w + "]";
+ }
+}
diff --git a/src/test/java/math/Vector4fTest.java b/src/test/java/math/Vector4fTest.java
new file mode 100644
index 00000000..f236d609
--- /dev/null
+++ b/src/test/java/math/Vector4fTest.java
@@ -0,0 +1,2677 @@
+package math;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class Vector4fTest {
+
+ @Test
+ public void testDefaultConstructor() {
+ Vector4f vector = new Vector4f();
+ assertEquals(0, vector.getX(), "Default X value should be 0.");
+ assertEquals(0, vector.getY(), "Default Y value should be 0.");
+ assertEquals(0, vector.getZ(), "Default Z value should be 0.");
+ assertEquals(0, vector.getW(), "Default W value should be 0.");
+ }
+
+ @Test
+ public void testParameterizedConstructor() {
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ assertEquals(1.0f, vector.getX(), "X value should match the constructor argument.");
+ assertEquals(2.0f, vector.getY(), "Y value should match the constructor argument.");
+ assertEquals(3.0f, vector.getZ(), "Z value should match the constructor argument.");
+ assertEquals(4.0f, vector.getW(), "W value should match the constructor argument.");
+ }
+
+ @Test
+ public void testCopyConstructor() {
+ Vector4f original = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f copy = new Vector4f(original);
+
+ assertEquals(original.getX(), copy.getX(), "X value of copy should match the original.");
+ assertEquals(original.getY(), copy.getY(), "Y value of copy should match the original.");
+ assertEquals(original.getZ(), copy.getZ(), "Z value of copy should match the original.");
+ assertEquals(original.getW(), copy.getW(), "W value of copy should match the original.");
+ }
+
+ @Test
+ public void testCopyConstructorIndependence() {
+ Vector4f original = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f copy = new Vector4f(original);
+
+ // Modify the original vector
+ original = new Vector4f(0, 0, 0, 0);
+
+ // Ensure the copy remains unchanged
+ assertEquals(1.0f, copy.getX(), "Copy X value should remain unchanged.");
+ assertEquals(2.0f, copy.getY(), "Copy Y value should remain unchanged.");
+ assertEquals(3.0f, copy.getZ(), "Copy Z value should remain unchanged.");
+ assertEquals(4.0f, copy.getW(), "Copy W value should remain unchanged.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Add
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testAddValidVector() {
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+
+ Vector4f result = vector1.add(vector2);
+
+ assertEquals(5.0f, result.getX());
+ assertEquals(5.0f, result.getY());
+ assertEquals(5.0f, result.getZ());
+ assertEquals(5.0f, result.getW());
+ }
+
+ @Test
+ public void testAddNullVector() {
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ assertThrows(IllegalArgumentException.class, () -> vector1.add(null));
+ }
+
+ @Test
+ public void testAddZeroVector() {
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f zeroVector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ Vector4f result = vector1.add(zeroVector);
+
+ assertEquals(1.0f, result.getX());
+ assertEquals(2.0f, result.getY());
+ assertEquals(3.0f, result.getZ());
+ assertEquals(4.0f, result.getW());
+ }
+
+ @Test
+ public void testAddShouldNotModifyOriginalVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vectorToAdd = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+
+ // Act
+ originalVector.add(vectorToAdd);
+
+ // Assert that original vector remains unchanged
+ assertEquals(1.0f, originalVector.getX());
+ assertEquals(2.0f, originalVector.getY());
+ assertEquals(3.0f, originalVector.getZ());
+ assertEquals(4.0f, originalVector.getW());
+
+ // Assert that the other vector remains unchanged
+ assertEquals(4.0f, vectorToAdd.getX());
+ assertEquals(3.0f, vectorToAdd.getY());
+ assertEquals(2.0f, vectorToAdd.getZ());
+ assertEquals(1.0f, vectorToAdd.getW());
+ }
+
+ @Test
+ public void testAddShouldNotModifyOriginalVectorWhenAddingZeroVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f zeroVector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ originalVector.add(zeroVector);
+
+ // Assert that original vector remains unchanged
+ assertEquals(1.0f, originalVector.getX());
+ assertEquals(2.0f, originalVector.getY());
+ assertEquals(3.0f, originalVector.getZ());
+ assertEquals(4.0f, originalVector.getW());
+
+ // Assert that zero vector remains unchanged
+ assertEquals(0.0f, zeroVector.getX());
+ assertEquals(0.0f, zeroVector.getY());
+ assertEquals(0.0f, zeroVector.getZ());
+ assertEquals(0.0f, zeroVector.getW());
+ }
+
+ @Test
+ public void testAddShouldNotModifyOtherVectorWhenAddingNonZeroVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vectorToAdd = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ // Act
+ originalVector.add(vectorToAdd);
+
+ // Assert that other vector (vectorToAdd) remains unchanged
+ assertEquals(1.0f, vectorToAdd.getX());
+ assertEquals(1.0f, vectorToAdd.getY());
+ assertEquals(1.0f, vectorToAdd.getZ());
+ assertEquals(1.0f, vectorToAdd.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Add Local
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testAddLocalValidVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vectorToAdd = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+
+ // Act
+ Vector4f result = originalVector.addLocal(vectorToAdd);
+
+ // Assert that the original vector is updated correctly
+ assertEquals(5.0f, originalVector.getX());
+ assertEquals(5.0f, originalVector.getY());
+ assertEquals(5.0f, originalVector.getZ());
+ assertEquals(5.0f, originalVector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(originalVector, result);
+ }
+
+ @Test
+ public void testAddLocalNullVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act and Assert: ensure IllegalArgumentException is thrown when adding null
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ originalVector.addLocal(null);
+ });
+ }
+
+ @Test
+ public void testAddLocalZeroVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f zeroVector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ Vector4f result = originalVector.addLocal(zeroVector);
+
+ // Assert that the original vector remains the same (adding zero doesn't change it)
+ assertEquals(1.0f, originalVector.getX());
+ assertEquals(2.0f, originalVector.getY());
+ assertEquals(3.0f, originalVector.getZ());
+ assertEquals(4.0f, originalVector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(originalVector, result);
+ }
+
+ @Test
+ public void testAddLocalShouldNotModifyOtherVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vectorToAdd = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+
+ // Act
+ originalVector.addLocal(vectorToAdd);
+
+ // Assert that the other vector (vectorToAdd) remains unchanged
+ assertEquals(4.0f, vectorToAdd.getX());
+ assertEquals(3.0f, vectorToAdd.getY());
+ assertEquals(2.0f, vectorToAdd.getZ());
+ assertEquals(1.0f, vectorToAdd.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Subtract
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testSubtractValidVector() {
+ // Arrange
+ Vector4f vector1 = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+ Vector4f vector2 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ Vector4f result = vector1.subtract(vector2);
+
+ // Assert
+ assertEquals(3.0f, result.getX());
+ assertEquals(1.0f, result.getY());
+ assertEquals(-1.0f, result.getZ());
+ assertEquals(-3.0f, result.getW());
+ }
+
+ @Test
+ public void testSubtractNullVector() {
+ Vector4f vector1 = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+
+ assertThrows(IllegalArgumentException.class, () -> vector1.subtract(null));
+ }
+
+ @Test
+ public void testSubtractZeroVector() {
+ Vector4f vector1 = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+ Vector4f zeroVector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ Vector4f result = vector1.subtract(zeroVector);
+
+ assertEquals(4.0f, result.getX());
+ assertEquals(3.0f, result.getY());
+ assertEquals(2.0f, result.getZ());
+ assertEquals(1.0f, result.getW());
+ }
+
+ @Test
+ public void testSubtractShouldNotModifyOriginalVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+ Vector4f vectorToSubtract = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ originalVector.subtract(vectorToSubtract);
+
+ // Assert that original vector remains unchanged
+ assertEquals(4.0f, originalVector.getX());
+ assertEquals(3.0f, originalVector.getY());
+ assertEquals(2.0f, originalVector.getZ());
+ assertEquals(1.0f, originalVector.getW());
+
+ // Assert that the other vector remains unchanged
+ assertEquals(1.0f, vectorToSubtract.getX());
+ assertEquals(2.0f, vectorToSubtract.getY());
+ assertEquals(3.0f, vectorToSubtract.getZ());
+ assertEquals(4.0f, vectorToSubtract.getW());
+ }
+
+ @Test
+ public void testSubtractShouldNotModifyOriginalVectorWhenSubtractingZeroVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+ Vector4f zeroVector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ originalVector.subtract(zeroVector);
+
+ // Assert that original vector remains unchanged
+ assertEquals(4.0f, originalVector.getX());
+ assertEquals(3.0f, originalVector.getY());
+ assertEquals(2.0f, originalVector.getZ());
+ assertEquals(1.0f, originalVector.getW());
+
+ // Assert that zero vector remains unchanged
+ assertEquals(0.0f, zeroVector.getX());
+ assertEquals(0.0f, zeroVector.getY());
+ assertEquals(0.0f, zeroVector.getZ());
+ assertEquals(0.0f, zeroVector.getW());
+ }
+
+ @Test
+ public void testSubtractShouldNotModifyOtherVectorWhenSubtractingNonZeroVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(4.0f, 3.0f, 2.0f, 1.0f);
+ Vector4f vectorToSubtract = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
+
+ // Act
+ originalVector.subtract(vectorToSubtract);
+
+ // Assert that other vector (vectorToSubtract) remains unchanged
+ assertEquals(1.0f, vectorToSubtract.getX());
+ assertEquals(1.0f, vectorToSubtract.getY());
+ assertEquals(1.0f, vectorToSubtract.getZ());
+ assertEquals(1.0f, vectorToSubtract.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Subtract Local
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testSubtractLocalValidVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(5.0f, 5.0f, 5.0f, 5.0f);
+ Vector4f vectorToSubtract = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ Vector4f result = originalVector.subtractLocal(vectorToSubtract);
+
+ // Assert that the original vector is updated correctly
+ assertEquals(4.0f, originalVector.getX());
+ assertEquals(3.0f, originalVector.getY());
+ assertEquals(2.0f, originalVector.getZ());
+ assertEquals(1.0f, originalVector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(originalVector, result);
+ }
+
+ @Test
+ public void testSubtractLocalNullVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(5.0f, 5.0f, 5.0f, 5.0f);
+
+ // Act and Assert: ensure IllegalArgumentException is thrown when subtracting null
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ originalVector.subtractLocal(null);
+ });
+ }
+
+ @Test
+ public void testSubtractLocalZeroVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(5.0f, 5.0f, 5.0f, 5.0f);
+ Vector4f zeroVector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ Vector4f result = originalVector.subtractLocal(zeroVector);
+
+ // Assert that the original vector remains the same (subtracting zero doesn't change it)
+ assertEquals(5.0f, originalVector.getX());
+ assertEquals(5.0f, originalVector.getY());
+ assertEquals(5.0f, originalVector.getZ());
+ assertEquals(5.0f, originalVector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(originalVector, result);
+ }
+
+ @Test
+ public void testSubtractLocalShouldNotModifyOtherVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(5.0f, 5.0f, 5.0f, 5.0f);
+ Vector4f vectorToSubtract = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ originalVector.subtractLocal(vectorToSubtract);
+
+ // Assert that the other vector (vectorToSubtract) remains unchanged
+ assertEquals(1.0f, vectorToSubtract.getX());
+ assertEquals(2.0f, vectorToSubtract.getY());
+ assertEquals(3.0f, vectorToSubtract.getZ());
+ assertEquals(4.0f, vectorToSubtract.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Scalar Multiplication
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testMultiplyValidScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ float scalar = 2.0f;
+
+ // Act
+ Vector4f result = vector.multiply(scalar);
+
+ // Assert that each component is multiplied correctly
+ assertEquals(2.0f, result.getX());
+ assertEquals(4.0f, result.getY());
+ assertEquals(6.0f, result.getZ());
+ assertEquals(8.0f, result.getW());
+ }
+
+ @Test
+ public void testMultiplyByZero() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = 0.0f;
+
+ // Act
+ Vector4f result = vector.multiply(scalar);
+
+ // This approach ensures that 0.0f and -0.0f are considered equal when performing
+ // assertions, as they are numerically the same but may differ in representation.
+ // Assert that each component is zero, allowing for floating-point precision issues
+ assertEquals(0.0f, result.getX(), 1e-6f); // delta of 1e-6f
+ assertEquals(0.0f, result.getY(), 1e-6f);
+ assertEquals(0.0f, result.getZ(), 1e-6f);
+ assertEquals(0.0f, result.getW(), 1e-6f);
+ }
+
+ @Test
+ public void testMultiplyByNegativeScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = -2.0f;
+
+ // Act
+ Vector4f result = vector.multiply(scalar);
+
+ // Assert that each component is negated correctly
+ assertEquals(-2.0f, result.getX());
+ assertEquals(4.0f, result.getY());
+ assertEquals(-6.0f, result.getZ());
+ assertEquals(8.0f, result.getW());
+ }
+
+ @Test
+ public void testMultiplyWithZeroVector() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ float scalar = 0.0f;
+
+ // Act
+ Vector4f result = vector.multiply(scalar);
+
+ // Assert that result is zero vector
+ assertEquals(0.0f, result.getX());
+ assertEquals(0.0f, result.getY());
+ assertEquals(0.0f, result.getZ());
+ assertEquals(0.0f, result.getW());
+ }
+
+ @Test
+ public void testMultiplyShouldNotModifyOriginalVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = 2.0f;
+
+ // Act
+ originalVector.multiply(scalar);
+
+ // Assert that original vector remains unchanged
+ assertEquals(1.0f, originalVector.getX());
+ assertEquals(-2.0f, originalVector.getY());
+ assertEquals(3.0f, originalVector.getZ());
+ assertEquals(-4.0f, originalVector.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Scalar Multiplication
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testMultiplyLocalByPositiveScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = 2.0f;
+
+ // Act
+ Vector4f result = vector.multiplyLocal(scalar);
+
+ // Assert: verify that the original vector is updated correctly
+ assertEquals(2.0f, vector.getX());
+ assertEquals(-4.0f, vector.getY());
+ assertEquals(6.0f, vector.getZ());
+ assertEquals(-8.0f, vector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testMultiplyLocalByNegativeScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = -2.0f;
+
+ // Act
+ Vector4f result = vector.multiplyLocal(scalar);
+
+ // Assert: verify that the original vector is updated correctly
+ assertEquals(-2.0f, vector.getX());
+ assertEquals(4.0f, vector.getY());
+ assertEquals(-6.0f, vector.getZ());
+ assertEquals(8.0f, vector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testMultiplyLocalByZero() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = 0.0f;
+
+ // Act
+ Vector4f result = vector.multiplyLocal(scalar);
+
+ // Assert: verify that all components become zero
+ assertEquals(0.0f, vector.getX(), 1e-6f);
+ assertEquals(0.0f, vector.getY(), 1e-6f);
+ assertEquals(0.0f, vector.getZ(), 1e-6f);
+ assertEquals(0.0f, vector.getW(), 1e-6f);
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testMultiplyLocalByOne() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = 1.0f;
+
+ // Act
+ Vector4f result = vector.multiplyLocal(scalar);
+
+ // Assert: verify that the vector remains unchanged
+ assertEquals(1.0f, vector.getX());
+ assertEquals(-2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(-4.0f, vector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testMultiplyLocalByFraction() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, 4.0f, 6.0f, 8.0f);
+ float scalar = 0.5f;
+
+ // Act
+ Vector4f result = vector.multiplyLocal(scalar);
+
+ // Assert: verify that the vector components are halved
+ assertEquals(1.0f, vector.getX());
+ assertEquals(2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(4.0f, vector.getW());
+
+ // Assert that the result is the same as the modified original vector
+ assertSame(vector, result);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Scalar Division
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDivideByPositiveScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = 2.0f;
+
+ // Act
+ Vector4f result = vector.divide(scalar);
+
+ // Assert: verify that the division works correctly
+ assertEquals(1.0f, result.getX());
+ assertEquals(-2.0f, result.getY());
+ assertEquals(3.0f, result.getZ());
+ assertEquals(-4.0f, result.getW());
+ }
+
+ @Test
+ public void testDivideByNegativeScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = -2.0f;
+
+ // Act
+ Vector4f result = vector.divide(scalar);
+
+ // Assert: verify that the division works correctly with negative scalar
+ assertEquals(-1.0f, result.getX());
+ assertEquals(2.0f, result.getY());
+ assertEquals(-3.0f, result.getZ());
+ assertEquals(4.0f, result.getW());
+ }
+
+ @Test
+ public void testDivideByZero() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = 0.0f;
+
+ // Act and Assert: ensure that dividing by zero throws an ArithmeticException
+ assertThrows(
+ ArithmeticException.class,
+ () -> {
+ vector.divide(scalar);
+ });
+ }
+
+ @Test
+ public void testDivideByOne() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = 1.0f;
+
+ // Act
+ Vector4f result = vector.divide(scalar);
+
+ // Assert: verify that the vector remains unchanged when divided by one
+ assertEquals(2.0f, result.getX());
+ assertEquals(-4.0f, result.getY());
+ assertEquals(6.0f, result.getZ());
+ assertEquals(-8.0f, result.getW());
+ }
+
+ @Test
+ public void testDivideByFraction() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, 4.0f, 6.0f, 8.0f);
+ float scalar = 0.5f;
+
+ // Act
+ Vector4f result = vector.divide(scalar);
+
+ // Assert: verify that the vector components are doubled when divided by a fraction
+ assertEquals(4.0f, result.getX());
+ assertEquals(8.0f, result.getY());
+ assertEquals(12.0f, result.getZ());
+ assertEquals(16.0f, result.getW());
+ }
+
+ @Test
+ public void testDivideShouldNotModifyOriginalVector() {
+ // Arrange
+ Vector4f originalVector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ float scalar = 2.0f;
+
+ // Act
+ originalVector.divide(scalar);
+
+ // Assert that original vector remains unchanged
+ assertEquals(1.0f, originalVector.getX());
+ assertEquals(-2.0f, originalVector.getY());
+ assertEquals(3.0f, originalVector.getZ());
+ assertEquals(-4.0f, originalVector.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Scalar Division Local
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDivideLocalByPositiveScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = 2.0f;
+
+ // Act
+ Vector4f result = vector.divideLocal(scalar);
+
+ // Assert: verify that the division works correctly and the vector is updated in place
+ assertEquals(1.0f, vector.getX());
+ assertEquals(-2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(-4.0f, vector.getW());
+
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testDivideLocalByNegativeScalar() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = -2.0f;
+
+ // Act
+ Vector4f result = vector.divideLocal(scalar);
+
+ // Assert: verify that the division works correctly with negative scalar and the vector is
+ // updated
+ assertEquals(-1.0f, vector.getX());
+ assertEquals(2.0f, vector.getY());
+ assertEquals(-3.0f, vector.getZ());
+ assertEquals(4.0f, vector.getW());
+
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testDivideLocalByZero() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = 0.0f;
+
+ // Act and Assert: ensure that dividing by zero throws an ArithmeticException
+ assertThrows(
+ ArithmeticException.class,
+ () -> {
+ vector.divideLocal(scalar);
+ });
+ }
+
+ @Test
+ public void testDivideLocalByOne() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = 1.0f;
+
+ // Act
+ Vector4f result = vector.divideLocal(scalar);
+
+ // Assert: verify that the vector remains unchanged when divided by one
+ assertEquals(2.0f, vector.getX());
+ assertEquals(-4.0f, vector.getY());
+ assertEquals(6.0f, vector.getZ());
+ assertEquals(-8.0f, vector.getW());
+
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testDivideLocalByFraction() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, 4.0f, 6.0f, 8.0f);
+ float scalar = 0.5f;
+
+ // Act
+ Vector4f result = vector.divideLocal(scalar);
+
+ // Assert: verify that the vector components are doubled when divided by a fraction
+ assertEquals(4.0f, vector.getX());
+ assertEquals(8.0f, vector.getY());
+ assertEquals(12.0f, vector.getZ());
+ assertEquals(16.0f, vector.getW());
+
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testDivideLocalWithNegativeFraction() {
+ // Arrange
+ Vector4f vector = new Vector4f(2.0f, -4.0f, 6.0f, -8.0f);
+ float scalar = -0.5f;
+
+ // Act
+ Vector4f result = vector.divideLocal(scalar);
+
+ // Assert: verify that the vector components are halved and the signs are flipped
+ assertEquals(-4.0f, vector.getX());
+ assertEquals(8.0f, vector.getY());
+ assertEquals(-12.0f, vector.getZ());
+ assertEquals(16.0f, vector.getW());
+
+ assertSame(vector, result);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Negate
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testNegate() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act
+ Vector4f negatedVector = vector.negate();
+
+ // Assert: verify that the negated vector has the opposite components
+ assertEquals(-1.0f, negatedVector.getX());
+ assertEquals(2.0f, negatedVector.getY());
+ assertEquals(-3.0f, negatedVector.getZ());
+ assertEquals(4.0f, negatedVector.getW());
+ // Verify that the original vector is unchanged
+ assertEquals(1.0f, vector.getX());
+ assertEquals(-2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(-4.0f, vector.getW());
+
+ // Ensure new instance is returned
+ assertNotSame(vector, negatedVector);
+ }
+
+ @Test
+ public void testNegateWithZero() {
+ // Arrange
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ Vector4f negatedVector = vector.negate();
+
+ // Assert: verify that negating a zero vector still results in a zero vector
+ assertEquals(0.0f, negatedVector.getX());
+ assertEquals(0.0f, negatedVector.getY());
+ assertEquals(0.0f, negatedVector.getZ());
+ assertEquals(0.0f, negatedVector.getW());
+
+ // Verify that the original zero vector is unchanged
+ assertEquals(0.0f, vector.getX());
+ assertEquals(0.0f, vector.getY());
+ assertEquals(0.0f, vector.getZ());
+ assertEquals(0.0f, vector.getW());
+
+ // Ensure new instance is returned
+ assertNotSame(vector, negatedVector);
+ }
+
+ @Test
+ public void testNegateWithNegativeValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(-1.0f, -2.0f, -3.0f, -4.0f);
+
+ // Act
+ Vector4f negatedVector = vector.negate();
+
+ // Assert: verify that negating a vector with negative values results in a vector with positive
+ // values
+ assertEquals(1.0f, negatedVector.getX());
+ assertEquals(2.0f, negatedVector.getY());
+ assertEquals(3.0f, negatedVector.getZ());
+ assertEquals(4.0f, negatedVector.getW());
+
+ // Verify that the original vector is unchanged
+ assertEquals(-1.0f, vector.getX());
+ assertEquals(-2.0f, vector.getY());
+ assertEquals(-3.0f, vector.getZ());
+ assertEquals(-4.0f, vector.getW());
+
+ // Ensure new instance is returned
+ assertNotSame(vector, negatedVector);
+ }
+
+ @Test
+ public void testNegateWithMixedValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -1.0f, 0.0f, 3.0f);
+
+ // Act
+ Vector4f negatedVector = vector.negate();
+
+ // Assert: verify that mixed values are negated correctly in the new vector
+ assertEquals(-1.0f, negatedVector.getX());
+ assertEquals(1.0f, negatedVector.getY());
+ assertEquals(0.0f, negatedVector.getZ());
+ assertEquals(-3.0f, negatedVector.getW());
+
+ // Verify that the original vector is unchanged
+ assertEquals(1.0f, vector.getX());
+ assertEquals(-1.0f, vector.getY());
+ assertEquals(0.0f, vector.getZ());
+ assertEquals(3.0f, vector.getW());
+
+ // Ensure new instance is returned
+ assertNotSame(vector, negatedVector);
+ }
+
+ @Test
+ public void testNegateTwice() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act
+ Vector4f negatedVectorOnce = vector.negate();
+ Vector4f negatedVectorTwice = negatedVectorOnce.negate();
+
+ // Assert: verify that negating twice returns the original vector
+ assertEquals(1.0f, negatedVectorTwice.getX());
+ assertEquals(-2.0f, negatedVectorTwice.getY());
+ assertEquals(3.0f, negatedVectorTwice.getZ());
+ assertEquals(-4.0f, negatedVectorTwice.getW());
+
+ // Ensure new instance is returned
+ assertNotSame(vector, negatedVectorOnce);
+ assertNotSame(vector, negatedVectorTwice);
+ assertNotSame(negatedVectorOnce, negatedVectorTwice);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Negate Local
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testNegateLocal() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act
+ Vector4f result = vector.negateLocal();
+
+ // Assert: verify that the vector components are negated correctly in place
+ assertEquals(-1.0f, vector.getX());
+ assertEquals(2.0f, vector.getY());
+ assertEquals(-3.0f, vector.getZ());
+ assertEquals(4.0f, vector.getW());
+
+ // Ensure self reference is returned
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNegateLocalWithZero() {
+ // Arrange
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ Vector4f result = vector.negateLocal();
+
+ // Assert: verify that negating a zero vector still results in a zero vector
+ assertEquals(0.0f, vector.getX());
+ assertEquals(0.0f, vector.getY());
+ assertEquals(0.0f, vector.getZ());
+ assertEquals(0.0f, vector.getW());
+
+ // Ensure self reference is returned
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNegateLocalWithNegativeValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(-1.0f, -2.0f, -3.0f, -4.0f);
+
+ // Act
+ Vector4f result = vector.negateLocal();
+
+ // Assert: verify that the negation of negative values results in positive values
+ assertEquals(1.0f, vector.getX());
+ assertEquals(2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(4.0f, vector.getW());
+
+ // Ensure self reference is returned
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNegateLocalWithMixedValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -1.0f, 0.0f, 3.0f);
+
+ // Act
+ Vector4f result = vector.negateLocal();
+
+ // Assert: verify that mixed values are negated correctly
+ assertEquals(-1.0f, vector.getX());
+ assertEquals(1.0f, vector.getY());
+ assertEquals(0.0f, vector.getZ());
+ assertEquals(-3.0f, vector.getW());
+
+ // Ensure self reference is returned
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNegateLocalTwice() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act
+ vector.negateLocal();
+ Vector4f result = vector.negateLocal(); // negate again
+
+ // Assert: verify that negating twice brings the vector back to its original state
+ assertEquals(1.0f, vector.getX());
+ assertEquals(-2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(-4.0f, vector.getW());
+
+ // Ensure self reference is returned
+ assertSame(vector, result);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Length
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testLength() {
+ // Arrange
+ Vector4f vector = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f);
+
+ // Act
+ float length = vector.length();
+
+ // Assert: Verify the length is calculated correctly (sqrt(3^2 + 4^2) = 5)
+ assertEquals(5.0f, length, 0.0001f);
+ }
+
+ @Test
+ public void testLengthZeroVector() {
+ // Arrange
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ float length = vector.length();
+
+ // Assert: The length of a zero vector should be 0
+ assertEquals(0.0f, length, 0.0001f);
+ }
+
+ @Test
+ public void testLengthNegativeValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(-3.0f, -4.0f, 0.0f, 0.0f);
+
+ // Act
+ float length = vector.length();
+
+ // Assert: The length is the same as for positive values, sqrt((-3)^2 + (-4)^2) = 5
+ assertEquals(5.0f, length, 0.0001f);
+ }
+
+ @Test
+ public void testLengthMixedValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act
+ float length = vector.length();
+
+ // Assert: The length is sqrt(1^2 + (-2)^2 + 3^2 + (-4)^2) = sqrt(1 + 4 + 9 + 16) = sqrt(30)
+ assertEquals(Math.sqrt(30), length, 0.0001f);
+ }
+
+ @Test
+ public void testLengthUnitVector() {
+ // Arrange: A unit vector with length 1
+ Vector4f vector = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ float length = vector.length();
+
+ // Assert: The length of a unit vector should be 1
+ assertEquals(1.0f, length, 0.0001f);
+ }
+
+ @Test
+ public void testLengthLargeValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(1000.0f, 2000.0f, 3000.0f, 4000.0f);
+
+ // Act
+ float length = vector.length();
+
+ // Assert: Verify that the length is computed correctly for large values
+ assertEquals(
+ Math.sqrt(1000.0f * 1000.0f + 2000.0f * 2000.0f + 3000.0f * 3000.0f + 4000.0f * 4000.0f),
+ length,
+ 0.0001f);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Length squared
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testLengthSquared() {
+ // Arrange
+ Vector4f vector = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f);
+
+ // Act
+ float lengthSquared = vector.lengthSquared();
+
+ // Assert: Verify the length squared is calculated correctly (3^2 + 4^2 = 9 + 16 = 25)
+ assertEquals(25.0f, lengthSquared, 0.0001f);
+ }
+
+ @Test
+ public void testLengthSquaredZeroVector() {
+ // Arrange
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ float lengthSquared = vector.lengthSquared();
+
+ // Assert: The length squared of a zero vector should be 0
+ assertEquals(0.0f, lengthSquared, 0.0001f);
+ }
+
+ @Test
+ public void testLengthSquaredNegativeValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(-3.0f, -4.0f, 0.0f, 0.0f);
+
+ // Act
+ float lengthSquared = vector.lengthSquared();
+
+ // Assert: The length squared is the same as for positive values (3^2 + 4^2 = 9 + 16 = 25)
+ assertEquals(25.0f, lengthSquared, 0.0001f);
+ }
+
+ @Test
+ public void testLengthSquaredMixedValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act
+ float lengthSquared = vector.lengthSquared();
+
+ // Assert: The length squared is calculated as (1^2 + (-2)^2 + 3^2 + (-4)^2) = 1 + 4 + 9 + 16 =
+ // 30
+ assertEquals(30.0f, lengthSquared, 0.0001f);
+ }
+
+ @Test
+ public void testLengthSquaredUnitVector() {
+ // Arrange: A unit vector with length 1, so length squared should also be 1
+ Vector4f vector = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act
+ float lengthSquared = vector.lengthSquared();
+
+ // Assert: The length squared of a unit vector should be 1
+ assertEquals(1.0f, lengthSquared, 0.0001f);
+ }
+
+ @Test
+ public void testLengthSquaredLargeValues() {
+ // Arrange
+ Vector4f vector = new Vector4f(1000.0f, 2000.0f, 3000.0f, 4000.0f);
+
+ // Act
+ float lengthSquared = vector.lengthSquared();
+
+ // Assert: Verify that the length squared is computed correctly for large values
+ assertEquals(
+ 1000.0f * 1000.0f + 2000.0f * 2000.0f + 3000.0f * 3000.0f + 4000.0f * 4000.0f,
+ lengthSquared,
+ 0.0001f);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Normalize
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testNormalize() {
+ // Arrange: Vector with a known magnitude
+ Vector4f vector = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the vector
+ Vector4f normalizedVector = vector.normalize();
+
+ // Assert: The length of the normalized vector should be 1
+ assertEquals(1.0f, normalizedVector.length(), 0.0001f);
+
+ // Assert: The normalized vector should still point in the same direction
+ // The expected normalized vector is (3/5, 4/5, 0, 0) because length of (3, 4, 0, 0) = 5
+ assertEquals(3.0f / 5.0f, normalizedVector.getX(), 0.0001f);
+ assertEquals(4.0f / 5.0f, normalizedVector.getY(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getZ(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getW(), 0.0001f);
+
+ assertNotSame(vector, normalizedVector);
+ }
+
+ @Test
+ public void testNormalizeZeroVector() {
+ // Arrange: A zero vector, which cannot be normalized
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the zero vector
+ Vector4f normalizedVector = vector.normalize();
+
+ // Assert: The result should be a zero vector since a zero vector cannot be normalized
+ assertEquals(0.0f, normalizedVector.getX(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getY(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getZ(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getW(), 0.0001f);
+
+ assertNotSame(vector, normalizedVector);
+ }
+
+ @Test
+ public void testNormalizeUnitVector() {
+ // Arrange: A unit vector (length = 1)
+ Vector4f vector = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the unit vector
+ Vector4f normalizedVector = vector.normalize();
+
+ // Assert: The normalized vector should be the same as the input vector
+ assertEquals(1.0f, normalizedVector.getX(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getY(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getZ(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getW(), 0.0001f);
+
+ assertNotSame(vector, normalizedVector);
+ }
+
+ @Test
+ public void testNormalizeNegativeValues() {
+ // Arrange: A vector with negative components
+ Vector4f vector = new Vector4f(-3.0f, -4.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the vector
+ Vector4f normalizedVector = vector.normalize();
+
+ // Assert: The normalized vector should have a magnitude of 1 and the same direction
+ assertEquals(1.0f, normalizedVector.length(), 0.0001f);
+
+ // Assert: The normalized vector should be (-3/5, -4/5, 0, 0)
+ assertEquals(-3.0f / 5.0f, normalizedVector.getX(), 0.0001f);
+ assertEquals(-4.0f / 5.0f, normalizedVector.getY(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getZ(), 0.0001f);
+ assertEquals(0.0f, normalizedVector.getW(), 0.0001f);
+
+ assertNotSame(vector, normalizedVector);
+ }
+
+ @Test
+ public void testNormalizeMixedValues() {
+ // Arrange: A vector with mixed positive and negative components
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act: Normalize the vector
+ Vector4f normalizedVector = vector.normalize();
+
+ // Assert: The length of the normalized vector should be 1
+ assertEquals(1.0f, normalizedVector.length(), 0.0001f);
+
+ // Assert: The normalized vector should point in the same direction as the input vector
+ float length =
+ (float) Math.sqrt(1.0f * 1.0f + (-2.0f) * (-2.0f) + 3.0f * 3.0f + (-4.0f) * (-4.0f));
+ float invLength = 1.0f / length;
+
+ assertEquals(1.0f * invLength, normalizedVector.getX(), 0.0001f);
+ assertEquals(-2.0f * invLength, normalizedVector.getY(), 0.0001f);
+ assertEquals(3.0f * invLength, normalizedVector.getZ(), 0.0001f);
+ assertEquals(-4.0f * invLength, normalizedVector.getW(), 0.0001f);
+
+ assertNotSame(vector, normalizedVector);
+ }
+
+ @Test
+ public void testNormalizeDoesNotAlterOriginalVector() {
+ // Arrange: Create a non-zero vector
+ Vector4f originalVector = new Vector4f(2.0f, 3.0f, 4.0f, 5.0f);
+ // Store the original values
+ float originalX = originalVector.getX();
+ float originalY = originalVector.getY();
+ float originalZ = originalVector.getZ();
+ float originalW = originalVector.getW();
+
+ // Act: Normalize the vector
+ Vector4f normalizedVector = originalVector.normalize();
+
+ // Assert: Ensure the original vector is not modified
+ assertEquals(originalX, originalVector.getX(), 0.0001f);
+ assertEquals(originalY, originalVector.getY(), 0.0001f);
+ assertEquals(originalZ, originalVector.getZ(), 0.0001f);
+ assertEquals(originalW, originalVector.getW(), 0.0001f);
+
+ // Optionally assert that the normalized vector has a length of 1
+ assertEquals(1.0f, normalizedVector.length(), 0.0001f);
+
+ assertNotSame(originalVector, normalizedVector);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Normalize Local
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testNormalizeLocal() {
+ // Arrange: Vector with a known magnitude
+ Vector4f vector = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the vector using normalizeLocal
+ Vector4f result = vector.normalizeLocal();
+
+ // Assert: The length of the normalized vector should be 1
+ assertEquals(1.0f, vector.length(), 0.0001f);
+
+ // Assert: The normalized vector should still point in the same direction
+ // The expected normalized vector is (3/5, 4/5, 0, 0) because length of (3, 4, 0, 0) = 5
+ assertEquals(3.0f / 5.0f, vector.getX(), 0.0001f);
+ assertEquals(4.0f / 5.0f, vector.getY(), 0.0001f);
+ assertEquals(0.0f, vector.getZ(), 0.0001f);
+ assertEquals(0.0f, vector.getW(), 0.0001f);
+
+ // Assert: The original vector should have been modified in place (same reference)
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNormalizeLocalZeroVector() {
+ // Arrange: A zero vector, which cannot be normalized
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the zero vector using normalizeLocal
+ Vector4f result = vector.normalizeLocal();
+
+ // Assert: The result should still be a zero vector since a zero vector cannot be normalized
+ assertEquals(0.0f, vector.getX(), 0.0001f);
+ assertEquals(0.0f, vector.getY(), 0.0001f);
+ assertEquals(0.0f, vector.getZ(), 0.0001f);
+ assertEquals(0.0f, vector.getW(), 0.0001f);
+
+ // Assert: The original vector should have been modified in place (same reference)
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNormalizeLocalUnitVector() {
+ // Arrange: A unit vector (length = 1)
+ Vector4f vector = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the unit vector using normalizeLocal
+ Vector4f result = vector.normalizeLocal();
+
+ // Assert: The normalized vector should be the same as the input vector
+ assertEquals(1.0f, vector.getX(), 0.0001f);
+ assertEquals(0.0f, vector.getY(), 0.0001f);
+ assertEquals(0.0f, vector.getZ(), 0.0001f);
+ assertEquals(0.0f, vector.getW(), 0.0001f);
+
+ // Assert: The original vector should have been modified in place (same reference)
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNormalizeLocalNegativeValues() {
+ // Arrange: A vector with negative components
+ Vector4f vector = new Vector4f(-3.0f, -4.0f, 0.0f, 0.0f);
+
+ // Act: Normalize the vector using normalizeLocal
+ Vector4f result = vector.normalizeLocal();
+
+ // Assert: The normalized vector should have a magnitude of 1 and the same direction
+ assertEquals(1.0f, vector.length(), 0.0001f);
+
+ // Assert: The normalized vector should be (-3/5, -4/5, 0, 0)
+ assertEquals(-3.0f / 5.0f, vector.getX(), 0.0001f);
+ assertEquals(-4.0f / 5.0f, vector.getY(), 0.0001f);
+ assertEquals(0.0f, vector.getZ(), 0.0001f);
+ assertEquals(0.0f, vector.getW(), 0.0001f);
+
+ // Assert: The original vector should have been modified in place (same reference)
+ assertSame(vector, result);
+ }
+
+ @Test
+ public void testNormalizeLocalMixedValues() {
+ // Arrange: A vector with mixed positive and negative components
+ Vector4f vector = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+
+ // Act: Normalize the vector using normalizeLocal
+ Vector4f result = vector.normalizeLocal();
+
+ // Assert: The length of the normalized vector should be 1
+ assertEquals(1.0f, vector.length(), 0.0001f);
+
+ // Assert: The normalized vector should point in the same direction as the input vector
+ float length =
+ (float) Math.sqrt(1.0f * 1.0f + (-2.0f) * (-2.0f) + 3.0f * 3.0f + (-4.0f) * (-4.0f));
+ float invLength = 1.0f / length;
+
+ assertEquals(1.0f * invLength, vector.getX(), 0.0001f);
+ assertEquals(-2.0f * invLength, vector.getY(), 0.0001f);
+ assertEquals(3.0f * invLength, vector.getZ(), 0.0001f);
+ assertEquals(-4.0f * invLength, vector.getW(), 0.0001f);
+
+ // Assert: The original vector should have been modified in place (same reference)
+ assertSame(vector, result);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Is Zero
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testIsZeroWithZeroVector() {
+ // Arrange: A zero vector (all components are zero)
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Check if the vector is zero
+ boolean result = vector.isZero();
+
+ // Assert: The result should be true for a zero vector
+ assertTrue(result);
+ }
+
+ @Test
+ public void testIsZeroWithNonZeroVector() {
+ // Arrange: A vector with non-zero components
+ Vector4f vector = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Check if the vector is zero
+ boolean result = vector.isZero();
+
+ // Assert: The result should be false for a non-zero vector
+ assertFalse(result);
+ }
+
+ @Test
+ public void testIsZeroWithNegativeValues() {
+ // Arrange: A vector with negative components
+ Vector4f vector = new Vector4f(-1.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Check if the vector is zero
+ boolean result = vector.isZero();
+
+ // Assert: The result should be false for a vector with negative components
+ assertFalse(result);
+ }
+
+ @Test
+ public void testIsZeroWithMixedNonZeroValues() {
+ // Arrange: A vector with mixed non-zero components
+ Vector4f vector = new Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
+
+ // Act: Check if the vector is zero
+ boolean result = vector.isZero();
+
+ // Assert: The result should be false for a vector with mixed non-zero components
+ assertFalse(result);
+ }
+
+ @Test
+ public void testIsZeroWithAllNonZeroComponents() {
+ // Arrange: A vector with all non-zero components
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act: Check if the vector is zero
+ boolean result = vector.isZero();
+
+ // Assert: The result should be false for a vector with all non-zero components
+ assertFalse(result);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Dot
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDotProductWithZeroVector() {
+ // Arrange: A vector and a zero vector
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Act: Calculate the dot product
+ float result = vector1.dot(vector2);
+
+ // Assert: The dot product of any vector with a zero vector should be 0
+ assertEquals(0.0f, result, 0.0001f);
+ }
+
+ @Test
+ public void testDotProductWithSameVector() {
+ // Arrange: A vector and itself
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act: Calculate the dot product of the vector with itself
+ float result = vector1.dot(vector1);
+
+ // Assert: The dot product of a vector with itself is the sum of the squares of its components
+ // dot(vector1, vector1) = 1^2 + 2^2 + 3^2 + 4^2 = 30
+ assertEquals(30.0f, result, 0.0001f);
+ }
+
+ @Test
+ public void testDotProductWithOrthogonalVectors() {
+ // Arrange: Two orthogonal vectors
+ Vector4f vector1 = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f);
+ Vector4f vector2 = new Vector4f(0.0f, 1.0f, 0.0f, 0.0f);
+
+ // Act: Calculate the dot product
+ float result = vector1.dot(vector2);
+
+ // Assert: The dot product of orthogonal vectors should be 0
+ assertEquals(0.0f, result, 0.0001f);
+ }
+
+ @Test
+ public void testDotProductWithParallelVectors() {
+ // Arrange: Two parallel vectors
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(2.0f, 4.0f, 6.0f, 8.0f); // Scalar multiple of vector1
+
+ // Act: Calculate the dot product
+ float result = vector1.dot(vector2);
+
+ // Assert: The dot product of parallel vectors is the product of their magnitudes and cosine of
+ // the angle between them, which is 1 for parallel vectors
+ // dot(vector1, vector2) = 1*2 + 2*4 + 3*6 + 4*8 = 70
+ assertEquals(60.0f, result, 0.0001f);
+ }
+
+ @Test
+ public void testDotProductWithNegativeValues() {
+ // Arrange: A vector and another vector with negative components
+ Vector4f vector1 = new Vector4f(1.0f, -2.0f, 3.0f, -4.0f);
+ Vector4f vector2 = new Vector4f(-1.0f, 2.0f, -3.0f, 4.0f);
+
+ // Act: Calculate the dot product
+ float result = vector1.dot(vector2);
+
+ // Assert: The result should be the sum of the products of corresponding components
+ // dot(vector1, vector2) = (1*(-1)) + (-2*2) + (3*(-3)) + (-4*4)
+ // dot(vector1, vector2) = -1 - 4 - 9 - 16 = -30
+ assertEquals(-30.0f, result, 0.0001f);
+ }
+
+ @Test
+ public void testDotProductWithDifferentMagnitudeVectors() {
+ // Arrange: Two vectors with different magnitudes
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(2.0f, 1.0f, -1.0f, 3.0f);
+
+ // Act: Calculate the dot product
+ float result = vector1.dot(vector2);
+
+ // Assert: The dot product should be computed as expected
+ // dot(vector1, vector2) = (1*2) + (2*1) + (3*(-1)) + (4*3) = 2 + 2 - 3 + 12 = 13
+ assertEquals(13.0f, result, 0.0001f);
+ }
+
+ @Test
+ public void testDotProductWithNull() {
+ // Arrange: Create a vector
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act & Assert: Calling dot with null should throw NullPointerException
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ vector1.dot(null);
+ });
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Is Equal With Tolerance
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testIsEqualExactMatch() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act & Assert
+ assertTrue(v1.isEqual(v2, 0.0f));
+ }
+
+ @Test
+ public void testIsEqualWithinTolerance() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.01f, 2.01f, 3.01f, 4.01f);
+
+ // Act & Assert
+ assertTrue(v1.isEqual(v2, 0.02f));
+ }
+
+ @Test
+ public void testIsEqualExceedsTolerance() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.05f, 2.05f, 3.05f, 4.05f);
+
+ // Act & Assert
+ assertFalse(v1.isEqual(v2, 0.02f));
+ }
+
+ @Test
+ public void testIsEqualNegativeTolerance() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act & Assert
+ assertFalse(v1.isEqual(v2, -0.1f));
+ }
+
+ @Test
+ public void testIsEqualWithNullVector() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act & Assert
+ assertFalse(v1.isEqual(null, 0.1f));
+ }
+
+ @Test
+ public void testIsEqualWithSelf() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act & Assert
+ assertTrue(v1.isEqual(v1, 0.0f));
+ }
+
+ @Test
+ public void testIsEqualWithVerySmallTolerance() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.00001f, 2.00001f, 3.00001f, 4.00001f);
+ Vector4f v2 = new Vector4f(1.00002f, 2.00002f, 3.00002f, 4.00002f);
+
+ // Act & Assert
+ assertTrue(v1.isEqual(v2, 0.0001f)); // Should be equal within the small tolerance
+ assertFalse(v1.isEqual(v2, 0.000001f)); // Should not be equal with a stricter tolerance
+ }
+
+ @Test
+ public void testIsEqualLargeTolerance() {
+ // Arrange
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(10.0f, 20.0f, 30.0f, 40.0f);
+
+ // Act & Assert
+ assertTrue(v1.isEqual(v2, 100.0f));
+ }
+
+ @Test
+ public void testIsEqualNaN() {
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.0f, Float.NaN, 3.0f, 4.0f);
+ assertFalse(v1.isEqual(v2, 0.1f));
+ }
+
+ @Test
+ public void testIsEqualPositiveInfinity() {
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.0f, Float.POSITIVE_INFINITY, 3.0f, 4.0f);
+ assertFalse(v1.isEqual(v2, 0.1f));
+ }
+
+ @Test
+ public void testIsEqualNegativeInfinity() {
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.0f, Float.NEGATIVE_INFINITY, 3.0f, 4.0f);
+ assertFalse(v1.isEqual(v2, 0.1f));
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Lerp
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testLerpWithTZero() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 0.0f);
+
+ // Assert
+ assertEquals(start.getX(), result.getX(), 0.0001f);
+ assertEquals(start.getY(), result.getY(), 0.0001f);
+ assertEquals(start.getZ(), result.getZ(), 0.0001f);
+ assertEquals(start.getW(), result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpWithTOne() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 1.0f);
+
+ // Assert
+ assertEquals(end.getX(), result.getX(), 0.0001f);
+ assertEquals(end.getY(), result.getY(), 0.0001f);
+ assertEquals(end.getZ(), result.getZ(), 0.0001f);
+ assertEquals(end.getW(), result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpWithHalfwayT() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 0.5f);
+
+ // Assert
+ assertEquals(3.0f, result.getX(), 0.0001f);
+ assertEquals(4.0f, result.getY(), 0.0001f);
+ assertEquals(5.0f, result.getZ(), 0.0001f);
+ assertEquals(6.0f, result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpWithNegativeT() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, -0.5f);
+
+ // Assert
+ assertEquals(-1.0f, result.getX(), 0.0001f);
+ assertEquals(0.0f, result.getY(), 0.0001f);
+ assertEquals(1.0f, result.getZ(), 0.0001f);
+ assertEquals(2.0f, result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpWithOverOneT() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 1.5f);
+
+ // Assert
+ assertEquals(7.0f, result.getX(), 0.0001f);
+ assertEquals(8.0f, result.getY(), 0.0001f);
+ assertEquals(9.0f, result.getZ(), 0.0001f);
+ assertEquals(10.0f, result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpWithIdenticalStartAndEnd() {
+ // Arrange
+ Vector4f start = new Vector4f(3.0f, 3.0f, 3.0f, 3.0f);
+ Vector4f end = new Vector4f(3.0f, 3.0f, 3.0f, 3.0f);
+
+ // Act
+ Vector4f result = start.lerp(end, 0.5f);
+
+ // Assert
+ assertEquals(3.0f, result.getX(), 0.0001f);
+ assertEquals(3.0f, result.getY(), 0.0001f);
+ assertEquals(3.0f, result.getZ(), 0.0001f);
+ assertEquals(3.0f, result.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpReturnsNewInstanceAndOriginalVectorsUntouched() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f end = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+ Vector4f startCopy = start.clone();
+ Vector4f endCopy = end.clone();
+
+ // Act
+ Vector4f result = start.lerp(end, 0.5f);
+
+ // Assert
+ // Verify that a new instance is returned
+ assertNotSame(start, result);
+ assertNotSame(end, result);
+
+ // Verify that the original start vector is untouched
+ assertEquals(startCopy.getX(), start.getX(), 0.0001f);
+ assertEquals(startCopy.getY(), start.getY(), 0.0001f);
+ assertEquals(startCopy.getZ(), start.getZ(), 0.0001f);
+ assertEquals(startCopy.getW(), start.getW(), 0.0001f);
+
+ // Verify that the original end vector is untouched
+ assertEquals(endCopy.getX(), end.getX(), 0.0001f);
+ assertEquals(endCopy.getY(), end.getY(), 0.0001f);
+ assertEquals(endCopy.getZ(), end.getZ(), 0.0001f);
+ assertEquals(endCopy.getW(), end.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testLerpWithNullThrowsException() {
+ // Arrange
+ Vector4f start = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f other = null;
+ float t = 0.5f;
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ start.lerp(other, t);
+ });
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Distance To Squared
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDistanceSquaredToWithValidInput() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f v2 = new Vector4f(5, 6, 7, 8);
+ float expected = 64.0f; // (5-1)^2 + (6-2)^2 + (7-3)^2 + (8-4)^2 = 16 + 16 + 16 + 16
+ assertEquals(
+ expected,
+ v1.distanceSquaredTo(v2),
+ 0.0001,
+ "Distance squared should be calculated correctly.");
+ }
+
+ @Test
+ public void testDistanceSquaredToWithZeroVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f v2 = new Vector4f(0, 0, 0, 0);
+ float expected = 30.0f; // 1^2 + 2^2 + 3^2 + 4^2 = 1 + 4 + 9 + 16
+ assertEquals(
+ expected,
+ v1.distanceSquaredTo(v2),
+ 0.0001,
+ "Distance squared to zero vector should be correct.");
+ }
+
+ @Test
+ public void testDistanceSquaredToItself() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ float expected = 0.0f; // Distance to itself is always 0
+ assertEquals(
+ expected, v1.distanceSquaredTo(v1), 0.0001, "Distance squared to itself should be zero.");
+ }
+
+ @Test
+ public void testDistanceSquaredToWithNegativeComponents() {
+ Vector4f v1 = new Vector4f(-1, -2, -3, -4);
+ Vector4f v2 = new Vector4f(-5, -6, -7, -8);
+ float expected =
+ 64.0f; // (-5 - (-1))^2 + (-6 - (-2))^2 + (-7 - (-3))^2 + (-8 - (-4))^2 = 16 + 16 + 16 + 16
+ assertEquals(
+ expected,
+ v1.distanceSquaredTo(v2),
+ 0.0001,
+ "Distance squared with negative components should be correct.");
+ }
+
+ @Test
+ public void testDistanceSquaredToWithNullOtherVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ v1.distanceSquaredTo(null);
+ },
+ "Should throw IllegalArgumentException if the other vector is null.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Distance To
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testDistanceToWithValidInput() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f v2 = new Vector4f(5, 6, 7, 8);
+ float expected =
+ (float) Math.sqrt(64.0); // sqrt((5-1)^2 + (6-2)^2 + (7-3)^2 + (8-4)^2) = sqrt(64)
+ assertEquals(expected, v1.distanceTo(v2), 0.0001, "Distance should be calculated correctly.");
+ }
+
+ @Test
+ public void testDistanceToWithZeroVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f v2 = new Vector4f(0, 0, 0, 0);
+ float expected = (float) Math.sqrt(30.0); // sqrt(1^2 + 2^2 + 3^2 + 4^2) = sqrt(30)
+ assertEquals(expected, v1.distanceTo(v2), 0.0001, "Distance to zero vector should be correct.");
+ }
+
+ @Test
+ public void testDistanceToItself() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ float expected = 0.0f; // Distance to itself is always 0
+ assertEquals(expected, v1.distanceTo(v1), 0.0001, "Distance to itself should be zero.");
+ }
+
+ @Test
+ public void testDistanceToWithNegativeComponents() {
+ Vector4f v1 = new Vector4f(-1, -2, -3, -4);
+ Vector4f v2 = new Vector4f(-5, -6, -7, -8);
+ float expected = (float) Math.sqrt(64.0); // sqrt((-5 - (-1))^2 + ...) = sqrt(64)
+ assertEquals(
+ expected,
+ v1.distanceTo(v2),
+ 0.0001,
+ "Distance with negative components should be correct.");
+ }
+
+ @Test
+ public void testDistanceToWithNullOtherVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ v1.distanceTo(null);
+ },
+ "Should throw IllegalArgumentException if the other vector is null.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Angle Between
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testAngleBetweenWithValidVectors() {
+ Vector4f v1 = new Vector4f(1, 0, 0, 0);
+ Vector4f v2 = new Vector4f(0, 1, 0, 0);
+
+ float expected = (float) (Math.PI / 2); // 90 degrees in radians
+ assertEquals(
+ expected,
+ v1.angleBetween(v2),
+ 0.0001,
+ "Angle between orthogonal vectors should be 90 degrees.");
+ }
+
+ @Test
+ public void testAngleBetweenWithSameDirection() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f v2 = new Vector4f(1, 2, 3, 4); // Use identical vectors
+
+ float expected = 0.0f; // Angle between identical vectors should be 0 degrees
+ assertEquals(
+ expected,
+ v1.angleBetween(v2),
+ 0.001,
+ "Angle between identical vectors should be 0 degrees.");
+ }
+
+ @Test
+ public void testAngleBetweenWithOppositeDirection() {
+ Vector4f v1 = new Vector4f(1, 0, 0, 0);
+ Vector4f v2 = new Vector4f(-1, 0, 0, 0);
+
+ float expected = (float) Math.PI; // 180 degrees in radians
+ assertEquals(
+ expected,
+ v1.angleBetween(v2),
+ 0.0001,
+ "Angle between opposite vectors should be 180 degrees.");
+ }
+
+ @Test
+ public void testAngleBetweenWithZeroVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f v2 = new Vector4f(0, 0, 0, 0);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ v1.angleBetween(v2);
+ },
+ "Should throw IllegalArgumentException when the other vector is zero.");
+ }
+
+ @Test
+ public void testAngleBetweenWithNullVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ v1.angleBetween(null);
+ },
+ "Should throw IllegalArgumentException when the other vector is null.");
+ }
+
+ @Test
+ public void testAngleBetweenWithDifferentAngles() {
+ Vector4f v1 = new Vector4f(1, 1, 0, 0);
+ Vector4f v2 = new Vector4f(1, 0, 0, 0);
+
+ float expected = (float) (Math.PI / 4); // 45 degrees in radians
+ assertEquals(
+ expected,
+ v1.angleBetween(v2),
+ 0.0001,
+ "Angle between vectors at 45 degrees should be correct.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Project Onto
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testProjectOntoSameDirection() {
+ Vector4f vector1 = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f);
+ Vector4f vector2 = new Vector4f(6.0f, 8.0f, 0.0f, 0.0f); // Same direction as vector1
+
+ Vector4f projection = vector1.projectOnto(vector2);
+
+ // Dot product of vector1 and vector2: 3*6 + 4*8 = 18 + 32 = 50
+ // Dot product of vector2 with itself: 6*6 + 8*8 = 36 + 64 = 100
+ // proj_vector2(vector1) = (50 / 100) * vector2 = 0.5 * vector2 = (3, 4, 0, 0)
+ Vector4f expected = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f);
+
+ assertEquals(expected, projection, "The projection should match the expected vector.");
+ }
+
+ @Test
+ public void testProjectOntoPerpendicularVectors() {
+ Vector4f vector1 = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f); // Along x-axis
+ Vector4f vector2 = new Vector4f(0.0f, 1.0f, 0.0f, 0.0f); // Along y-axis
+
+ Vector4f projection = vector1.projectOnto(vector2);
+
+ // The projection of a vector onto a perpendicular vector should be zero
+ Vector4f expected = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ assertEquals(
+ expected, projection, "The projection of perpendicular vectors should be a zero vector.");
+ }
+
+ @Test
+ public void testProjectOntoZeroVector() {
+ Vector4f vector1 = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
+ Vector4f vector2 = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f); // Zero vector
+
+ // Projecting onto a zero vector should throw an exception
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ vector1.projectOnto(vector2);
+ },
+ "Projecting onto a zero vector should throw an exception.");
+ }
+
+ @Test
+ public void testProjectOntoNonZeroVector() {
+ Vector4f vector1 = new Vector4f(2.0f, 3.0f, 0.0f, 0.0f);
+ Vector4f vector2 = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f); // Along x-axis
+
+ Vector4f projection = vector1.projectOnto(vector2);
+
+ // The projection onto the x-axis is just the x-component of vector1
+ Vector4f expected = new Vector4f(2.0f, 0.0f, 0.0f, 0.0f);
+
+ assertEquals(expected, projection, "The projection should match the expected x-component.");
+ }
+
+ @Test
+ public void testProjectOntoNonUnitVector() {
+ Vector4f vector1 = new Vector4f(2.0f, 3.0f, 0.0f, 0.0f);
+ Vector4f vector2 =
+ new Vector4f(4.0f, 6.0f, 0.0f, 0.0f); // Not a unit vector, same direction as vector1
+
+ Vector4f projection = vector1.projectOnto(vector2);
+
+ // Dot product of vector1 and vector2: 2*4 + 3*6 = 8 + 18 = 26
+ // Dot product of vector2 with itself: 4*4 + 6*6 = 16 + 36 = 52
+ // proj_vector2(vector1) = (26 / 52) * vector2 = 0.5 * vector2 = (2, 3, 0, 0)
+ Vector4f expected = new Vector4f(2.0f, 3.0f, 0.0f, 0.0f);
+
+ assertEquals(expected, projection, "The projection should match the expected vector.");
+ }
+
+ @Test
+ public void testProjectOntoNullThrowsIllegalArgumentException() {
+ Vector4f vector1 = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f); // Any non-zero vector
+
+ // Projecting onto a null vector should throw an IllegalArgumentException
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ vector1.projectOnto(null);
+ },
+ "Projecting onto a null vector should throw an IllegalArgumentException.");
+ }
+
+ @Test
+ public void testProjectOntoReturnsNewInstanceAndLeavesOriginalUntouched() {
+ // Given: Two non-null vectors
+ Vector4f vector1 = new Vector4f(3.0f, 4.0f, 0.0f, 0.0f); // Original vector
+ Vector4f vector2 = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f); // Vector to project onto
+
+ // When: Project vector1 onto vector2
+ Vector4f projected = vector1.projectOnto(vector2);
+
+ // Then: Verify that the returned vector is a new instance
+ assertNotSame(vector1, projected);
+ assertNotSame(vector2, projected);
+
+ // Verify that the original vectors are untouched
+ assertEquals(3.0f, vector1.getX(), "The original vector1's X component should be unchanged.");
+ assertEquals(4.0f, vector1.getY(), "The original vector1's Y component should be unchanged.");
+ assertEquals(0.0f, vector1.getZ(), "The original vector1's Z component should be unchanged.");
+ assertEquals(0.0f, vector1.getW(), "The original vector1's W component should be unchanged.");
+
+ assertEquals(1.0f, vector2.getX(), "The original vector2's X component should be unchanged.");
+ assertEquals(0.0f, vector2.getY(), "The original vector2's Y component should be unchanged.");
+ assertEquals(0.0f, vector2.getZ(), "The original vector2's Z component should be unchanged.");
+ assertEquals(0.0f, vector2.getW(), "The original vector2's W component should be unchanged.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Reflect
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testReflectXAxis() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f normal = new Vector4f(1, 0, 0, 0); // Normal of the x-axis
+ Vector4f expected = new Vector4f(-1, 2, 3, 4);
+ assertEquals(expected, v1.reflect(normal));
+ }
+
+ @Test
+ public void testReflectYAxis() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f normal = new Vector4f(0, 1, 0, 0); // Normal of the y-axis
+ Vector4f expected = new Vector4f(1, -2, 3, 4);
+ assertEquals(expected, v1.reflect(normal));
+ }
+
+ @Test
+ public void testReflectZAxis() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f normal = new Vector4f(0, 0, 1, 0); // Normal of the z-axis
+ Vector4f expected = new Vector4f(1, 2, -3, 4);
+ assertEquals(expected, v1.reflect(normal));
+ }
+
+ @Test
+ public void testReflectArbitraryNormal() {
+ // Setup
+ Vector4f v1 = new Vector4f(1, 1, 1, 1);
+ Vector4f normal = new Vector4f(1, 1, 1, 0).normalize();
+
+ // Expected reflection result calculated manually:
+ // Reflection formula: v' = v - 2 * (v ⋅ n) * n
+ // Dot product (v ⋅ n)
+ float dot =
+ v1.getX() * normal.getX()
+ + v1.getY() * normal.getY()
+ + v1.getZ() * normal.getZ()
+ + v1.getW() * normal.getW();
+
+ // Scale the normal by 2 * dot
+ Vector4f scaledNormal =
+ new Vector4f(
+ 2 * dot * normal.getX(),
+ 2 * dot * normal.getY(),
+ 2 * dot * normal.getZ(),
+ 2 * dot * normal.getW());
+
+ // Subtract scaledNormal from v1 to get the reflected vector
+ Vector4f expectedReflection =
+ new Vector4f(
+ v1.getX() - scaledNormal.getX(),
+ v1.getY() - scaledNormal.getY(),
+ v1.getZ() - scaledNormal.getZ(),
+ v1.getW() - scaledNormal.getW());
+
+ // Actual reflection
+ Vector4f reflected = v1.reflect(normal);
+
+ // Assert equality
+ assertEquals(expectedReflection.getX(), reflected.getX(), 1e-6);
+ assertEquals(expectedReflection.getY(), reflected.getY(), 1e-6);
+ assertEquals(expectedReflection.getZ(), reflected.getZ(), 1e-6);
+ assertEquals(expectedReflection.getW(), reflected.getW(), 1e-6);
+ }
+
+ @Test
+ public void testReflectAlongItself() {
+ Vector4f v1 = new Vector4f(1, 0, 0, 0);
+ Vector4f normal = new Vector4f(1, 0, 0, 0);
+ assertEquals(v1.negate(), v1.reflect(normal));
+ }
+
+ @Test
+ public void testReflectAlongZeroVector() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ Vector4f normal = new Vector4f(0, 0, 0, 0);
+ assertThrows(IllegalArgumentException.class, () -> v1.reflect(normal));
+ }
+
+ @Test
+ public void testReflectAlongNullVectorThrowsIllegalArgumentException() {
+ Vector4f v1 = new Vector4f(1, 2, 3, 4);
+ assertThrows(IllegalArgumentException.class, () -> v1.reflect(null));
+ }
+
+ @Test
+ public void testReflectParallelToNormal() {
+ // Vector and normal are parallel
+ Vector4f v1 = new Vector4f(2, 0, 0, 0);
+ Vector4f normal = new Vector4f(1, 0, 0, 0).normalize();
+
+ // Perform reflection
+ Vector4f reflected = v1.reflect(normal);
+
+ // Expected result: negation of the vector
+ Vector4f expected = new Vector4f(-2, 0, 0, 0);
+
+ // Assert the reflected vector is correct
+ assertEquals(
+ expected, reflected, "The reflected vector is incorrect when v1 is parallel to normal.");
+
+ // Assert v1 and normal remain unchanged
+ Vector4f originalV1 = new Vector4f(2, 0, 0, 0);
+ Vector4f originalNormal = new Vector4f(1, 0, 0, 0).normalize();
+ assertEquals(originalV1, v1, "The original vector should remain unchanged.");
+ assertEquals(originalNormal, normal, "The normal vector should remain unchanged.");
+ }
+
+ @Test
+ public void testReflectCreatesNewInstanceAndDoesNotChangeOriginalVectorAndInputNormal() {
+ // Original vector
+ Vector4f v1 = new Vector4f(3, -2, 1, 0);
+ // Normal vector
+ Vector4f normal = new Vector4f(0, 1, 0, 0).normalize();
+
+ // Store copies of the original vectors for later comparison
+ Vector4f originalV1 = new Vector4f(v1.getX(), v1.getY(), v1.getZ(), v1.getW());
+ Vector4f originalNormal =
+ new Vector4f(normal.getX(), normal.getY(), normal.getZ(), normal.getW());
+
+ // Perform reflection
+ Vector4f reflected = v1.reflect(normal);
+
+ // Assert that the returned instance is a new object
+ assertNotSame("The method should return a new Vector4f instance.", v1, reflected);
+
+ // Assert the original vector remains unchanged
+ assertEquals(originalV1, v1, "The original vector should remain unchanged.");
+
+ // Assert the normal vector remains unchanged
+ assertEquals(originalNormal, normal, "The normal vector should remain unchanged.");
+
+ // Optional: Assert that the reflection result is correct
+ Vector4f expected = new Vector4f(3, 2, 1, 0); // Reflection around Y-axis
+ assertEquals(expected, reflected, "The reflected vector is incorrect.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Set Values
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testSetUpdatesVector() {
+ // Given: A vector with initial values
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling set to update the vector components
+ vector.set(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // Then: The vector's components should match the new values
+ assertEquals(5.0f, vector.getX());
+ assertEquals(6.0f, vector.getY());
+ assertEquals(7.0f, vector.getZ());
+ assertEquals(8.0f, vector.getW());
+ }
+
+ @Test
+ public void testSetToZero() {
+ // Given: A vector with non-zero values
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling set to update the vector components to zero
+ vector.set(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // Then: The vector's components should be zero
+ assertEquals(0.0f, vector.getX());
+ assertEquals(0.0f, vector.getY());
+ assertEquals(0.0f, vector.getZ());
+ assertEquals(0.0f, vector.getW());
+ }
+
+ @Test
+ public void testSetWithNegativeValues() {
+ // Given: A vector with positive values
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling set to update the vector components with negative values
+ vector.set(-1.0f, -2.0f, -3.0f, -4.0f);
+
+ // Then: The vector's components should be the negative values
+ assertEquals(-1.0f, vector.getX());
+ assertEquals(-2.0f, vector.getY());
+ assertEquals(-3.0f, vector.getZ());
+ assertEquals(-4.0f, vector.getW());
+ }
+
+ @Test
+ public void testSetSameValues() {
+ // Given: A vector with some initial values
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling set with the same values
+ vector.set(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Then: The vector's components should remain unchanged
+ assertEquals(1.0f, vector.getX());
+ assertEquals(2.0f, vector.getY());
+ assertEquals(3.0f, vector.getZ());
+ assertEquals(4.0f, vector.getW());
+ }
+
+ @Test
+ public void testSetWithLargeValues() {
+ // Given: A vector with small values
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // When: Calling set to update the vector with large values
+ vector.set(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
+
+ // Then: The vector's components should match the large values
+ assertEquals(Float.MAX_VALUE, vector.getX());
+ assertEquals(Float.MAX_VALUE, vector.getY());
+ assertEquals(Float.MAX_VALUE, vector.getZ());
+ assertEquals(Float.MAX_VALUE, vector.getW());
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Clone
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testCloneCreatesIdenticalVector() {
+ // Arrange
+ Vector4f original = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ Vector4f clone = original.clone();
+
+ // Assert
+ assertEquals(original.getX(), clone.getX(), 0.0001f);
+ assertEquals(original.getY(), clone.getY(), 0.0001f);
+ assertEquals(original.getZ(), clone.getZ(), 0.0001f);
+ assertEquals(original.getW(), clone.getW(), 0.0001f);
+ }
+
+ @Test
+ public void testCloneCreatesNewInstance() {
+ // Arrange
+ Vector4f original = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ Vector4f clone = original.clone();
+
+ // Assert
+ assertNotNull(clone);
+ assertNotSame(original, clone); // Ensure it's a new instance
+ }
+
+ @Test
+ public void testCloneIsIndependentOfOriginal() {
+ // Arrange
+ Vector4f original = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // Act
+ Vector4f clone = original.clone();
+ clone.setX(10.0f);
+
+ // Assert
+ assertNotEquals(original.getX(), clone.getX()); // Ensure the original vector is not affected
+ assertEquals(1.0f, original.getX(), 0.0001f); // Verify the original value remains unchanged
+ assertEquals(10.0f, clone.getX(), 0.0001f); // Verify the clone was updated
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Hash Code
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testHashCodeWithSlightlyDifferentVectors() {
+ Vector4f v1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f v2 = new Vector4f(1.0001f, 2.0f, 3.0f, 4.0f);
+
+ // Slightly different vectors may or may not have equal hash codes (due to hash function
+ // characteristics)
+ // This test checks for potential hash code collisions, but doesn't guarantee they won't occur
+ assertNotEquals(v1.hashCode(), v2.hashCode());
+ }
+
+ @Test
+ public void testHashCodeConsistency() {
+ // Given: A vector4 object
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling hashCode multiple times
+ int hash1 = vector.hashCode();
+ int hash2 = vector.hashCode();
+
+ // Then: hashCode should return the same value each time for the same object
+ assertEquals(hash1, hash2, "hashCode should be consistent for the same object.");
+ }
+
+ @Test
+ public void testHashCodeEquality() {
+ // Given: Two equal vectors
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling hashCode on both vectors
+ int hash1 = vector1.hashCode();
+ int hash2 = vector2.hashCode();
+
+ // Then: Equal vectors should have the same hashCode
+ assertEquals(hash1, hash2, "hashCode should be the same for equal objects.");
+ }
+
+ @Test
+ public void testHashCodeInequality() {
+ // Given: Two different vectors
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // When: Calling hashCode on both vectors
+ int hash1 = vector1.hashCode();
+ int hash2 = vector2.hashCode();
+
+ // Then: Different vectors should likely have different hashCodes (though not guaranteed)
+ assertNotEquals(hash1, hash2, "hashCode should be different for different objects.");
+ }
+
+ @Test
+ public void testHashCodeWithNullValues() {
+ // Given: A vector with zero values
+ Vector4f vector1 = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+ Vector4f vector2 = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // When: Calling hashCode on both vectors
+ int hash1 = vector1.hashCode();
+ int hash2 = vector2.hashCode();
+
+ // Then: Two identical vectors should have the same hashCode
+ assertEquals(hash1, hash2, "hashCode should be the same for identical vectors.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Equals
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testEqualsSameObject() {
+ // Given: A vector4 object
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Comparing the vector to itself
+ boolean result = vector.equals(vector);
+
+ // Then: The vector should be equal to itself
+ assertTrue(result, "A vector should be equal to itself.");
+ }
+
+ @Test
+ public void testEqualsIdenticalVectors() {
+ // Given: Two equal vectors
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Comparing the two vectors
+ boolean result = vector1.equals(vector2);
+
+ // Then: The vectors should be equal
+ assertTrue(result, "Two vectors with the same components should be equal.");
+ }
+
+ @Test
+ public void testEqualsDifferentTypes() {
+ // Given: A vector4 object and an object of a different type
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ String otherObject = "Not a vector";
+
+ // When: Comparing the vector to the other object
+ boolean result = vector.equals((Object) otherObject);
+
+ // Then: The result should be false, since the object is not a Vector4f
+ assertFalse(result, "A vector should not be equal to an object of a different type.");
+ }
+
+ @Test
+ public void testEqualsDifferentVectors() {
+ // Given: Two different vectors
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(5.0f, 6.0f, 7.0f, 8.0f);
+
+ // When: Comparing the two vectors
+ boolean result = vector1.equals(vector2);
+
+ // Then: The vectors should not be equal
+ assertFalse(result, "Two vectors with different components should not be equal.");
+ }
+
+ @Test
+ public void testEqualsNull() {
+ // Given: A vector4 object
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Comparing the vector to null
+ boolean result = vector.equals(null);
+
+ // Then: The result should be false, as a vector cannot be equal to null
+ assertFalse(result, "A vector should not be equal to null.");
+ }
+
+ @Test
+ public void testEqualsWithEpsilonTolerance() {
+ // Given: Two vectors that are close but slightly different due to precision issues
+ Vector4f vector1 = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+ Vector4f vector2 = new Vector4f(1.0000001f, 2.0000001f, 3.0000001f, 4.0000001f);
+
+ // When: Comparing the two vectors with a very small epsilon
+ boolean result = vector1.equals(vector2);
+
+ // Then: The result should be false since they are not exactly equal
+ assertFalse(result, "Vectors that differ slightly should not be equal without tolerance.");
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // To String
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testToStringBasic() {
+ // Given: A Vector4f with some standard values
+ Vector4f vector = new Vector4f(1.0f, 2.0f, 3.0f, 4.0f);
+
+ // When: Calling toString on the vector
+ String result = vector.toString();
+
+ // Then: The result should be in the expected format
+ assertEquals("Vector4f [x=1.0, y=2.0, z=3.0, w=4.0]", result);
+ }
+
+ @Test
+ public void testToStringNegativeValues() {
+ // Given: A Vector4f with negative values
+ Vector4f vector = new Vector4f(-1.0f, -2.0f, -3.0f, -4.0f);
+
+ // When: Calling toString on the vector
+ String result = vector.toString();
+
+ // Then: The result should be in the expected format with negative values
+ assertEquals("Vector4f [x=-1.0, y=-2.0, z=-3.0, w=-4.0]", result);
+ }
+
+ @Test
+ public void testToStringZeroValues() {
+ // Given: A Vector4f with all zero values
+ Vector4f vector = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
+
+ // When: Calling toString on the vector
+ String result = vector.toString();
+
+ // Then: The result should be in the expected format with zero values
+ assertEquals("Vector4f [x=0.0, y=0.0, z=0.0, w=0.0]", result);
+ }
+
+ @Test
+ public void testToStringLargeValues() {
+ // Given: A Vector4f with very large values
+ Vector4f vector =
+ new Vector4f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
+
+ // When: Calling toString on the vector
+ String result = vector.toString();
+
+ // Then: The result should be in the expected format with large values
+ assertEquals(
+ "Vector4f [x="
+ + Float.MAX_VALUE
+ + ", y="
+ + Float.MAX_VALUE
+ + ", z="
+ + Float.MAX_VALUE
+ + ", w="
+ + Float.MAX_VALUE
+ + "]",
+ result);
+ }
+
+ @Test
+ public void testToStringSmallValues() {
+ // Given: A Vector4f with very small values
+ Vector4f vector =
+ new Vector4f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
+
+ // When: Calling toString on the vector
+ String result = vector.toString();
+
+ // Then: The result should be in the expected format with small values
+ assertEquals(
+ "Vector4f [x="
+ + Float.MIN_VALUE
+ + ", y="
+ + Float.MIN_VALUE
+ + ", z="
+ + Float.MIN_VALUE
+ + ", w="
+ + Float.MIN_VALUE
+ + "]",
+ result);
+ }
+
+ @Test
+ public void testToStringEmptyVector() {
+ // Given: A default Vector4f (all zero values)
+ Vector4f vector = new Vector4f();
+
+ // When: Calling toString on the vector
+ String result = vector.toString();
+
+ // Then: The result should be in the expected format with zero values
+ assertEquals("Vector4f [x=0.0, y=0.0, z=0.0, w=0.0]", result);
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // Getter Setter
+ // ----------------------------------------------------------------------------------------------
+
+ @Test
+ public void testSetAndGetX() {
+ Vector4f vector = new Vector4f();
+ vector.setX(1.0f);
+ assertEquals(1.0f, vector.getX(), 0.0001);
+
+ vector.setX(2.0f);
+ assertEquals(2.0f, vector.getX(), 0.0001);
+ }
+
+ @Test
+ public void testSetAndGetY() {
+ Vector4f vector = new Vector4f();
+ vector.setY(3.0f);
+ assertEquals(3.0f, vector.getY(), 0.0001);
+
+ vector.setY(4.0f);
+ assertEquals(4.0f, vector.getY(), 0.0001);
+ }
+
+ @Test
+ public void testSetAndGetZ() {
+ Vector4f vector = new Vector4f();
+ vector.setZ(5.0f);
+ assertEquals(5.0f, vector.getZ(), 0.0001);
+
+ vector.setZ(6.0f);
+ assertEquals(6.0f, vector.getZ(), 0.0001);
+ }
+
+ @Test
+ public void testSetAndGetW() {
+ Vector4f vector = new Vector4f();
+ vector.setW(7.0f);
+ assertEquals(7.0f, vector.getW(), 0.0001);
+
+ vector.setW(8.0f);
+ assertEquals(8.0f, vector.getW(), 0.0001);
+ }
+}