From c491c6d4ec770ec6bd9bfecdb0b043d8ab4efb98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:10:16 +0000 Subject: [PATCH 1/6] Initial plan From 1ed8b635204c302eac74b242d4409c69486e9f4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:19:09 +0000 Subject: [PATCH 2/6] Add unit tests for Vector2f, Vector4f, Matrix3f, and Matrix4f Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com> --- .../test/java/com/jme3/math/Matrix3fTest.java | 513 +++++++++++++++++ .../test/java/com/jme3/math/Matrix4fTest.java | 450 +++++++++++++++ .../test/java/com/jme3/math/Vector2fTest.java | 534 ++++++++++++++++++ .../test/java/com/jme3/math/Vector4fTest.java | 532 +++++++++++++++++ 4 files changed, 2029 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/Vector2fTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/Vector4fTest.java diff --git a/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java b/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java new file mode 100644 index 0000000000..92e4e8ca6a --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Matrix3f} class works correctly. + */ +public class Matrix3fTest { + + private static final float TOLERANCE = 1e-6f; + + // ----------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------- + + @Test + public void testDefaultConstructorIsIdentity() { + Matrix3f m = new Matrix3f(); + Assert.assertTrue(m.isIdentity()); + } + + @Test + public void testParameterizedConstructor() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Assert.assertEquals(1f, m.get(0, 0), 0f); + Assert.assertEquals(2f, m.get(0, 1), 0f); + Assert.assertEquals(3f, m.get(0, 2), 0f); + Assert.assertEquals(4f, m.get(1, 0), 0f); + Assert.assertEquals(5f, m.get(1, 1), 0f); + Assert.assertEquals(6f, m.get(1, 2), 0f); + Assert.assertEquals(7f, m.get(2, 0), 0f); + Assert.assertEquals(8f, m.get(2, 1), 0f); + Assert.assertEquals(9f, m.get(2, 2), 0f); + } + + @Test + public void testCopyConstructor() { + Matrix3f original = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f copy = new Matrix3f(original); + Assert.assertEquals(original, copy); + Assert.assertNotSame(original, copy); + } + + @Test + public void testCopyConstructorNull() { + Matrix3f m = new Matrix3f(null); + Assert.assertTrue(m.isIdentity()); + } + + // ----------------------------------------------------------------------- + // Identity / zero + // ----------------------------------------------------------------------- + + @Test + public void testLoadIdentity() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + m.loadIdentity(); + Assert.assertTrue(m.isIdentity()); + } + + @Test + public void testZero() { + Matrix3f m = new Matrix3f(); + m.zero(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals(0f, m.get(i, j), 0f); + } + } + Assert.assertFalse(m.isIdentity()); + } + + // ----------------------------------------------------------------------- + // set(Matrix3f) and set(int, int, float) + // ----------------------------------------------------------------------- + + @Test + public void testSetMatrix() { + Matrix3f source = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f target = new Matrix3f(); + Matrix3f result = target.set(source); + Assert.assertSame(target, result); + Assert.assertEquals(source, target); + } + + @Test + public void testSetMatrixNull() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + m.set((Matrix3f) null); + Assert.assertTrue(m.isIdentity()); + } + + @Test + public void testSetElement() { + Matrix3f m = new Matrix3f(); + Matrix3f result = m.set(1, 2, 99f); + Assert.assertSame(m, result); + Assert.assertEquals(99f, m.get(1, 2), 0f); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetInvalidRow() { + Matrix3f m = new Matrix3f(); + m.get(3, 0); + } + + // ----------------------------------------------------------------------- + // absoluteLocal + // ----------------------------------------------------------------------- + + @Test + public void testAbsoluteLocal() { + Matrix3f m = new Matrix3f( + -1f, 2f, -3f, + 4f, -5f, 6f, + -7f, 8f, -9f); + m.absoluteLocal(); + Assert.assertEquals(1f, m.get(0, 0), 0f); + Assert.assertEquals(2f, m.get(0, 1), 0f); + Assert.assertEquals(3f, m.get(0, 2), 0f); + Assert.assertEquals(4f, m.get(1, 0), 0f); + Assert.assertEquals(5f, m.get(1, 1), 0f); + Assert.assertEquals(6f, m.get(1, 2), 0f); + Assert.assertEquals(7f, m.get(2, 0), 0f); + Assert.assertEquals(8f, m.get(2, 1), 0f); + Assert.assertEquals(9f, m.get(2, 2), 0f); + } + + // ----------------------------------------------------------------------- + // Determinant + // ----------------------------------------------------------------------- + + @Test + public void testDeterminantIdentity() { + Matrix3f m = new Matrix3f(); + Assert.assertEquals(1f, m.determinant(), TOLERANCE); + } + + @Test + public void testDeterminant() { + // Simple scaling matrix: diag(2, 3, 4) -> det = 24 + Matrix3f m = new Matrix3f( + 2f, 0f, 0f, + 0f, 3f, 0f, + 0f, 0f, 4f); + Assert.assertEquals(24f, m.determinant(), TOLERANCE); + } + + @Test + public void testDeterminantSingular() { + // Row of zeros -> singular + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Assert.assertEquals(0f, m.determinant(), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Transpose + // ----------------------------------------------------------------------- + + @Test + public void testTransposeNew() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f t = m.transposeNew(); + Assert.assertNotSame(m, t); + // column i of m == row i of t + Assert.assertEquals(m.get(0, 1), t.get(1, 0), 0f); + Assert.assertEquals(m.get(1, 2), t.get(2, 1), 0f); + Assert.assertEquals(m.get(2, 0), t.get(0, 2), 0f); + // original unaffected + Assert.assertEquals(2f, m.get(0, 1), 0f); + } + + @Test + public void testTransposeLocal() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f result = m.transposeLocal(); + Assert.assertSame(m, result); + Assert.assertEquals(4f, m.get(0, 1), 0f); + Assert.assertEquals(2f, m.get(1, 0), 0f); + Assert.assertEquals(7f, m.get(0, 2), 0f); + Assert.assertEquals(3f, m.get(2, 0), 0f); + } + + @Test + public void testDoubleTransposeIsOriginal() { + Matrix3f original = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f copy = new Matrix3f(original); + copy.transposeLocal().transposeLocal(); + Assert.assertEquals(original, copy); + } + + // ----------------------------------------------------------------------- + // Invert + // ----------------------------------------------------------------------- + + @Test + public void testInvertIdentity() { + Matrix3f m = new Matrix3f(); + Matrix3f inv = m.invert(); + Assert.assertTrue(inv.isIdentity()); + } + + @Test + public void testInvert() { + // Diagonal matrix: inv = diag(1/2, 1/3, 1/4) + Matrix3f m = new Matrix3f( + 2f, 0f, 0f, + 0f, 3f, 0f, + 0f, 0f, 4f); + Matrix3f inv = m.invert(); + Assert.assertEquals(0.5f, inv.get(0, 0), TOLERANCE); + Assert.assertEquals(1f / 3f, inv.get(1, 1), TOLERANCE); + Assert.assertEquals(0.25f, inv.get(2, 2), TOLERANCE); + } + + @Test + public void testInvertTimesOriginalIsIdentity() { + Matrix3f m = new Matrix3f( + 1f, 2f, 0f, + 0f, 1f, 3f, + 0f, 0f, 1f); + Matrix3f inv = m.invert(); + Matrix3f product = m.mult(inv); + Assert.assertTrue(product.isIdentity()); + } + + @Test + public void testInvertSingular() { + // Row of zeros -> singular, should return all-zero matrix + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f inv = m.invert(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals(0f, inv.get(i, j), 0f); + } + } + } + + // ----------------------------------------------------------------------- + // Multiply + // ----------------------------------------------------------------------- + + @Test + public void testMultIdentity() { + Matrix3f a = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f identity = new Matrix3f(); + Matrix3f result = a.mult(identity); + Assert.assertEquals(a, result); + } + + @Test + public void testMult() { + // [[1,0],[0,2]] * [[3,0],[0,4]] = [[3,0],[0,8]] + Matrix3f a = new Matrix3f( + 1f, 0f, 0f, + 0f, 2f, 0f, + 0f, 0f, 1f); + Matrix3f b = new Matrix3f( + 3f, 0f, 0f, + 0f, 4f, 0f, + 0f, 0f, 1f); + Matrix3f result = a.mult(b); + Assert.assertEquals(3f, result.get(0, 0), TOLERANCE); + Assert.assertEquals(8f, result.get(1, 1), TOLERANCE); + Assert.assertEquals(1f, result.get(2, 2), TOLERANCE); + Assert.assertEquals(0f, result.get(0, 1), TOLERANCE); + } + + @Test + public void testMultVector() { + Matrix3f m = new Matrix3f(); // identity + Vector3f v = new Vector3f(1f, 2f, 3f); + Vector3f result = m.mult(v); + Assert.assertEquals(1f, result.x, TOLERANCE); + Assert.assertEquals(2f, result.y, TOLERANCE); + Assert.assertEquals(3f, result.z, TOLERANCE); + } + + @Test + public void testMultVectorScaling() { + Matrix3f m = new Matrix3f( + 2f, 0f, 0f, + 0f, 3f, 0f, + 0f, 0f, 4f); + Vector3f v = new Vector3f(1f, 1f, 1f); + Vector3f result = m.mult(v); + Assert.assertEquals(2f, result.x, TOLERANCE); + Assert.assertEquals(3f, result.y, TOLERANCE); + Assert.assertEquals(4f, result.z, TOLERANCE); + } + + @Test + public void testMultLocalScalar() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f result = m.multLocal(2f); + Assert.assertSame(m, result); + Assert.assertEquals(2f, m.get(0, 0), TOLERANCE); + Assert.assertEquals(4f, m.get(0, 1), TOLERANCE); + Assert.assertEquals(18f, m.get(2, 2), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Columns and rows + // ----------------------------------------------------------------------- + + @Test + public void testGetColumn() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Vector3f col0 = m.getColumn(0); + Assert.assertEquals(1f, col0.x, 0f); + Assert.assertEquals(4f, col0.y, 0f); + Assert.assertEquals(7f, col0.z, 0f); + } + + @Test + public void testGetRow() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Vector3f row1 = m.getRow(1); + Assert.assertEquals(4f, row1.x, 0f); + Assert.assertEquals(5f, row1.y, 0f); + Assert.assertEquals(6f, row1.z, 0f); + } + + @Test + public void testSetColumn() { + Matrix3f m = new Matrix3f(); + Vector3f col = new Vector3f(1f, 2f, 3f); + m.setColumn(0, col); + Assert.assertEquals(1f, m.get(0, 0), 0f); + Assert.assertEquals(2f, m.get(1, 0), 0f); + Assert.assertEquals(3f, m.get(2, 0), 0f); + } + + @Test + public void testSetRow() { + Matrix3f m = new Matrix3f(); + Vector3f row = new Vector3f(7f, 8f, 9f); + m.setRow(2, row); + Assert.assertEquals(7f, m.get(2, 0), 0f); + Assert.assertEquals(8f, m.get(2, 1), 0f); + Assert.assertEquals(9f, m.get(2, 2), 0f); + } + + // ----------------------------------------------------------------------- + // fromAngleAxis + // ----------------------------------------------------------------------- + + @Test + public void testFromAngleAxisRotation90DegreesAroundZ() { + Matrix3f m = new Matrix3f(); + m.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + // Rotating (1,0,0) by 90 degrees around Z should give ~(0,1,0) + Vector3f result = m.mult(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(0f, result.x, TOLERANCE); + Assert.assertEquals(1f, result.y, TOLERANCE); + Assert.assertEquals(0f, result.z, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // get/set float arrays + // ----------------------------------------------------------------------- + + @Test + public void testGetFloatArrayRowMajor() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + float[] data = new float[9]; + m.get(data, true); + Assert.assertEquals(1f, data[0], 0f); + Assert.assertEquals(2f, data[1], 0f); + Assert.assertEquals(3f, data[2], 0f); + Assert.assertEquals(4f, data[3], 0f); + Assert.assertEquals(9f, data[8], 0f); + } + + @Test + public void testGetFloatArrayColumnMajor() { + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + float[] data = new float[9]; + m.get(data, false); + // column-major: first column (m00, m10, m20) = (1, 4, 7) + Assert.assertEquals(1f, data[0], 0f); + Assert.assertEquals(4f, data[1], 0f); + Assert.assertEquals(7f, data[2], 0f); + } + + // ----------------------------------------------------------------------- + // Clone and equals + // ----------------------------------------------------------------------- + + @Test + public void testClone() { + Matrix3f original = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original, cloned); + } + + @Test + public void testEqualsAndHashCode() { + Matrix3f a = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f b = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + Matrix3f c = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 0f); + Assert.assertEquals(a, b); + Assert.assertNotEquals(a, c); + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + + // ----------------------------------------------------------------------- + // Static constants + // ----------------------------------------------------------------------- + + @Test + public void testStaticConstants() { + Assert.assertTrue(Matrix3f.IDENTITY.isIdentity()); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals(0f, Matrix3f.ZERO.get(i, j), 0f); + } + } + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java b/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java new file mode 100644 index 0000000000..e7a4dfa097 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Matrix4f} class works correctly. + */ +public class Matrix4fTest { + + private static final float TOLERANCE = 1e-5f; + + /** Helper: compare two Matrix4f element-by-element. */ + private static void assertMatricesEqual(Matrix4f expected, Matrix4f actual, float delta) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Assert.assertEquals( + "element (" + i + "," + j + ")", + expected.get(i, j), actual.get(i, j), delta); + } + } + } + + /** Helper: build identity-like check (each diagonal = 1, off-diagonal = 0). */ + private static void assertIsIdentity(Matrix4f m, float delta) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + float expected = (i == j) ? 1f : 0f; + Assert.assertEquals( + "element (" + i + "," + j + ")", + expected, m.get(i, j), delta); + } + } + } + + // ----------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------- + + @Test + public void testDefaultConstructorIsIdentity() { + Matrix4f m = new Matrix4f(); + assertIsIdentity(m, 0f); + } + + @Test + public void testCopyConstructor() { + Matrix4f original = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f copy = new Matrix4f(original); + assertMatricesEqual(original, copy, 0f); + Assert.assertNotSame(original, copy); + } + + @Test + public void testCopyConstructorNull() { + Matrix4f m = new Matrix4f((Matrix4f) null); + assertIsIdentity(m, 0f); + } + + @Test + public void testParameterizedConstructor() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Assert.assertEquals(1f, m.get(0, 0), 0f); + Assert.assertEquals(4f, m.get(0, 3), 0f); + Assert.assertEquals(16f, m.get(3, 3), 0f); + } + + // ----------------------------------------------------------------------- + // loadIdentity / zero + // ----------------------------------------------------------------------- + + @Test + public void testLoadIdentity() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + m.loadIdentity(); + assertIsIdentity(m, 0f); + } + + @Test + public void testZero() { + Matrix4f m = new Matrix4f(); + Matrix4f result = m.zero(); + Assert.assertSame(m, result); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Assert.assertEquals(0f, m.get(i, j), 0f); + } + } + } + + // ----------------------------------------------------------------------- + // set / copy + // ----------------------------------------------------------------------- + + @Test + public void testSetMatrix() { + Matrix4f source = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f target = new Matrix4f(); + Matrix4f result = target.set(source); + Assert.assertSame(target, result); + assertMatricesEqual(source, target, 0f); + } + + @Test + public void testCopyNull() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + m.copy(null); + assertIsIdentity(m, 0f); + } + + // ----------------------------------------------------------------------- + // Determinant + // ----------------------------------------------------------------------- + + @Test + public void testDeterminantIdentity() { + Matrix4f m = new Matrix4f(); + Assert.assertEquals(1f, m.determinant(), TOLERANCE); + } + + @Test + public void testDeterminantDiagonal() { + // diag(2,3,4,5) -> det = 120 + Matrix4f m = new Matrix4f( + 2f, 0f, 0f, 0f, + 0f, 3f, 0f, 0f, + 0f, 0f, 4f, 0f, + 0f, 0f, 0f, 5f); + Assert.assertEquals(120f, m.determinant(), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Transpose + // ----------------------------------------------------------------------- + + @Test + public void testTranspose() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f t = m.transpose(); + // m[i][j] should equal t[j][i] + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Assert.assertEquals(m.get(i, j), t.get(j, i), 0f); + } + } + } + + @Test + public void testTransposeLocal() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f original = new Matrix4f(m); + Matrix4f result = m.transposeLocal(); + Assert.assertSame(m, result); + // Verify transpose + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Assert.assertEquals(original.get(i, j), m.get(j, i), 0f); + } + } + } + + @Test + public void testDoubleTransposeIsOriginal() { + Matrix4f original = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f copy = new Matrix4f(original); + copy.transposeLocal().transposeLocal(); + assertMatricesEqual(original, copy, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Multiply + // ----------------------------------------------------------------------- + + @Test + public void testMultIdentity() { + Matrix4f a = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f identity = new Matrix4f(); + Matrix4f result = a.mult(identity); + assertMatricesEqual(a, result, TOLERANCE); + } + + @Test + public void testMultScalar() { + Matrix4f m = new Matrix4f(); + Matrix4f result = m.mult(2f); + Assert.assertEquals(2f, result.get(0, 0), TOLERANCE); + Assert.assertEquals(2f, result.get(1, 1), TOLERANCE); + Assert.assertEquals(0f, result.get(0, 1), TOLERANCE); + } + + @Test + public void testMultVector3f() { + Matrix4f m = new Matrix4f(); // identity + Vector3f v = new Vector3f(1f, 2f, 3f); + Vector3f result = m.mult(v); + Assert.assertEquals(1f, result.x, TOLERANCE); + Assert.assertEquals(2f, result.y, TOLERANCE); + Assert.assertEquals(3f, result.z, TOLERANCE); + } + + @Test + public void testMultVector4f() { + Matrix4f m = new Matrix4f(); // identity + Vector4f v = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = m.mult(v); + Assert.assertEquals(1f, result.x, TOLERANCE); + Assert.assertEquals(2f, result.y, TOLERANCE); + Assert.assertEquals(3f, result.z, TOLERANCE); + Assert.assertEquals(4f, result.w, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Invert + // ----------------------------------------------------------------------- + + @Test + public void testInvertIdentity() { + Matrix4f m = new Matrix4f(); + Matrix4f inv = m.invert(); + assertIsIdentity(inv, TOLERANCE); + } + + @Test + public void testInvertTimesOriginalIsIdentity() { + // Simple non-trivial invertible matrix + Matrix4f m = new Matrix4f( + 1f, 0f, 0f, 5f, + 0f, 1f, 0f, 3f, + 0f, 0f, 1f, 2f, + 0f, 0f, 0f, 1f); + Matrix4f inv = m.invert(); + Matrix4f product = m.mult(inv); + assertIsIdentity(product, TOLERANCE); + } + + @Test(expected = ArithmeticException.class) + public void testInvertSingularThrows() { + Matrix4f m = new Matrix4f(); + m.zero(); // all zeros is singular + m.invert(); + } + + // ----------------------------------------------------------------------- + // add / addLocal + // ----------------------------------------------------------------------- + + @Test + public void testAdd() { + Matrix4f a = new Matrix4f(); // identity + Matrix4f b = new Matrix4f(); // identity + Matrix4f result = a.add(b); + // result should be identity + identity = 2*identity + Assert.assertEquals(2f, result.get(0, 0), TOLERANCE); + Assert.assertEquals(0f, result.get(0, 1), TOLERANCE); + Assert.assertEquals(2f, result.get(3, 3), TOLERANCE); + } + + @Test + public void testAddLocal() { + Matrix4f a = new Matrix4f(); // identity + Matrix4f b = new Matrix4f(); // identity + a.addLocal(b); + Assert.assertEquals(2f, a.get(0, 0), TOLERANCE); + Assert.assertEquals(0f, a.get(0, 1), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Translation + // ----------------------------------------------------------------------- + + @Test + public void testToTranslationVector() { + Matrix4f m = new Matrix4f(); + m.setTranslation(new Vector3f(1f, 2f, 3f)); + Vector3f translation = m.toTranslationVector(); + Assert.assertEquals(1f, translation.x, TOLERANCE); + Assert.assertEquals(2f, translation.y, TOLERANCE); + Assert.assertEquals(3f, translation.z, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // get(float[]) round-trip + // ----------------------------------------------------------------------- + + @Test + public void testGetFloatArrayRowMajor() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + float[] data = new float[16]; + m.get(data, true); + Assert.assertEquals(1f, data[0], 0f); + Assert.assertEquals(2f, data[1], 0f); + Assert.assertEquals(4f, data[3], 0f); + Assert.assertEquals(16f, data[15], 0f); + } + + @Test + public void testGetFloatArrayColumnMajor() { + Matrix4f m = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + float[] data = new float[16]; + m.get(data, false); + // column-major: first column is (m00, m10, m20, m30) = (1, 5, 9, 13) + Assert.assertEquals(1f, data[0], 0f); + Assert.assertEquals(5f, data[1], 0f); + Assert.assertEquals(9f, data[2], 0f); + Assert.assertEquals(13f, data[3], 0f); + } + + // ----------------------------------------------------------------------- + // fromAngleAxis + // ----------------------------------------------------------------------- + + @Test + public void testFromAngleAxisRotation90DegreesAroundY() { + Matrix4f m = new Matrix4f(); + m.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Y); + // Rotating (1,0,0) by 90 degrees around Y should give ~(0,0,-1) + Vector3f v = new Vector3f(1f, 0f, 0f); + Vector3f result = m.mult(v); + Assert.assertEquals(0f, result.x, TOLERANCE); + Assert.assertEquals(0f, result.y, TOLERANCE); + Assert.assertEquals(-1f, result.z, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Clone and equals + // ----------------------------------------------------------------------- + + @Test + public void testClone() { + Matrix4f original = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f cloned = original.clone(); + Assert.assertNotSame(original, cloned); + assertMatricesEqual(original, cloned, 0f); + } + + @Test + public void testEqualsAndHashCode() { + Matrix4f a = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f b = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 16f); + Matrix4f c = new Matrix4f( + 1f, 2f, 3f, 4f, + 5f, 6f, 7f, 8f, + 9f, 10f, 11f, 12f, + 13f, 14f, 15f, 0f); + Assert.assertEquals(a, b); + Assert.assertNotEquals(a, c); + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + + // ----------------------------------------------------------------------- + // Static constants + // ----------------------------------------------------------------------- + + @Test + public void testStaticConstants() { + assertIsIdentity(Matrix4f.IDENTITY, 0f); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Assert.assertEquals(0f, Matrix4f.ZERO.get(i, j), 0f); + } + } + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java new file mode 100644 index 0000000000..a2a56f59ab --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Vector2f} class works correctly. + */ +public class Vector2fTest { + + private static final float TOLERANCE = 1e-6f; + + // ----------------------------------------------------------------------- + // Constructors and set + // ----------------------------------------------------------------------- + + @Test + public void testDefaultConstructor() { + Vector2f v = new Vector2f(); + Assert.assertEquals(0f, v.x, 0f); + Assert.assertEquals(0f, v.y, 0f); + } + + @Test + public void testParameterizedConstructor() { + Vector2f v = new Vector2f(3f, -5f); + Assert.assertEquals(3f, v.x, 0f); + Assert.assertEquals(-5f, v.y, 0f); + } + + @Test + public void testCopyConstructor() { + Vector2f original = new Vector2f(2f, 7f); + Vector2f copy = new Vector2f(original); + Assert.assertEquals(original.x, copy.x, 0f); + Assert.assertEquals(original.y, copy.y, 0f); + // Ensure it is a distinct object + Assert.assertNotSame(original, copy); + } + + @Test + public void testSet() { + Vector2f v = new Vector2f(); + Vector2f result = v.set(4f, -2f); + Assert.assertSame(v, result); + Assert.assertEquals(4f, v.x, 0f); + Assert.assertEquals(-2f, v.y, 0f); + } + + @Test + public void testSetVector() { + Vector2f v = new Vector2f(); + Vector2f other = new Vector2f(9f, 1f); + Vector2f result = v.set(other); + Assert.assertSame(v, result); + Assert.assertEquals(9f, v.x, 0f); + Assert.assertEquals(1f, v.y, 0f); + } + + // ----------------------------------------------------------------------- + // Add + // ----------------------------------------------------------------------- + + @Test + public void testAdd() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(3f, 4f); + Vector2f result = a.add(b); + Assert.assertNotSame(a, result); + Assert.assertEquals(4f, result.x, 0f); + Assert.assertEquals(6f, result.y, 0f); + // original unaffected + Assert.assertEquals(1f, a.x, 0f); + } + + @Test + public void testAddReturnsNullForNullArg() { + Vector2f a = new Vector2f(1f, 2f); + Assert.assertNull(a.add((Vector2f) null)); + } + + @Test + public void testAddLocal() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(3f, 4f); + Vector2f result = a.addLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(4f, a.x, 0f); + Assert.assertEquals(6f, a.y, 0f); + } + + @Test + public void testAddLocalScalars() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f result = a.addLocal(3f, 4f); + Assert.assertSame(a, result); + Assert.assertEquals(4f, a.x, 0f); + Assert.assertEquals(6f, a.y, 0f); + } + + @Test + public void testAddWithStore() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(3f, 4f); + Vector2f store = new Vector2f(); + Vector2f result = a.add(b, store); + Assert.assertSame(store, result); + Assert.assertEquals(4f, store.x, 0f); + Assert.assertEquals(6f, store.y, 0f); + } + + @Test + public void testAddWithNullStore() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(3f, 4f); + Vector2f result = a.add(b, null); + Assert.assertNotNull(result); + Assert.assertEquals(4f, result.x, 0f); + Assert.assertEquals(6f, result.y, 0f); + } + + // ----------------------------------------------------------------------- + // Subtract + // ----------------------------------------------------------------------- + + @Test + public void testSubtract() { + Vector2f a = new Vector2f(5f, 8f); + Vector2f b = new Vector2f(3f, 2f); + Vector2f result = a.subtract(b); + Assert.assertEquals(2f, result.x, 0f); + Assert.assertEquals(6f, result.y, 0f); + Assert.assertEquals(5f, a.x, 0f); // unaffected + } + + @Test + public void testSubtractScalars() { + Vector2f a = new Vector2f(5f, 8f); + Vector2f result = a.subtract(1f, 3f); + Assert.assertEquals(4f, result.x, 0f); + Assert.assertEquals(5f, result.y, 0f); + } + + @Test + public void testSubtractLocal() { + Vector2f a = new Vector2f(5f, 8f); + Vector2f b = new Vector2f(3f, 2f); + Vector2f result = a.subtractLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(2f, a.x, 0f); + Assert.assertEquals(6f, a.y, 0f); + } + + @Test + public void testSubtractLocalScalars() { + Vector2f a = new Vector2f(5f, 8f); + Vector2f result = a.subtractLocal(1f, 3f); + Assert.assertSame(a, result); + Assert.assertEquals(4f, a.x, 0f); + Assert.assertEquals(5f, a.y, 0f); + } + + // ----------------------------------------------------------------------- + // Multiply + // ----------------------------------------------------------------------- + + @Test + public void testMultScalar() { + Vector2f a = new Vector2f(2f, 3f); + Vector2f result = a.mult(4f); + Assert.assertEquals(8f, result.x, 0f); + Assert.assertEquals(12f, result.y, 0f); + Assert.assertEquals(2f, a.x, 0f); // unaffected + } + + @Test + public void testMultScalarWithStore() { + Vector2f a = new Vector2f(2f, 3f); + Vector2f store = new Vector2f(); + Vector2f result = a.mult(4f, store); + Assert.assertSame(store, result); + Assert.assertEquals(8f, store.x, 0f); + Assert.assertEquals(12f, store.y, 0f); + } + + @Test + public void testMultLocalScalar() { + Vector2f a = new Vector2f(2f, 3f); + Vector2f result = a.multLocal(4f); + Assert.assertSame(a, result); + Assert.assertEquals(8f, a.x, 0f); + Assert.assertEquals(12f, a.y, 0f); + } + + @Test + public void testMultLocalVector() { + Vector2f a = new Vector2f(2f, 3f); + Vector2f b = new Vector2f(4f, 5f); + Vector2f result = a.multLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(8f, a.x, 0f); + Assert.assertEquals(15f, a.y, 0f); + } + + @Test + public void testMultComponents() { + Vector2f a = new Vector2f(2f, 3f); + Vector2f result = a.mult(4f, 5f); + Assert.assertEquals(8f, result.x, 0f); + Assert.assertEquals(15f, result.y, 0f); + } + + // ----------------------------------------------------------------------- + // Divide + // ----------------------------------------------------------------------- + + @Test + public void testDivideScalar() { + Vector2f a = new Vector2f(8f, 12f); + Vector2f result = a.divide(4f); + Assert.assertEquals(2f, result.x, TOLERANCE); + Assert.assertEquals(3f, result.y, TOLERANCE); + Assert.assertEquals(8f, a.x, 0f); // unaffected + } + + @Test + public void testDivideLocalScalar() { + Vector2f a = new Vector2f(8f, 12f); + Vector2f result = a.divideLocal(4f); + Assert.assertSame(a, result); + Assert.assertEquals(2f, a.x, TOLERANCE); + Assert.assertEquals(3f, a.y, TOLERANCE); + } + + @Test + public void testDivideComponents() { + Vector2f a = new Vector2f(8f, 12f); + Vector2f result = a.divide(4f, 3f); + Assert.assertEquals(2f, result.x, TOLERANCE); + Assert.assertEquals(4f, result.y, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Negate + // ----------------------------------------------------------------------- + + @Test + public void testNegate() { + Vector2f a = new Vector2f(3f, -5f); + Vector2f result = a.negate(); + Assert.assertNotSame(a, result); + Assert.assertEquals(-3f, result.x, 0f); + Assert.assertEquals(5f, result.y, 0f); + Assert.assertEquals(3f, a.x, 0f); // unaffected + } + + @Test + public void testNegateLocal() { + Vector2f a = new Vector2f(3f, -5f); + Vector2f result = a.negateLocal(); + Assert.assertSame(a, result); + Assert.assertEquals(-3f, a.x, 0f); + Assert.assertEquals(5f, a.y, 0f); + } + + // ----------------------------------------------------------------------- + // Dot product + // ----------------------------------------------------------------------- + + @Test + public void testDot() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(3f, 4f); + float dot = a.dot(b); + Assert.assertEquals(11f, dot, TOLERANCE); + } + + @Test + public void testDotReturnsZeroForNullArg() { + Vector2f a = new Vector2f(1f, 2f); + Assert.assertEquals(0f, a.dot(null), 0f); + } + + @Test + public void testDotOrthogonal() { + Vector2f a = new Vector2f(1f, 0f); + Vector2f b = new Vector2f(0f, 1f); + Assert.assertEquals(0f, a.dot(b), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Cross product (determinant) + // ----------------------------------------------------------------------- + + @Test + public void testDeterminant() { + Vector2f a = new Vector2f(1f, 0f); + Vector2f b = new Vector2f(0f, 1f); + Assert.assertEquals(1f, a.determinant(b), TOLERANCE); + } + + @Test + public void testCross() { + Vector2f a = new Vector2f(1f, 0f); + Vector2f b = new Vector2f(0f, 1f); + Vector3f result = a.cross(b); + Assert.assertEquals(0f, result.x, TOLERANCE); + Assert.assertEquals(0f, result.y, TOLERANCE); + Assert.assertEquals(1f, result.z, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Length / distance + // ----------------------------------------------------------------------- + + @Test + public void testLength() { + Vector2f a = new Vector2f(3f, 4f); + Assert.assertEquals(5f, a.length(), TOLERANCE); + } + + @Test + public void testLengthSquared() { + Vector2f a = new Vector2f(3f, 4f); + Assert.assertEquals(25f, a.lengthSquared(), TOLERANCE); + } + + @Test + public void testDistance() { + Vector2f a = new Vector2f(0f, 0f); + Vector2f b = new Vector2f(3f, 4f); + Assert.assertEquals(5f, a.distance(b), TOLERANCE); + } + + @Test + public void testDistanceSquared() { + Vector2f a = new Vector2f(0f, 0f); + Vector2f b = new Vector2f(3f, 4f); + Assert.assertEquals(25f, a.distanceSquared(b), TOLERANCE); + } + + @Test + public void testDistanceSquaredComponents() { + Vector2f a = new Vector2f(0f, 0f); + Assert.assertEquals(25f, a.distanceSquared(3f, 4f), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Normalize + // ----------------------------------------------------------------------- + + @Test + public void testNormalize() { + Vector2f a = new Vector2f(3f, 4f); + Vector2f result = a.normalize(); + Assert.assertNotSame(a, result); + Assert.assertEquals(1f, result.length(), TOLERANCE); + Assert.assertEquals(3f / 5f, result.x, TOLERANCE); + Assert.assertEquals(4f / 5f, result.y, TOLERANCE); + // original unaffected + Assert.assertEquals(3f, a.x, 0f); + } + + @Test + public void testNormalizeLocal() { + Vector2f a = new Vector2f(3f, 4f); + Vector2f result = a.normalizeLocal(); + Assert.assertSame(a, result); + Assert.assertEquals(1f, a.length(), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Interpolation + // ----------------------------------------------------------------------- + + @Test + public void testInterpolateLocalFinalVec() { + Vector2f a = new Vector2f(0f, 0f); + Vector2f b = new Vector2f(10f, 20f); + Vector2f result = a.interpolateLocal(b, 0.5f); + Assert.assertSame(a, result); + Assert.assertEquals(5f, a.x, TOLERANCE); + Assert.assertEquals(10f, a.y, TOLERANCE); + } + + @Test + public void testInterpolateLocalBeginEnd() { + Vector2f v = new Vector2f(); + Vector2f begin = new Vector2f(0f, 0f); + Vector2f end = new Vector2f(10f, 20f); + Vector2f result = v.interpolateLocal(begin, end, 0.25f); + Assert.assertSame(v, result); + Assert.assertEquals(2.5f, v.x, TOLERANCE); + Assert.assertEquals(5f, v.y, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Validation + // ----------------------------------------------------------------------- + + @Test + public void testIsValidVector() { + Assert.assertTrue(Vector2f.isValidVector(new Vector2f(1f, 2f))); + Assert.assertFalse(Vector2f.isValidVector(null)); + Assert.assertFalse(Vector2f.isValidVector(new Vector2f(Float.NaN, 0f))); + Assert.assertFalse(Vector2f.isValidVector(new Vector2f(0f, Float.NaN))); + Assert.assertFalse(Vector2f.isValidVector(new Vector2f(Float.POSITIVE_INFINITY, 0f))); + Assert.assertFalse(Vector2f.isValidVector(new Vector2f(0f, Float.NEGATIVE_INFINITY))); + } + + // ----------------------------------------------------------------------- + // Zero / getters / setters + // ----------------------------------------------------------------------- + + @Test + public void testZero() { + Vector2f v = new Vector2f(3f, 4f); + Vector2f result = v.zero(); + Assert.assertSame(v, result); + Assert.assertEquals(0f, v.x, 0f); + Assert.assertEquals(0f, v.y, 0f); + } + + @Test + public void testGettersAndSetters() { + Vector2f v = new Vector2f(); + v.setX(5f); + v.setY(7f); + Assert.assertEquals(5f, v.getX(), 0f); + Assert.assertEquals(7f, v.getY(), 0f); + } + + @Test + public void testGetAngle() { + Vector2f v = new Vector2f(1f, 0f); + Assert.assertEquals(0f, v.getAngle(), TOLERANCE); + + Vector2f v2 = new Vector2f(0f, 1f); + Assert.assertEquals(FastMath.HALF_PI, v2.getAngle(), TOLERANCE); + } + + @Test + public void testSmallestAngleBetween() { + Vector2f a = new Vector2f(1f, 0f); + Vector2f b = new Vector2f(0f, 1f); + Assert.assertEquals(FastMath.HALF_PI, a.smallestAngleBetween(b), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Clone and equals + // ----------------------------------------------------------------------- + + @Test + public void testClone() { + Vector2f original = new Vector2f(3f, -4f); + Vector2f cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.x, cloned.x, 0f); + Assert.assertEquals(original.y, cloned.y, 0f); + } + + @Test + public void testEqualsAndHashCode() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(1f, 2f); + Vector2f c = new Vector2f(1f, 3f); + Assert.assertEquals(a, b); + Assert.assertNotEquals(a, c); + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + public void testToArray() { + Vector2f v = new Vector2f(3f, 4f); + float[] arr = v.toArray(null); + Assert.assertNotNull(arr); + Assert.assertEquals(2, arr.length); + Assert.assertEquals(3f, arr[0], 0f); + Assert.assertEquals(4f, arr[1], 0f); + + float[] provided = new float[2]; + float[] result = v.toArray(provided); + Assert.assertSame(provided, result); + Assert.assertEquals(3f, provided[0], 0f); + Assert.assertEquals(4f, provided[1], 0f); + } + + // ----------------------------------------------------------------------- + // Static constants + // ----------------------------------------------------------------------- + + @Test + public void testStaticConstants() { + Assert.assertEquals(0f, Vector2f.ZERO.x, 0f); + Assert.assertEquals(0f, Vector2f.ZERO.y, 0f); + Assert.assertEquals(1f, Vector2f.UNIT_X.x, 0f); + Assert.assertEquals(0f, Vector2f.UNIT_X.y, 0f); + Assert.assertEquals(0f, Vector2f.UNIT_Y.x, 0f); + Assert.assertEquals(1f, Vector2f.UNIT_Y.y, 0f); + Assert.assertTrue(Float.isNaN(Vector2f.NAN.x)); + Assert.assertTrue(Float.isNaN(Vector2f.NAN.y)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java new file mode 100644 index 0000000000..0febc1b5be --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Vector4f} class works correctly. + */ +public class Vector4fTest { + + private static final float TOLERANCE = 1e-6f; + + // ----------------------------------------------------------------------- + // Constructors and set + // ----------------------------------------------------------------------- + + @Test + public void testDefaultConstructor() { + Vector4f v = new Vector4f(); + Assert.assertEquals(0f, v.x, 0f); + Assert.assertEquals(0f, v.y, 0f); + Assert.assertEquals(0f, v.z, 0f); + Assert.assertEquals(0f, v.w, 0f); + } + + @Test + public void testParameterizedConstructor() { + Vector4f v = new Vector4f(1f, 2f, 3f, 4f); + Assert.assertEquals(1f, v.x, 0f); + Assert.assertEquals(2f, v.y, 0f); + Assert.assertEquals(3f, v.z, 0f); + Assert.assertEquals(4f, v.w, 0f); + } + + @Test + public void testCopyConstructor() { + Vector4f original = new Vector4f(1f, 2f, 3f, 4f); + Vector4f copy = new Vector4f(original); + Assert.assertEquals(original.x, copy.x, 0f); + Assert.assertEquals(original.y, copy.y, 0f); + Assert.assertEquals(original.z, copy.z, 0f); + Assert.assertEquals(original.w, copy.w, 0f); + Assert.assertNotSame(original, copy); + } + + @Test + public void testSet() { + Vector4f v = new Vector4f(); + Vector4f result = v.set(1f, 2f, 3f, 4f); + Assert.assertSame(v, result); + Assert.assertEquals(1f, v.x, 0f); + Assert.assertEquals(2f, v.y, 0f); + Assert.assertEquals(3f, v.z, 0f); + Assert.assertEquals(4f, v.w, 0f); + } + + @Test + public void testSetVector() { + Vector4f v = new Vector4f(); + Vector4f other = new Vector4f(5f, 6f, 7f, 8f); + Vector4f result = v.set(other); + Assert.assertSame(v, result); + Assert.assertEquals(5f, v.x, 0f); + Assert.assertEquals(6f, v.y, 0f); + Assert.assertEquals(7f, v.z, 0f); + Assert.assertEquals(8f, v.w, 0f); + } + + // ----------------------------------------------------------------------- + // Add + // ----------------------------------------------------------------------- + + @Test + public void testAdd() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(5f, 6f, 7f, 8f); + Vector4f result = a.add(b); + Assert.assertNotSame(a, result); + Assert.assertEquals(6f, result.x, 0f); + Assert.assertEquals(8f, result.y, 0f); + Assert.assertEquals(10f, result.z, 0f); + Assert.assertEquals(12f, result.w, 0f); + Assert.assertEquals(1f, a.x, 0f); // unaffected + } + + @Test + public void testAddReturnsNullForNullArg() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Assert.assertNull(a.add((Vector4f) null)); + } + + @Test + public void testAddWithStore() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(5f, 6f, 7f, 8f); + Vector4f store = new Vector4f(); + Vector4f result = a.add(b, store); + Assert.assertSame(store, result); + Assert.assertEquals(6f, store.x, 0f); + Assert.assertEquals(8f, store.y, 0f); + Assert.assertEquals(10f, store.z, 0f); + Assert.assertEquals(12f, store.w, 0f); + } + + @Test + public void testAddScalars() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = a.add(5f, 6f, 7f, 8f); + Assert.assertEquals(6f, result.x, 0f); + Assert.assertEquals(8f, result.y, 0f); + Assert.assertEquals(10f, result.z, 0f); + Assert.assertEquals(12f, result.w, 0f); + } + + @Test + public void testAddLocal() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(5f, 6f, 7f, 8f); + Vector4f result = a.addLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(6f, a.x, 0f); + Assert.assertEquals(8f, a.y, 0f); + Assert.assertEquals(10f, a.z, 0f); + Assert.assertEquals(12f, a.w, 0f); + } + + @Test + public void testAddLocalScalars() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = a.addLocal(5f, 6f, 7f, 8f); + Assert.assertSame(a, result); + Assert.assertEquals(6f, a.x, 0f); + Assert.assertEquals(8f, a.y, 0f); + Assert.assertEquals(10f, a.z, 0f); + Assert.assertEquals(12f, a.w, 0f); + } + + // ----------------------------------------------------------------------- + // Subtract + // ----------------------------------------------------------------------- + + @Test + public void testSubtract() { + Vector4f a = new Vector4f(5f, 8f, 11f, 14f); + Vector4f b = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = a.subtract(b); + Assert.assertNotSame(a, result); + Assert.assertEquals(4f, result.x, 0f); + Assert.assertEquals(6f, result.y, 0f); + Assert.assertEquals(8f, result.z, 0f); + Assert.assertEquals(10f, result.w, 0f); + Assert.assertEquals(5f, a.x, 0f); // unaffected + } + + @Test + public void testSubtractLocal() { + Vector4f a = new Vector4f(5f, 8f, 11f, 14f); + Vector4f b = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = a.subtractLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(4f, a.x, 0f); + Assert.assertEquals(6f, a.y, 0f); + Assert.assertEquals(8f, a.z, 0f); + Assert.assertEquals(10f, a.w, 0f); + } + + @Test + public void testSubtractScalars() { + Vector4f a = new Vector4f(5f, 8f, 11f, 14f); + Vector4f result = a.subtract(1f, 2f, 3f, 4f); + Assert.assertEquals(4f, result.x, 0f); + Assert.assertEquals(6f, result.y, 0f); + Assert.assertEquals(8f, result.z, 0f); + Assert.assertEquals(10f, result.w, 0f); + } + + // ----------------------------------------------------------------------- + // Multiply + // ----------------------------------------------------------------------- + + @Test + public void testMultScalar() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = a.mult(2f); + Assert.assertNotSame(a, result); + Assert.assertEquals(2f, result.x, 0f); + Assert.assertEquals(4f, result.y, 0f); + Assert.assertEquals(6f, result.z, 0f); + Assert.assertEquals(8f, result.w, 0f); + Assert.assertEquals(1f, a.x, 0f); // unaffected + } + + @Test + public void testMultScalarWithStore() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f store = new Vector4f(); + Vector4f result = a.mult(2f, store); + Assert.assertSame(store, result); + Assert.assertEquals(2f, store.x, 0f); + Assert.assertEquals(4f, store.y, 0f); + Assert.assertEquals(6f, store.z, 0f); + Assert.assertEquals(8f, store.w, 0f); + } + + @Test + public void testMultLocalScalar() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = a.multLocal(2f); + Assert.assertSame(a, result); + Assert.assertEquals(2f, a.x, 0f); + Assert.assertEquals(4f, a.y, 0f); + Assert.assertEquals(6f, a.z, 0f); + Assert.assertEquals(8f, a.w, 0f); + } + + @Test + public void testMultLocalVector() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(2f, 3f, 4f, 5f); + Vector4f result = a.multLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(2f, a.x, 0f); + Assert.assertEquals(6f, a.y, 0f); + Assert.assertEquals(12f, a.z, 0f); + Assert.assertEquals(20f, a.w, 0f); + } + + @Test + public void testMultVector() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(2f, 3f, 4f, 5f); + Vector4f result = a.mult(b); + Assert.assertNotSame(a, result); + Assert.assertEquals(2f, result.x, 0f); + Assert.assertEquals(6f, result.y, 0f); + Assert.assertEquals(12f, result.z, 0f); + Assert.assertEquals(20f, result.w, 0f); + } + + // ----------------------------------------------------------------------- + // Divide + // ----------------------------------------------------------------------- + + @Test + public void testDivideScalar() { + Vector4f a = new Vector4f(2f, 4f, 6f, 8f); + Vector4f result = a.divide(2f); + Assert.assertNotSame(a, result); + Assert.assertEquals(1f, result.x, TOLERANCE); + Assert.assertEquals(2f, result.y, TOLERANCE); + Assert.assertEquals(3f, result.z, TOLERANCE); + Assert.assertEquals(4f, result.w, TOLERANCE); + Assert.assertEquals(2f, a.x, 0f); // unaffected + } + + @Test + public void testDivideLocalScalar() { + Vector4f a = new Vector4f(2f, 4f, 6f, 8f); + Vector4f result = a.divideLocal(2f); + Assert.assertSame(a, result); + Assert.assertEquals(1f, a.x, TOLERANCE); + Assert.assertEquals(2f, a.y, TOLERANCE); + Assert.assertEquals(3f, a.z, TOLERANCE); + Assert.assertEquals(4f, a.w, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Negate + // ----------------------------------------------------------------------- + + @Test + public void testNegate() { + Vector4f a = new Vector4f(1f, -2f, 3f, -4f); + Vector4f result = a.negate(); + Assert.assertNotSame(a, result); + Assert.assertEquals(-1f, result.x, 0f); + Assert.assertEquals(2f, result.y, 0f); + Assert.assertEquals(-3f, result.z, 0f); + Assert.assertEquals(4f, result.w, 0f); + Assert.assertEquals(1f, a.x, 0f); // unaffected + } + + @Test + public void testNegateLocal() { + Vector4f a = new Vector4f(1f, -2f, 3f, -4f); + Vector4f result = a.negateLocal(); + Assert.assertSame(a, result); + Assert.assertEquals(-1f, a.x, 0f); + Assert.assertEquals(2f, a.y, 0f); + Assert.assertEquals(-3f, a.z, 0f); + Assert.assertEquals(4f, a.w, 0f); + } + + // ----------------------------------------------------------------------- + // Dot product + // ----------------------------------------------------------------------- + + @Test + public void testDot() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(5f, 6f, 7f, 8f); + // 1*5 + 2*6 + 3*7 + 4*8 = 5 + 12 + 21 + 32 = 70 + Assert.assertEquals(70f, a.dot(b), TOLERANCE); + } + + @Test + public void testDotReturnsZeroForNullArg() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Assert.assertEquals(0f, a.dot(null), 0f); + } + + // ----------------------------------------------------------------------- + // Length / distance + // ----------------------------------------------------------------------- + + @Test + public void testLength() { + // 1^2 + 0 + 0 + 0 = 1 + Vector4f a = new Vector4f(1f, 0f, 0f, 0f); + Assert.assertEquals(1f, a.length(), TOLERANCE); + + // 1^2 + 2^2 + 2^2 + 0 = 9, sqrt = 3 + Vector4f b = new Vector4f(1f, 2f, 2f, 0f); + Assert.assertEquals(3f, b.length(), TOLERANCE); + } + + @Test + public void testLengthSquared() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + // 1 + 4 + 9 + 16 = 30 + Assert.assertEquals(30f, a.lengthSquared(), TOLERANCE); + } + + @Test + public void testDistance() { + Vector4f a = new Vector4f(0f, 0f, 0f, 0f); + Vector4f b = new Vector4f(1f, 0f, 0f, 0f); + Assert.assertEquals(1f, a.distance(b), TOLERANCE); + } + + @Test + public void testDistanceSquared() { + Vector4f a = new Vector4f(0f, 0f, 0f, 0f); + Vector4f b = new Vector4f(2f, 2f, 1f, 0f); + // 4 + 4 + 1 + 0 = 9 + Assert.assertEquals(9f, a.distanceSquared(b), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Normalize + // ----------------------------------------------------------------------- + + @Test + public void testNormalize() { + Vector4f a = new Vector4f(0f, 3f, 0f, 4f); + Vector4f result = a.normalize(); + Assert.assertNotSame(a, result); + Assert.assertEquals(1f, result.length(), TOLERANCE); + // original unaffected + Assert.assertEquals(3f, a.y, 0f); + } + + @Test + public void testNormalizeLocal() { + Vector4f a = new Vector4f(0f, 3f, 0f, 4f); + Vector4f result = a.normalizeLocal(); + Assert.assertSame(a, result); + Assert.assertEquals(1f, a.length(), TOLERANCE); + } + + @Test + public void testIsUnitVector() { + Vector4f a = new Vector4f(1f, 0f, 0f, 0f); + Assert.assertTrue(a.isUnitVector()); + + Vector4f b = new Vector4f(2f, 0f, 0f, 0f); + Assert.assertFalse(b.isUnitVector()); + } + + // ----------------------------------------------------------------------- + // scaleAdd + // ----------------------------------------------------------------------- + + @Test + public void testScaleAdd() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f add = new Vector4f(1f, 1f, 1f, 1f); + Vector4f result = a.scaleAdd(2f, add); + Assert.assertSame(a, result); + Assert.assertEquals(3f, a.x, TOLERANCE); // 1*2 + 1 + Assert.assertEquals(5f, a.y, TOLERANCE); // 2*2 + 1 + Assert.assertEquals(7f, a.z, TOLERANCE); // 3*2 + 1 + Assert.assertEquals(9f, a.w, TOLERANCE); // 4*2 + 1 + } + + @Test + public void testScaleAddWithMult() { + Vector4f v = new Vector4f(); + Vector4f mult = new Vector4f(1f, 2f, 3f, 4f); + Vector4f add = new Vector4f(10f, 10f, 10f, 10f); + Vector4f result = v.scaleAdd(3f, mult, add); + Assert.assertSame(v, result); + Assert.assertEquals(13f, v.x, TOLERANCE); // 1*3 + 10 + Assert.assertEquals(16f, v.y, TOLERANCE); // 2*3 + 10 + Assert.assertEquals(19f, v.z, TOLERANCE); // 3*3 + 10 + Assert.assertEquals(22f, v.w, TOLERANCE); // 4*3 + 10 + } + + // ----------------------------------------------------------------------- + // project + // ----------------------------------------------------------------------- + + @Test + public void testProject() { + // projecting (2,0,0,0) onto (1,0,0,0) should return (2,0,0,0) + Vector4f a = new Vector4f(2f, 0f, 0f, 0f); + Vector4f b = new Vector4f(1f, 0f, 0f, 0f); + Vector4f proj = a.project(b); + Assert.assertEquals(2f, proj.x, TOLERANCE); + Assert.assertEquals(0f, proj.y, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // min/max + // ----------------------------------------------------------------------- + + @Test + public void testMinLocal() { + Vector4f a = new Vector4f(5f, 2f, 8f, 1f); + Vector4f b = new Vector4f(3f, 6f, 4f, 7f); + Vector4f result = a.minLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(3f, a.x, 0f); + Assert.assertEquals(2f, a.y, 0f); + Assert.assertEquals(4f, a.z, 0f); + Assert.assertEquals(1f, a.w, 0f); + } + + @Test + public void testMaxLocal() { + Vector4f a = new Vector4f(5f, 2f, 8f, 1f); + Vector4f b = new Vector4f(3f, 6f, 4f, 7f); + Vector4f result = a.maxLocal(b); + Assert.assertSame(a, result); + Assert.assertEquals(5f, a.x, 0f); + Assert.assertEquals(6f, a.y, 0f); + Assert.assertEquals(8f, a.z, 0f); + Assert.assertEquals(7f, a.w, 0f); + } + + // ----------------------------------------------------------------------- + // Zero / clone / equals + // ----------------------------------------------------------------------- + + @Test + public void testZero() { + Vector4f v = new Vector4f(1f, 2f, 3f, 4f); + Vector4f result = v.zero(); + Assert.assertSame(v, result); + Assert.assertEquals(0f, v.x, 0f); + Assert.assertEquals(0f, v.y, 0f); + Assert.assertEquals(0f, v.z, 0f); + Assert.assertEquals(0f, v.w, 0f); + } + + @Test + public void testClone() { + Vector4f original = new Vector4f(1f, 2f, 3f, 4f); + Vector4f cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.x, cloned.x, 0f); + Assert.assertEquals(original.y, cloned.y, 0f); + Assert.assertEquals(original.z, cloned.z, 0f); + Assert.assertEquals(original.w, cloned.w, 0f); + } + + @Test + public void testEqualsAndHashCode() { + Vector4f a = new Vector4f(1f, 2f, 3f, 4f); + Vector4f b = new Vector4f(1f, 2f, 3f, 4f); + Vector4f c = new Vector4f(1f, 2f, 3f, 5f); + Assert.assertEquals(a, b); + Assert.assertNotEquals(a, c); + Assert.assertEquals(a.hashCode(), b.hashCode()); + } + + // ----------------------------------------------------------------------- + // Static constants + // ----------------------------------------------------------------------- + + @Test + public void testStaticConstants() { + Assert.assertEquals(0f, Vector4f.ZERO.x, 0f); + Assert.assertEquals(0f, Vector4f.ZERO.w, 0f); + Assert.assertEquals(1f, Vector4f.UNIT_X.x, 0f); + Assert.assertEquals(0f, Vector4f.UNIT_X.y, 0f); + Assert.assertEquals(1f, Vector4f.UNIT_W.w, 0f); + Assert.assertEquals(0f, Vector4f.UNIT_W.x, 0f); + Assert.assertTrue(Float.isNaN(Vector4f.NAN.x)); + } +} From d0050c6788d4354626b8883f2c01fd3a66cced81 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:37:07 +0000 Subject: [PATCH 3/6] Add JaCoCo coverage report and 10 new test files covering math, light, audio, and util classes Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com> --- common.gradle | 14 + .../java/com/jme3/audio/ListenerTest.java | 118 ++++++++ .../test/java/com/jme3/light/LightTest.java | 241 +++++++++++++++ .../test/java/com/jme3/math/EasingTest.java | 174 +++++++++++ .../java/com/jme3/math/LineSegmentTest.java | 194 ++++++++++++ .../src/test/java/com/jme3/math/LineTest.java | 108 +++++++ .../test/java/com/jme3/math/PlaneTest.java | 189 ++++++++++++ .../src/test/java/com/jme3/math/RayTest.java | 156 ++++++++++ .../java/com/jme3/math/RectangleTest.java | 137 +++++++++ .../src/test/java/com/jme3/math/RingTest.java | 99 +++++++ .../java/com/jme3/util/BufferUtilsTest.java | 201 +++++++++++++ .../test/java/com/jme3/util/IntMapTest.java | 165 +++++++++++ .../java/com/jme3/util/SafeArrayListTest.java | 276 ++++++++++++++++++ .../test/java/com/jme3/util/SortUtilTest.java | 186 ++++++++++++ 14 files changed, 2258 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/audio/ListenerTest.java create mode 100644 jme3-core/src/test/java/com/jme3/light/LightTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/EasingTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/LineTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/PlaneTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/RayTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/RectangleTest.java create mode 100644 jme3-core/src/test/java/com/jme3/math/RingTest.java create mode 100644 jme3-core/src/test/java/com/jme3/util/BufferUtilsTest.java create mode 100644 jme3-core/src/test/java/com/jme3/util/IntMapTest.java create mode 100644 jme3-core/src/test/java/com/jme3/util/SafeArrayListTest.java create mode 100644 jme3-core/src/test/java/com/jme3/util/SortUtilTest.java diff --git a/common.gradle b/common.gradle index 042d88e3b5..0372a94822 100644 --- a/common.gradle +++ b/common.gradle @@ -8,6 +8,7 @@ apply plugin: 'maven-publish' apply plugin: 'signing' apply plugin: 'eclipse' apply plugin: 'checkstyle' +apply plugin: 'jacoco' eclipse.jdt.file.withProperties { props -> props.setProperty "org.eclipse.jdt.core.circularClasspath", "warning" @@ -79,6 +80,19 @@ test { testLogging { exceptionFormat = 'full' } + finalizedBy jacocoTestReport +} + +jacoco { + toolVersion = '0.8.11' +} + +jacocoTestReport { + dependsOn test + reports { + xml.required.set(true) + html.required.set(true) + } } task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') { diff --git a/jme3-core/src/test/java/com/jme3/audio/ListenerTest.java b/jme3-core/src/test/java/com/jme3/audio/ListenerTest.java new file mode 100644 index 0000000000..cf576b2f14 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/audio/ListenerTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio; + +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Listener} class works correctly. + */ +public class ListenerTest { + + private static final float TOLERANCE = 1e-6f; + + @Test + public void testDefaultConstructor() { + Listener listener = new Listener(); + Assert.assertNotNull(listener.getLocation()); + Assert.assertNotNull(listener.getVelocity()); + Assert.assertNotNull(listener.getRotation()); + Assert.assertEquals(1f, listener.getVolume(), 0f); + } + + @Test + public void testCopyConstructor() { + Listener original = new Listener(); + original.setLocation(new Vector3f(1f, 2f, 3f)); + original.setVelocity(new Vector3f(4f, 5f, 6f)); + original.setVolume(0.5f); + + Listener copy = new Listener(original); + Assert.assertEquals(1f, copy.getLocation().x, TOLERANCE); + Assert.assertEquals(2f, copy.getLocation().y, TOLERANCE); + Assert.assertEquals(3f, copy.getLocation().z, TOLERANCE); + Assert.assertEquals(4f, copy.getVelocity().x, TOLERANCE); + Assert.assertEquals(0.5f, copy.getVolume(), TOLERANCE); + } + + @Test + public void testSetLocation() { + Listener listener = new Listener(); + listener.setLocation(new Vector3f(10f, 20f, 30f)); + Assert.assertEquals(10f, listener.getLocation().x, TOLERANCE); + Assert.assertEquals(20f, listener.getLocation().y, TOLERANCE); + Assert.assertEquals(30f, listener.getLocation().z, TOLERANCE); + } + + @Test + public void testSetVelocity() { + Listener listener = new Listener(); + listener.setVelocity(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(1f, listener.getVelocity().x, TOLERANCE); + } + + @Test + public void testSetRotation() { + Listener listener = new Listener(); + Quaternion q = new Quaternion(0f, 0f, 0f, 1f); + listener.setRotation(q); + Assert.assertEquals(q, listener.getRotation()); + } + + @Test + public void testSetVolume() { + Listener listener = new Listener(); + listener.setVolume(0.7f); + Assert.assertEquals(0.7f, listener.getVolume(), TOLERANCE); + } + + @Test + public void testGetDirectionVectors() { + Listener listener = new Listener(); + // Default rotation should give reasonable direction vectors + Assert.assertNotNull(listener.getLeft()); + Assert.assertNotNull(listener.getUp()); + Assert.assertNotNull(listener.getDirection()); + } + + @Test + public void testSetRendererDoesNotCrash() { + Listener listener = new Listener(); + // Setting null renderer should not crash + listener.setRenderer(null); + // Setting volume with null renderer should not crash + listener.setVolume(0.5f); + } +} diff --git a/jme3-core/src/test/java/com/jme3/light/LightTest.java b/jme3-core/src/test/java/com/jme3/light/LightTest.java new file mode 100644 index 0000000000..7cad19876c --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/light/LightTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.light; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the light classes work correctly. + */ +public class LightTest { + + private static final float TOLERANCE = 1e-6f; + + // ----------------------------------------------------------------------- + // DirectionalLight + // ----------------------------------------------------------------------- + + @Test + public void testDirectionalLightDefaultConstructor() { + DirectionalLight light = new DirectionalLight(); + Assert.assertEquals(Light.Type.Directional, light.getType()); + Assert.assertNotNull(light.getDirection()); + Assert.assertNotNull(light.getColor()); + Assert.assertTrue(light.isEnabled()); + } + + @Test + public void testDirectionalLightConstructorWithDirection() { + Vector3f dir = new Vector3f(0f, -1f, 0f); + DirectionalLight light = new DirectionalLight(dir); + Assert.assertEquals(dir, light.getDirection()); + } + + @Test + public void testDirectionalLightConstructorWithDirectionAndColor() { + Vector3f dir = new Vector3f(0f, -1f, 0f); + ColorRGBA color = new ColorRGBA(1f, 0f, 0f, 1f); + DirectionalLight light = new DirectionalLight(dir, color); + Assert.assertEquals(dir, light.getDirection()); + Assert.assertEquals(color, light.getColor()); + } + + @Test + public void testDirectionalLightSetDirectionNormalizesVector() { + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(0f, -2f, 0f)); // not unit vector + float len = light.getDirection().length(); + Assert.assertEquals(1f, len, TOLERANCE); + } + + @Test + public void testDirectionalLightNameAndEnabled() { + DirectionalLight light = new DirectionalLight(); + light.setName("sun"); + light.setEnabled(false); + Assert.assertEquals("sun", light.getName()); + Assert.assertFalse(light.isEnabled()); + } + + @Test + public void testDirectionalLightClone() { + DirectionalLight original = new DirectionalLight( + new Vector3f(0f, -1f, 0f), ColorRGBA.White); + DirectionalLight cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getDirection(), cloned.getDirection()); + Assert.assertNotSame(original.getDirection(), cloned.getDirection()); + } + + @Test + public void testDirectionalLightToString() { + DirectionalLight light = new DirectionalLight(); + String s = light.toString(); + Assert.assertNotNull(s); + Assert.assertTrue(s.contains("DirectionalLight")); + } + + // ----------------------------------------------------------------------- + // PointLight + // ----------------------------------------------------------------------- + + @Test + public void testPointLightDefaultConstructor() { + PointLight light = new PointLight(); + Assert.assertEquals(Light.Type.Point, light.getType()); + Assert.assertNotNull(light.getPosition()); + Assert.assertEquals(0f, light.getRadius(), 0f); + } + + @Test + public void testPointLightConstructorWithPosition() { + Vector3f pos = new Vector3f(1f, 2f, 3f); + PointLight light = new PointLight(pos); + Assert.assertEquals(pos, light.getPosition()); + } + + @Test + public void testPointLightRadius() { + PointLight light = new PointLight(); + light.setRadius(10f); + Assert.assertEquals(10f, light.getRadius(), TOLERANCE); + // invRadius should be 1/10 + Assert.assertEquals(0.1f, light.getInvRadius(), TOLERANCE); + } + + @Test + public void testPointLightRadiusZeroMeansUnlimited() { + PointLight light = new PointLight(); + light.setRadius(0f); + Assert.assertEquals(0f, light.getRadius(), 0f); + Assert.assertEquals(0f, light.getInvRadius(), 0f); + } + + @Test + public void testPointLightClone() { + PointLight original = new PointLight(new Vector3f(1f, 2f, 3f), ColorRGBA.White, 5f); + PointLight cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getPosition(), cloned.getPosition()); + Assert.assertEquals(original.getRadius(), cloned.getRadius(), 0f); + } + + @Test + public void testPointLightToString() { + PointLight light = new PointLight(); + Assert.assertNotNull(light.toString()); + } + + // ----------------------------------------------------------------------- + // AmbientLight + // ----------------------------------------------------------------------- + + @Test + public void testAmbientLightDefaultConstructor() { + AmbientLight light = new AmbientLight(); + Assert.assertEquals(Light.Type.Ambient, light.getType()); + } + + @Test + public void testAmbientLightConstructorWithColor() { + ColorRGBA color = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); + AmbientLight light = new AmbientLight(color); + Assert.assertEquals(color, light.getColor()); + } + + @Test + public void testAmbientLightToString() { + AmbientLight light = new AmbientLight(); + Assert.assertNotNull(light.toString()); + } + + // ----------------------------------------------------------------------- + // SpotLight + // ----------------------------------------------------------------------- + + @Test + public void testSpotLightDefaultConstructor() { + SpotLight light = new SpotLight(); + Assert.assertEquals(Light.Type.Spot, light.getType()); + Assert.assertNotNull(light.getPosition()); + Assert.assertNotNull(light.getDirection()); + } + + @Test + public void testSpotLightPositionAndDirection() { + Vector3f pos = new Vector3f(0f, 5f, 0f); + Vector3f dir = new Vector3f(0f, -1f, 0f); + SpotLight light = new SpotLight(pos, dir); + Assert.assertEquals(pos, light.getPosition()); + Assert.assertEquals(dir, light.getDirection()); + } + + @Test + public void testSpotLightRange() { + SpotLight light = new SpotLight(); + light.setSpotRange(20f); + Assert.assertEquals(20f, light.getSpotRange(), TOLERANCE); + } + + @Test + public void testSpotLightAngles() { + SpotLight light = new SpotLight(); + light.setSpotInnerAngle(FastMath.DEG_TO_RAD * 10f); + light.setSpotOuterAngle(FastMath.DEG_TO_RAD * 20f); + Assert.assertEquals(FastMath.DEG_TO_RAD * 10f, light.getSpotInnerAngle(), TOLERANCE); + Assert.assertEquals(FastMath.DEG_TO_RAD * 20f, light.getSpotOuterAngle(), TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Light base class + // ----------------------------------------------------------------------- + + @Test + public void testLightColorSetGet() { + DirectionalLight light = new DirectionalLight(); + light.setColor(ColorRGBA.Red); + Assert.assertEquals(ColorRGBA.Red, light.getColor()); + } + + @Test + public void testLightFrustumFlags() { + DirectionalLight light = new DirectionalLight(); + light.setFrustumCheckNeeded(false); + Assert.assertFalse(light.isFrustumCheckNeeded()); + light.setIntersectsFrustum(true); + Assert.assertTrue(light.isIntersectsFrustum()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/EasingTest.java b/jme3-core/src/test/java/com/jme3/math/EasingTest.java new file mode 100644 index 0000000000..8ee920dc9f --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/EasingTest.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Easing} functions work correctly. + */ +public class EasingTest { + + private static final float TOLERANCE = 1e-5f; + + // --- constant --- + + @Test + public void testConstantAlwaysZero() { + Assert.assertEquals(0f, Easing.constant.apply(0f), TOLERANCE); + Assert.assertEquals(0f, Easing.constant.apply(0.5f), TOLERANCE); + Assert.assertEquals(0f, Easing.constant.apply(1f), TOLERANCE); + } + + // --- linear --- + + @Test + public void testLinearEndpoints() { + Assert.assertEquals(0f, Easing.linear.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.linear.apply(1f), TOLERANCE); + Assert.assertEquals(0.5f, Easing.linear.apply(0.5f), TOLERANCE); + } + + // --- inQuad --- + + @Test + public void testInQuadEndpoints() { + Assert.assertEquals(0f, Easing.inQuad.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.inQuad.apply(1f), TOLERANCE); + } + + @Test + public void testInQuadMidpoint() { + // f(0.5) = 0.25 + Assert.assertEquals(0.25f, Easing.inQuad.apply(0.5f), TOLERANCE); + } + + // --- inCubic --- + + @Test + public void testInCubicEndpoints() { + Assert.assertEquals(0f, Easing.inCubic.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.inCubic.apply(1f), TOLERANCE); + } + + @Test + public void testInCubicMidpoint() { + Assert.assertEquals(0.125f, Easing.inCubic.apply(0.5f), TOLERANCE); + } + + // --- outQuad --- + + @Test + public void testOutQuadEndpoints() { + Assert.assertEquals(0f, Easing.outQuad.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.outQuad.apply(1f), TOLERANCE); + } + + @Test + public void testOutQuadMidpoint() { + // outQuad is inversion of inQuad: f(t) = 1 - (1-t)^2 + // f(0.5) = 1 - 0.25 = 0.75 + Assert.assertEquals(0.75f, Easing.outQuad.apply(0.5f), TOLERANCE); + } + + // --- inOutQuad --- + + @Test + public void testInOutQuadEndpoints() { + Assert.assertEquals(0f, Easing.inOutQuad.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.inOutQuad.apply(1f), TOLERANCE); + } + + @Test + public void testInOutQuadMidpoint() { + Assert.assertEquals(0.5f, Easing.inOutQuad.apply(0.5f), TOLERANCE); + } + + // --- smoothStep --- + + @Test + public void testSmoothStepEndpoints() { + Assert.assertEquals(0f, Easing.smoothStep.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.smoothStep.apply(1f), TOLERANCE); + } + + @Test + public void testSmoothStepMidpoint() { + Assert.assertEquals(0.5f, Easing.smoothStep.apply(0.5f), TOLERANCE); + } + + // --- smootherStep --- + + @Test + public void testSmootherStepEndpoints() { + Assert.assertEquals(0f, Easing.smootherStep.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.smootherStep.apply(1f), TOLERANCE); + } + + @Test + public void testSmootherStepMidpoint() { + Assert.assertEquals(0.5f, Easing.smootherStep.apply(0.5f), TOLERANCE); + } + + // --- outBounce --- + + @Test + public void testOutBounceAtZero() { + Assert.assertEquals(0f, Easing.outBounce.apply(0f), TOLERANCE); + } + + @Test + public void testOutBounceAtOne() { + Assert.assertEquals(1f, Easing.outBounce.apply(1f), TOLERANCE); + } + + // --- inOutElastic --- + + @Test + public void testInOutElasticEndpoints() { + Assert.assertEquals(0f, Easing.inOutElastic.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.inOutElastic.apply(1f), TOLERANCE); + } + + // --- inQuart / inQuint --- + + @Test + public void testInQuartMidpoint() { + Assert.assertEquals(0.0625f, Easing.inQuart.apply(0.5f), TOLERANCE); + } + + @Test + public void testInQuintMidpoint() { + Assert.assertEquals(0.03125f, Easing.inQuint.apply(0.5f), TOLERANCE); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java b/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java new file mode 100644 index 0000000000..edb8cd598f --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link LineSegment} class works correctly. + */ +public class LineSegmentTest { + + private static final float TOLERANCE = 1e-5f; + + @Test + public void testDefaultConstructor() { + LineSegment seg = new LineSegment(); + Assert.assertEquals(0f, seg.getExtent(), 0f); + Assert.assertNotNull(seg.getOrigin()); + Assert.assertNotNull(seg.getDirection()); + } + + @Test + public void testOriginDirectionExtentConstructor() { + Vector3f origin = new Vector3f(0f, 0f, 0f); + Vector3f direction = new Vector3f(0f, 1f, 0f); + LineSegment seg = new LineSegment(origin, direction, 5f); + Assert.assertSame(origin, seg.getOrigin()); + Assert.assertSame(direction, seg.getDirection()); + Assert.assertEquals(5f, seg.getExtent(), 0f); + } + + @Test + public void testStartEndConstructor() { + // Segment from (0,0,0) to (0,2,0) -> midpoint at (0,1,0), direction (0,1,0), extent 1 + Vector3f start = new Vector3f(0f, 0f, 0f); + Vector3f end = new Vector3f(0f, 2f, 0f); + LineSegment seg = new LineSegment(start, end); + Assert.assertEquals(0f, seg.getOrigin().x, TOLERANCE); + Assert.assertEquals(1f, seg.getOrigin().y, TOLERANCE); + Assert.assertEquals(0f, seg.getOrigin().z, TOLERANCE); + Assert.assertEquals(0f, seg.getDirection().x, TOLERANCE); + Assert.assertEquals(1f, seg.getDirection().y, TOLERANCE); + Assert.assertEquals(1f, seg.getExtent(), TOLERANCE); + } + + @Test + public void testCopyConstructor() { + LineSegment original = new LineSegment( + new Vector3f(1f, 2f, 3f), + new Vector3f(0f, 1f, 0f), + 4f); + LineSegment copy = new LineSegment(original); + Assert.assertEquals(original.getOrigin(), copy.getOrigin()); + Assert.assertEquals(original.getDirection(), copy.getDirection()); + Assert.assertEquals(original.getExtent(), copy.getExtent(), 0f); + Assert.assertNotSame(original.getOrigin(), copy.getOrigin()); + } + + @Test + public void testSetFromExisting() { + LineSegment source = new LineSegment( + new Vector3f(1f, 2f, 3f), + new Vector3f(0f, 1f, 0f), + 4f); + LineSegment target = new LineSegment(); + target.set(source); + Assert.assertEquals(source.getOrigin(), target.getOrigin()); + Assert.assertEquals(source.getExtent(), target.getExtent(), 0f); + } + + @Test + public void testSettersGetters() { + LineSegment seg = new LineSegment(); + Vector3f origin = new Vector3f(5f, 0f, 0f); + Vector3f direction = new Vector3f(1f, 0f, 0f); + seg.setOrigin(origin); + seg.setDirection(direction); + seg.setExtent(3f); + Assert.assertSame(origin, seg.getOrigin()); + Assert.assertSame(direction, seg.getDirection()); + Assert.assertEquals(3f, seg.getExtent(), 0f); + } + + @Test + public void testGetPositiveEnd() { + // origin=(0,0,0), direction=(0,1,0), extent=5 -> positive end at (0,5,0) + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 5f); + Vector3f posEnd = seg.getPositiveEnd(null); + Assert.assertEquals(0f, posEnd.x, TOLERANCE); + Assert.assertEquals(5f, posEnd.y, TOLERANCE); + Assert.assertEquals(0f, posEnd.z, TOLERANCE); + } + + @Test + public void testGetNegativeEnd() { + // origin=(0,0,0), direction=(0,1,0), extent=5 -> negative end at (0,-5,0) + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 5f); + Vector3f negEnd = seg.getNegativeEnd(null); + Assert.assertEquals(0f, negEnd.x, TOLERANCE); + Assert.assertEquals(-5f, negEnd.y, TOLERANCE); + Assert.assertEquals(0f, negEnd.z, TOLERANCE); + } + + @Test + public void testDistanceSquaredToPointOnSegment() { + // Segment along Y from -1 to +1 (origin=0,0,0; direction=0,1,0; extent=1) + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 1f); + // Point at (3, 0, 0) -> closest point on segment is origin, distance^2 = 9 + Assert.assertEquals(9f, seg.distanceSquared(new Vector3f(3f, 0f, 0f)), TOLERANCE); + } + + @Test + public void testDistanceToPoint() { + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 1f); + Assert.assertEquals(3f, seg.distance(new Vector3f(3f, 0f, 0f)), TOLERANCE); + } + + @Test + public void testIsPointInsideBounds() { + // Segment from (0,0,0) to (0,2,0): origin=(0,1,0), direction=(0,1,0), extent=1 + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 2f, 0f)); + // midpoint + Assert.assertTrue(seg.isPointInsideBounds(new Vector3f(0f, 1f, 0f), 0.01f)); + // way outside + Assert.assertFalse(seg.isPointInsideBounds(new Vector3f(5f, 5f, 5f))); + } + + @Test + public void testClone() { + LineSegment original = new LineSegment( + new Vector3f(1f, 2f, 3f), + new Vector3f(0f, 1f, 0f), + 4f); + LineSegment cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getOrigin(), cloned.getOrigin()); + Assert.assertNotSame(original.getOrigin(), cloned.getOrigin()); + } + + @Test + public void testToString() { + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 1f); + String s = seg.toString(); + Assert.assertNotNull(s); + Assert.assertTrue(s.contains("LineSegment")); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/LineTest.java b/jme3-core/src/test/java/com/jme3/math/LineTest.java new file mode 100644 index 0000000000..d31dc9d53e --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/LineTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Line} class works correctly. + */ +public class LineTest { + + private static final float TOLERANCE = 1e-5f; + + @Test + public void testDefaultConstructor() { + Line l = new Line(); + Assert.assertEquals(0f, l.getOrigin().x, 0f); + Assert.assertEquals(0f, l.getDirection().x, 0f); + } + + @Test + public void testParameterizedConstructor() { + Vector3f origin = new Vector3f(1f, 2f, 3f); + Vector3f direction = new Vector3f(0f, 1f, 0f); + Line l = new Line(origin, direction); + Assert.assertSame(origin, l.getOrigin()); + Assert.assertSame(direction, l.getDirection()); + } + + @Test + public void testSetters() { + Line l = new Line(); + Vector3f origin = new Vector3f(1f, 2f, 3f); + Vector3f direction = new Vector3f(0f, 1f, 0f); + l.setOrigin(origin); + l.setDirection(direction); + Assert.assertSame(origin, l.getOrigin()); + Assert.assertSame(direction, l.getDirection()); + } + + @Test + public void testDistanceSquaredToOrigin() { + // Line along X axis through origin, direction = (1,0,0) + Line l = new Line(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + // A point on the X axis has zero distance + Assert.assertEquals(0f, l.distanceSquared(new Vector3f(5f, 0f, 0f)), TOLERANCE); + } + + @Test + public void testDistanceSquaredPerpendicular() { + // Line along X axis; point at (0, 3, 0) should be 3 units away + Line l = new Line(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(9f, l.distanceSquared(new Vector3f(0f, 3f, 0f)), TOLERANCE); + } + + @Test + public void testDistance() { + Line l = new Line(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(3f, l.distance(new Vector3f(0f, 3f, 0f)), TOLERANCE); + } + + @Test + public void testClone() { + Line original = new Line(new Vector3f(1f, 2f, 3f), new Vector3f(0f, 1f, 0f)); + Line cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getOrigin(), cloned.getOrigin()); + Assert.assertEquals(original.getDirection(), cloned.getDirection()); + } + + @Test + public void testToString() { + Line l = new Line(new Vector3f(1f, 2f, 3f), new Vector3f(0f, 1f, 0f)); + String s = l.toString(); + Assert.assertNotNull(s); + Assert.assertFalse(s.isEmpty()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/PlaneTest.java b/jme3-core/src/test/java/com/jme3/math/PlaneTest.java new file mode 100644 index 0000000000..77ec905793 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/PlaneTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Plane} class works correctly. + */ +public class PlaneTest { + + private static final float TOLERANCE = 1e-6f; + + @Test + public void testDefaultConstructor() { + Plane p = new Plane(); + Assert.assertEquals(0f, p.getConstant(), 0f); + Assert.assertEquals(0f, p.getNormal().x, 0f); + } + + @Test + public void testConstructorNormalConstant() { + Vector3f normal = new Vector3f(0f, 1f, 0f); + Plane p = new Plane(normal, 5f); + Assert.assertEquals(5f, p.getConstant(), 0f); + Assert.assertEquals(0f, p.getNormal().x, 0f); + Assert.assertEquals(1f, p.getNormal().y, 0f); + } + + @Test(expected = IllegalArgumentException.class) + public void testConstructorNullNormalThrows() { + new Plane(null, 1f); + } + + @Test + public void testConstructorNormalPoint() { + Vector3f normal = new Vector3f(0f, 1f, 0f); + Vector3f point = new Vector3f(0f, 3f, 0f); + Plane p = new Plane(normal, point); + // constant = normal.dot(point) = 3 + Assert.assertEquals(3f, p.getConstant(), TOLERANCE); + } + + @Test + public void testSetNormalVector() { + Plane p = new Plane(); + p.setNormal(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(1f, p.getNormal().x, 0f); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetNullNormalThrows() { + Plane p = new Plane(); + p.setNormal(null); + } + + @Test + public void testSetNormalComponents() { + Plane p = new Plane(); + p.setNormal(0f, 0f, 1f); + Assert.assertEquals(1f, p.getNormal().z, 0f); + } + + @Test + public void testSetConstant() { + Plane p = new Plane(); + p.setConstant(7f); + Assert.assertEquals(7f, p.getConstant(), 0f); + } + + @Test + public void testPseudoDistance() { + // Plane y=5: normal=(0,1,0), constant=5 + Plane p = new Plane(new Vector3f(0f, 1f, 0f), 5f); + // Point above: y=7 -> distance = 7-5 = 2 + Assert.assertEquals(2f, p.pseudoDistance(new Vector3f(0f, 7f, 0f)), TOLERANCE); + // Point below: y=3 -> distance = 3-5 = -2 + Assert.assertEquals(-2f, p.pseudoDistance(new Vector3f(0f, 3f, 0f)), TOLERANCE); + // On plane: y=5 -> distance = 0 + Assert.assertEquals(0f, p.pseudoDistance(new Vector3f(0f, 5f, 0f)), TOLERANCE); + } + + @Test + public void testWhichSide() { + Plane p = new Plane(new Vector3f(0f, 1f, 0f), 5f); + Assert.assertEquals(Plane.Side.Positive, p.whichSide(new Vector3f(0f, 7f, 0f))); + Assert.assertEquals(Plane.Side.Negative, p.whichSide(new Vector3f(0f, 3f, 0f))); + Assert.assertEquals(Plane.Side.None, p.whichSide(new Vector3f(0f, 5f, 0f))); + } + + @Test + public void testIsOnPlane() { + Plane p = new Plane(new Vector3f(0f, 1f, 0f), 5f); + Assert.assertTrue(p.isOnPlane(new Vector3f(0f, 5f, 0f))); + Assert.assertFalse(p.isOnPlane(new Vector3f(0f, 6f, 0f))); + } + + @Test + public void testGetClosestPoint() { + // Plane y=0: normal=(0,1,0), constant=0 + Plane p = new Plane(new Vector3f(0f, 1f, 0f), 0f); + Vector3f point = new Vector3f(3f, 5f, 7f); + Vector3f closest = p.getClosestPoint(point); + // Closest point should project onto the plane (y=0) + Assert.assertEquals(3f, closest.x, TOLERANCE); + Assert.assertEquals(0f, closest.y, TOLERANCE); + Assert.assertEquals(7f, closest.z, TOLERANCE); + } + + @Test + public void testReflect() { + // Plane y=0: normal=(0,1,0) + Plane p = new Plane(new Vector3f(0f, 1f, 0f), 0f); + Vector3f point = new Vector3f(0f, 3f, 0f); + Vector3f reflected = p.reflect(point, null); + Assert.assertEquals(0f, reflected.x, TOLERANCE); + Assert.assertEquals(-3f, reflected.y, TOLERANCE); + Assert.assertEquals(0f, reflected.z, TOLERANCE); + } + + @Test + public void testSetPlanePoints() { + Plane p = new Plane(); + p.setPlanePoints( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + // Normal should be (0,0,1), constant = 0 + Assert.assertEquals(0f, p.getNormal().x, TOLERANCE); + Assert.assertEquals(0f, p.getNormal().y, TOLERANCE); + Assert.assertEquals(1f, Math.abs(p.getNormal().z), TOLERANCE); + } + + @Test + public void testSetOriginNormal() { + Vector3f origin = new Vector3f(0f, 5f, 0f); + Vector3f normal = new Vector3f(0f, 1f, 0f); + Plane p = new Plane(); + p.setOriginNormal(origin, normal); + Assert.assertEquals(5f, p.getConstant(), TOLERANCE); + } + + @Test + public void testClone() { + Plane original = new Plane(new Vector3f(0f, 1f, 0f), 5f); + Plane cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getConstant(), cloned.getConstant(), 0f); + Assert.assertEquals(original.getNormal(), cloned.getNormal()); + } + + @Test + public void testToString() { + Plane p = new Plane(new Vector3f(0f, 1f, 0f), 5f); + String s = p.toString(); + Assert.assertNotNull(s); + Assert.assertTrue(s.contains("Plane")); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/RayTest.java b/jme3-core/src/test/java/com/jme3/math/RayTest.java new file mode 100644 index 0000000000..913fe465f1 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/RayTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Ray} class works correctly. + */ +public class RayTest { + + private static final float TOLERANCE = 1e-5f; + + @Test + public void testDefaultConstructor() { + Ray r = new Ray(); + Assert.assertEquals(0f, r.getOrigin().x, 0f); + Assert.assertEquals(0f, r.getOrigin().y, 0f); + Assert.assertEquals(0f, r.getOrigin().z, 0f); + Assert.assertEquals(0f, r.getDirection().x, 0f); + Assert.assertEquals(0f, r.getDirection().y, 0f); + Assert.assertEquals(1f, r.getDirection().z, 0f); + Assert.assertTrue(Float.isInfinite(r.getLimit())); + } + + @Test + public void testParameterizedConstructor() { + Vector3f origin = new Vector3f(1f, 2f, 3f); + Vector3f direction = new Vector3f(0f, 0f, 1f); + Ray r = new Ray(origin, direction); + Assert.assertEquals(1f, r.getOrigin().x, 0f); + Assert.assertEquals(2f, r.getOrigin().y, 0f); + Assert.assertEquals(3f, r.getOrigin().z, 0f); + Assert.assertEquals(0f, r.getDirection().x, 0f); + Assert.assertEquals(0f, r.getDirection().y, 0f); + Assert.assertEquals(1f, r.getDirection().z, 0f); + } + + @Test + public void testSetOriginAndDirection() { + Ray r = new Ray(); + r.setOrigin(new Vector3f(5f, 0f, 0f)); + r.setDirection(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(5f, r.getOrigin().x, 0f); + Assert.assertEquals(1f, r.getDirection().x, 0f); + } + + @Test + public void testSetLimit() { + Ray r = new Ray(); + r.setLimit(10f); + Assert.assertEquals(10f, r.getLimit(), 0f); + } + + @Test + public void testSetFromSource() { + Ray source = new Ray(new Vector3f(1f, 2f, 3f), new Vector3f(0f, 1f, 0f)); + Ray r = new Ray(); + r.set(source); + Assert.assertEquals(1f, r.getOrigin().x, 0f); + Assert.assertEquals(2f, r.getOrigin().y, 0f); + Assert.assertEquals(0f, r.getDirection().x, 0f); + Assert.assertEquals(1f, r.getDirection().y, 0f); + } + + @Test + public void testDistanceSquaredToPoint() { + // Ray along X axis from origin + Ray r = new Ray(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + // Point at (0, 3, 0) - distance squared to X axis = 9 + float d2 = r.distanceSquared(new Vector3f(0f, 3f, 0f)); + Assert.assertEquals(9f, d2, TOLERANCE); + } + + @Test + public void testDistanceSquaredToPointBehindOrigin() { + // Ray pointing along +X from origin; point is at (-5, 0, 0) + // Since it's behind the origin, closest point is the origin + Ray r = new Ray(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + float d2 = r.distanceSquared(new Vector3f(-5f, 0f, 0f)); + Assert.assertEquals(25f, d2, TOLERANCE); + } + + @Test + public void testIntersectWhere_hit() { + // Ray from (0,0,-5) pointing in +Z direction + Ray r = new Ray(new Vector3f(0f, 0f, -5f), new Vector3f(0f, 0f, 1f)); + // Triangle in XY plane at z=0 + Vector3f v0 = new Vector3f(-1f, -1f, 0f); + Vector3f v1 = new Vector3f(1f, -1f, 0f); + Vector3f v2 = new Vector3f(0f, 1f, 0f); + Vector3f loc = new Vector3f(); + boolean hit = r.intersectWhere(v0, v1, v2, loc); + Assert.assertTrue(hit); + Assert.assertEquals(0f, loc.z, TOLERANCE); + } + + @Test + public void testIntersectWhere_miss() { + // Ray pointing away from the triangle + Ray r = new Ray(new Vector3f(5f, 5f, -5f), new Vector3f(0f, 0f, 1f)); + Vector3f v0 = new Vector3f(-1f, -1f, 0f); + Vector3f v1 = new Vector3f(1f, -1f, 0f); + Vector3f v2 = new Vector3f(0f, 1f, 0f); + Vector3f loc = new Vector3f(); + boolean hit = r.intersectWhere(v0, v1, v2, loc); + Assert.assertFalse(hit); + } + + @Test + public void testClone() { + Ray original = new Ray(new Vector3f(1f, 2f, 3f), new Vector3f(0f, 0f, 1f)); + Ray cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getOrigin(), cloned.getOrigin()); + Assert.assertEquals(original.getDirection(), cloned.getDirection()); + } + + @Test + public void testToString() { + Ray r = new Ray(new Vector3f(1f, 2f, 3f), new Vector3f(0f, 0f, 1f)); + String s = r.toString(); + Assert.assertNotNull(s); + Assert.assertTrue(s.contains("Ray")); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/RectangleTest.java b/jme3-core/src/test/java/com/jme3/math/RectangleTest.java new file mode 100644 index 0000000000..d54e5d5813 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/RectangleTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Rectangle} class works correctly. + */ +public class RectangleTest { + + private static final float TOLERANCE = 1e-5f; + + @Test + public void testDefaultConstructor() { + Rectangle r = new Rectangle(); + Assert.assertNotNull(r.getA()); + Assert.assertNotNull(r.getB()); + Assert.assertNotNull(r.getC()); + } + + @Test + public void testParameterizedConstructor() { + Vector3f a = new Vector3f(0f, 0f, 0f); + Vector3f b = new Vector3f(1f, 0f, 0f); + Vector3f c = new Vector3f(0f, 1f, 0f); + Rectangle r = new Rectangle(a, b, c); + Assert.assertSame(a, r.getA()); + Assert.assertSame(b, r.getB()); + Assert.assertSame(c, r.getC()); + } + + @Test + public void testSetters() { + Rectangle r = new Rectangle(); + Vector3f a = new Vector3f(1f, 0f, 0f); + Vector3f b = new Vector3f(0f, 1f, 0f); + Vector3f c = new Vector3f(0f, 0f, 1f); + r.setA(a); + r.setB(b); + r.setC(c); + Assert.assertSame(a, r.getA()); + Assert.assertSame(b, r.getB()); + Assert.assertSame(c, r.getC()); + } + + @Test + public void testCalculateD() { + // A=(0,0,0), B=(1,0,0), C=(0,1,0) => D = B+C-A = (1,1,0) + Rectangle r = new Rectangle( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + Vector3f d = r.calculateD(); + Assert.assertEquals(1f, d.x, TOLERANCE); + Assert.assertEquals(1f, d.y, TOLERANCE); + Assert.assertEquals(0f, d.z, TOLERANCE); + } + + @Test + public void testCalculateNormal() { + // Axis-aligned rectangle in XY plane + Rectangle r = new Rectangle( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + Vector3f normal = r.calculateNormal(null); + Assert.assertNotNull(normal); + // Normal should be (0,0,1) or (0,0,-1) + Assert.assertEquals(0f, normal.x, TOLERANCE); + Assert.assertEquals(0f, normal.y, TOLERANCE); + Assert.assertEquals(1f, Math.abs(normal.z), TOLERANCE); + } + + @Test + public void testRandomReturnsNonNull() { + Rectangle r = new Rectangle( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + Assert.assertNotNull(r.random()); + Vector3f store = new Vector3f(); + Assert.assertSame(store, r.random(store)); + } + + @Test + public void testClone() { + Rectangle original = new Rectangle( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + Rectangle cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getA(), cloned.getA()); + Assert.assertEquals(original.getB(), cloned.getB()); + Assert.assertEquals(original.getC(), cloned.getC()); + } + + @Test + public void testToString() { + Rectangle r = new Rectangle( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + Assert.assertNotNull(r.toString()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/RingTest.java b/jme3-core/src/test/java/com/jme3/math/RingTest.java new file mode 100644 index 0000000000..952f5b7a2c --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/RingTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Ring} class works correctly. + */ +public class RingTest { + + @Test + public void testDefaultConstructor() { + Ring r = new Ring(); + Assert.assertEquals(0f, r.getInnerRadius(), 0f); + Assert.assertEquals(1f, r.getOuterRadius(), 0f); + Assert.assertNotNull(r.getCenter()); + Assert.assertNotNull(r.getUp()); + } + + @Test + public void testParameterizedConstructor() { + Vector3f center = new Vector3f(1f, 2f, 3f); + Vector3f up = new Vector3f(0f, 1f, 0f); + Ring r = new Ring(center, up, 0.5f, 2f); + Assert.assertSame(center, r.getCenter()); + Assert.assertSame(up, r.getUp()); + Assert.assertEquals(0.5f, r.getInnerRadius(), 0f); + Assert.assertEquals(2f, r.getOuterRadius(), 0f); + } + + @Test + public void testSetters() { + Ring r = new Ring(); + Vector3f center = new Vector3f(1f, 0f, 0f); + Vector3f up = new Vector3f(0f, 0f, 1f); + r.setCenter(center); + r.setUp(up); + r.setInnerRadius(1f); + r.setOuterRadius(3f); + Assert.assertSame(center, r.getCenter()); + Assert.assertSame(up, r.getUp()); + Assert.assertEquals(1f, r.getInnerRadius(), 0f); + Assert.assertEquals(3f, r.getOuterRadius(), 0f); + } + + @Test + public void testRandomReturnsNonNull() { + Ring r = new Ring(); + Assert.assertNotNull(r.random()); + + Vector3f store = new Vector3f(); + Assert.assertSame(store, r.random(store)); + } + + @Test + public void testClone() { + Ring original = new Ring( + new Vector3f(1f, 2f, 3f), + new Vector3f(0f, 1f, 0f), + 0.5f, 2f); + Ring cloned = original.clone(); + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getInnerRadius(), cloned.getInnerRadius(), 0f); + Assert.assertEquals(original.getOuterRadius(), cloned.getOuterRadius(), 0f); + Assert.assertEquals(original.getCenter(), cloned.getCenter()); + Assert.assertNotSame(original.getCenter(), cloned.getCenter()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/util/BufferUtilsTest.java b/jme3-core/src/test/java/com/jme3/util/BufferUtilsTest.java new file mode 100644 index 0000000000..9b0955c27b --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/BufferUtilsTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import java.nio.FloatBuffer; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link BufferUtils} class works correctly. + */ +public class BufferUtilsTest { + + private static final float TOLERANCE = 1e-6f; + + // ----------------------------------------------------------------------- + // createFloatBuffer + // ----------------------------------------------------------------------- + + @Test + public void testCreateFloatBufferFromFloatVarargs() { + FloatBuffer buf = BufferUtils.createFloatBuffer(1f, 2f, 3f); + Assert.assertNotNull(buf); + Assert.assertEquals(3, buf.capacity()); + Assert.assertEquals(1f, buf.get(0), TOLERANCE); + Assert.assertEquals(2f, buf.get(1), TOLERANCE); + Assert.assertEquals(3f, buf.get(2), TOLERANCE); + } + + @Test + public void testCreateFloatBufferFromVector3fArray() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new Vector3f(1f, 2f, 3f), + new Vector3f(4f, 5f, 6f)); + Assert.assertEquals(6, buf.capacity()); + Assert.assertEquals(1f, buf.get(0), TOLERANCE); + Assert.assertEquals(2f, buf.get(1), TOLERANCE); + Assert.assertEquals(3f, buf.get(2), TOLERANCE); + } + + @Test + public void testCreateFloatBufferFromVector2fArray() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new Vector2f(1f, 2f), + new Vector2f(3f, 4f)); + Assert.assertEquals(4, buf.capacity()); + Assert.assertEquals(1f, buf.get(0), TOLERANCE); + Assert.assertEquals(2f, buf.get(1), TOLERANCE); + Assert.assertEquals(3f, buf.get(2), TOLERANCE); + Assert.assertEquals(4f, buf.get(3), TOLERANCE); + } + + @Test + public void testCreateFloatBufferFromVector4fArray() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new Vector4f(1f, 2f, 3f, 4f)); + Assert.assertEquals(4, buf.capacity()); + Assert.assertEquals(4f, buf.get(3), TOLERANCE); + } + + @Test + public void testCreateFloatBufferFromColorRGBA() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new ColorRGBA(1f, 0.5f, 0.25f, 1f)); + Assert.assertEquals(4, buf.capacity()); + Assert.assertEquals(1f, buf.get(0), TOLERANCE); + Assert.assertEquals(0.5f, buf.get(1), TOLERANCE); + } + + @Test + public void testCreateFloatBufferFromQuaternion() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new Quaternion(0f, 0f, 0f, 1f)); + Assert.assertEquals(4, buf.capacity()); + } + + // ----------------------------------------------------------------------- + // Vector3 buffer operations + // ----------------------------------------------------------------------- + + @Test + public void testCreateVector3Buffer() { + FloatBuffer buf = BufferUtils.createVector3Buffer(3); + Assert.assertNotNull(buf); + Assert.assertEquals(9, buf.capacity()); + } + + @Test + public void testSetAndPopulateVector3() { + FloatBuffer buf = BufferUtils.createFloatBuffer(6); + Vector3f v = new Vector3f(1f, 2f, 3f); + BufferUtils.setInBuffer(v, buf, 0); + Vector3f result = new Vector3f(); + BufferUtils.populateFromBuffer(result, buf, 0); + Assert.assertEquals(1f, result.x, TOLERANCE); + Assert.assertEquals(2f, result.y, TOLERANCE); + Assert.assertEquals(3f, result.z, TOLERANCE); + } + + @Test + public void testEqualsVector3InBuffer() { + FloatBuffer buf = BufferUtils.createFloatBuffer(6); + Vector3f v = new Vector3f(1f, 2f, 3f); + BufferUtils.setInBuffer(v, buf, 0); + Assert.assertTrue(BufferUtils.equals(v, buf, 0)); + Assert.assertFalse(BufferUtils.equals(new Vector3f(4f, 5f, 6f), buf, 0)); + } + + @Test + public void testGetVector3Array() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new Vector3f(1f, 2f, 3f), + new Vector3f(4f, 5f, 6f)); + Vector3f[] arr = BufferUtils.getVector3Array(buf); + Assert.assertEquals(2, arr.length); + Assert.assertEquals(1f, arr[0].x, TOLERANCE); + Assert.assertEquals(4f, arr[1].x, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Vector2 buffer operations + // ----------------------------------------------------------------------- + + @Test + public void testCreateVector2Buffer() { + FloatBuffer buf = BufferUtils.createVector2Buffer(4); + Assert.assertNotNull(buf); + Assert.assertEquals(8, buf.capacity()); + } + + @Test + public void testSetAndPopulateVector2() { + FloatBuffer buf = BufferUtils.createFloatBuffer(4); + Vector2f v = new Vector2f(3f, 7f); + BufferUtils.setInBuffer(v, buf, 0); + Vector2f result = new Vector2f(); + BufferUtils.populateFromBuffer(result, buf, 0); + Assert.assertEquals(3f, result.x, TOLERANCE); + Assert.assertEquals(7f, result.y, TOLERANCE); + } + + @Test + public void testGetVector2Array() { + FloatBuffer buf = BufferUtils.createFloatBuffer( + new Vector2f(1f, 2f), + new Vector2f(3f, 4f)); + Vector2f[] arr = BufferUtils.getVector2Array(buf); + Assert.assertEquals(2, arr.length); + Assert.assertEquals(1f, arr[0].x, TOLERANCE); + Assert.assertEquals(3f, arr[1].x, TOLERANCE); + } + + // ----------------------------------------------------------------------- + // Vector4 buffer operations + // ----------------------------------------------------------------------- + + @Test + public void testSetAndPopulateVector4() { + FloatBuffer buf = BufferUtils.createFloatBuffer(8); + Vector4f v = new Vector4f(1f, 2f, 3f, 4f); + BufferUtils.setInBuffer(v, buf, 0); + Vector4f result = new Vector4f(); + BufferUtils.populateFromBuffer(result, buf, 0); + Assert.assertEquals(1f, result.x, TOLERANCE); + Assert.assertEquals(4f, result.w, TOLERANCE); + } +} diff --git a/jme3-core/src/test/java/com/jme3/util/IntMapTest.java b/jme3-core/src/test/java/com/jme3/util/IntMapTest.java new file mode 100644 index 0000000000..612aa024f0 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/IntMapTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.util.Iterator; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link IntMap} class works correctly. + */ +public class IntMapTest { + + @Test + public void testDefaultConstructor() { + IntMap map = new IntMap<>(); + Assert.assertEquals(0, map.size()); + } + + @Test + public void testPutAndGet() { + IntMap map = new IntMap<>(); + map.put(1, "one"); + map.put(2, "two"); + Assert.assertEquals("one", map.get(1)); + Assert.assertEquals("two", map.get(2)); + Assert.assertEquals(2, map.size()); + } + + @Test + public void testGetMissingKey() { + IntMap map = new IntMap<>(); + Assert.assertNull(map.get(999)); + } + + @Test + public void testContainsKey() { + IntMap map = new IntMap<>(); + map.put(5, "five"); + Assert.assertTrue(map.containsKey(5)); + Assert.assertFalse(map.containsKey(99)); + } + + @Test + public void testContainsValue() { + IntMap map = new IntMap<>(); + map.put(1, "hello"); + Assert.assertTrue(map.containsValue("hello")); + Assert.assertFalse(map.containsValue("world")); + } + + @Test + public void testRemove() { + IntMap map = new IntMap<>(); + map.put(1, "one"); + map.put(2, "two"); + String removed = map.remove(1); + Assert.assertEquals("one", removed); + Assert.assertEquals(1, map.size()); + Assert.assertNull(map.get(1)); + } + + @Test + public void testRemoveMissingKey() { + IntMap map = new IntMap<>(); + Assert.assertNull(map.remove(42)); + } + + @Test + public void testClear() { + IntMap map = new IntMap<>(); + map.put(1, "one"); + map.put(2, "two"); + map.clear(); + Assert.assertEquals(0, map.size()); + Assert.assertNull(map.get(1)); + } + + @Test + public void testOverwriteExistingKey() { + IntMap map = new IntMap<>(); + map.put(1, "first"); + String old = map.put(1, "second"); + Assert.assertEquals("first", old); + Assert.assertEquals("second", map.get(1)); + Assert.assertEquals(1, map.size()); + } + + @Test + public void testIterator() { + IntMap map = new IntMap<>(); + map.put(10, "ten"); + map.put(20, "twenty"); + map.put(30, "thirty"); + int count = 0; + for (IntMap.Entry entry : map) { + Assert.assertNotNull(entry.getValue()); + count++; + } + Assert.assertEquals(3, count); + } + + @Test + public void testClone() { + IntMap original = new IntMap<>(); + original.put(1, "one"); + original.put(2, "two"); + IntMap cloned = original.clone(); + Assert.assertEquals(original.size(), cloned.size()); + Assert.assertEquals("one", cloned.get(1)); + // Verify independence + cloned.put(3, "three"); + Assert.assertEquals(2, original.size()); + } + + @Test + public void testLargeMap() { + IntMap map = new IntMap<>(); + for (int i = 0; i < 100; i++) { + map.put(i, i * 10); + } + Assert.assertEquals(100, map.size()); + for (int i = 0; i < 100; i++) { + Assert.assertEquals(Integer.valueOf(i * 10), map.get(i)); + } + } + + @Test + public void testNegativeKeys() { + IntMap map = new IntMap<>(); + map.put(-1, "neg1"); + map.put(-100, "neg100"); + Assert.assertEquals("neg1", map.get(-1)); + Assert.assertEquals("neg100", map.get(-100)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/util/SafeArrayListTest.java b/jme3-core/src/test/java/com/jme3/util/SafeArrayListTest.java new file mode 100644 index 0000000000..0634316eb1 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/SafeArrayListTest.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link SafeArrayList} class works correctly. + */ +public class SafeArrayListTest { + + @Test + public void testDefaultConstructor() { + SafeArrayList list = new SafeArrayList<>(String.class); + Assert.assertTrue(list.isEmpty()); + Assert.assertEquals(0, list.size()); + } + + @Test + public void testAddAndGet() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + list.add("c"); + Assert.assertEquals(3, list.size()); + Assert.assertEquals("a", list.get(0)); + Assert.assertEquals("b", list.get(1)); + Assert.assertEquals("c", list.get(2)); + } + + @Test + public void testAddAtIndex() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("c"); + list.add(1, "b"); + Assert.assertEquals("b", list.get(1)); + Assert.assertEquals("c", list.get(2)); + } + + @Test + public void testContains() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("hello"); + Assert.assertTrue(list.contains("hello")); + Assert.assertFalse(list.contains("world")); + } + + @Test + public void testRemoveByObject() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + boolean removed = list.remove("a"); + Assert.assertTrue(removed); + Assert.assertEquals(1, list.size()); + Assert.assertEquals("b", list.get(0)); + } + + @Test + public void testRemoveByIndex() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + String removed = list.remove(0); + Assert.assertEquals("a", removed); + Assert.assertEquals(1, list.size()); + } + + @Test + public void testSet() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + String old = list.set(0, "z"); + Assert.assertEquals("a", old); + Assert.assertEquals("z", list.get(0)); + } + + @Test + public void testClear() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + list.clear(); + Assert.assertTrue(list.isEmpty()); + Assert.assertEquals(0, list.size()); + } + + @Test + public void testIndexOf() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + list.add("a"); + Assert.assertEquals(0, list.indexOf("a")); + Assert.assertEquals(2, list.lastIndexOf("a")); + Assert.assertEquals(-1, list.indexOf("z")); + } + + @Test + public void testAddAll() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.addAll(Arrays.asList("x", "y", "z")); + Assert.assertEquals(3, list.size()); + Assert.assertEquals("x", list.get(0)); + } + + @Test + public void testAddAllAtIndex() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("d"); + list.addAll(1, Arrays.asList("b", "c")); + Assert.assertEquals(4, list.size()); + Assert.assertEquals("b", list.get(1)); + Assert.assertEquals("c", list.get(2)); + Assert.assertEquals("d", list.get(3)); + } + + @Test + public void testRemoveAll() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.addAll(Arrays.asList("a", "b", "c", "b")); + list.removeAll(Arrays.asList("b")); + Assert.assertEquals(2, list.size()); + Assert.assertFalse(list.contains("b")); + } + + @Test + public void testRetainAll() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.addAll(Arrays.asList("a", "b", "c")); + list.retainAll(Arrays.asList("a", "c")); + Assert.assertEquals(2, list.size()); + Assert.assertFalse(list.contains("b")); + } + + @Test + public void testContainsAll() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.addAll(Arrays.asList("a", "b", "c")); + Assert.assertTrue(list.containsAll(Arrays.asList("a", "b"))); + Assert.assertFalse(list.containsAll(Arrays.asList("a", "z"))); + } + + @Test + public void testGetArray() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + String[] arr = list.getArray(); + Assert.assertNotNull(arr); + Assert.assertEquals(2, arr.length); + Assert.assertEquals("a", arr[0]); + Assert.assertEquals("b", arr[1]); + } + + @Test + public void testToArray() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + Object[] arr = list.toArray(); + Assert.assertEquals(2, arr.length); + } + + @Test + public void testToArrayTyped() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + String[] arr = list.toArray(new String[0]); + Assert.assertEquals(2, arr.length); + Assert.assertEquals("a", arr[0]); + } + + @Test + public void testIterator() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("x"); + list.add("y"); + Iterator it = list.iterator(); + Assert.assertTrue(it.hasNext()); + Assert.assertEquals("x", it.next()); + Assert.assertEquals("y", it.next()); + Assert.assertFalse(it.hasNext()); + } + + @Test + public void testSubList() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.addAll(Arrays.asList("a", "b", "c", "d")); + List sub = list.subList(1, 3); + Assert.assertEquals(2, sub.size()); + Assert.assertEquals("b", sub.get(0)); + Assert.assertEquals("c", sub.get(1)); + } + + @Test + public void testEqualsAndHashCode() { + SafeArrayList list1 = new SafeArrayList<>(String.class); + SafeArrayList list2 = new SafeArrayList<>(String.class); + list1.addAll(Arrays.asList("a", "b", "c")); + list2.addAll(Arrays.asList("a", "b", "c")); + Assert.assertEquals(list1, list2); + Assert.assertEquals(list1.hashCode(), list2.hashCode()); + } + + @Test + public void testClone() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.add("a"); + list.add("b"); + SafeArrayList cloned = list.clone(); + Assert.assertNotSame(list, cloned); + Assert.assertEquals(list, cloned); + // Verify independence + cloned.add("c"); + Assert.assertEquals(2, list.size()); + } + + @Test + public void testListIterator() { + SafeArrayList list = new SafeArrayList<>(String.class); + list.addAll(Arrays.asList("a", "b", "c")); + ListIterator it = list.listIterator(); + Assert.assertTrue(it.hasNext()); + Assert.assertEquals("a", it.next()); + Assert.assertTrue(it.hasPrevious()); + Assert.assertEquals("a", it.previous()); + Assert.assertEquals(0, it.nextIndex()); + Assert.assertEquals(-1, it.previousIndex()); + } + + @Test + public void testConstructorWithCollection() { + List source = Arrays.asList("x", "y", "z"); + SafeArrayList list = new SafeArrayList<>(String.class, source); + Assert.assertEquals(3, list.size()); + Assert.assertEquals("x", list.get(0)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/util/SortUtilTest.java b/jme3-core/src/test/java/com/jme3/util/SortUtilTest.java new file mode 100644 index 0000000000..809c1a9b44 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/SortUtilTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.util.Arrays; +import java.util.Comparator; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link SortUtil} sorting algorithms work correctly. + */ +public class SortUtilTest { + + private static final Comparator INT_CMP = Comparator.naturalOrder(); + + // ----------------------------------------------------------------------- + // gsort (gnome sort) + // ----------------------------------------------------------------------- + + @Test + public void testGsortEmptyArray() { + Integer[] arr = {}; + SortUtil.gsort(arr, INT_CMP); + Assert.assertEquals(0, arr.length); + } + + @Test + public void testGsortSingleElement() { + Integer[] arr = {42}; + SortUtil.gsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{42}, arr); + } + + @Test + public void testGsortAlreadySorted() { + Integer[] arr = {1, 2, 3, 4, 5}; + SortUtil.gsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{1, 2, 3, 4, 5}, arr); + } + + @Test + public void testGsortReverseSorted() { + Integer[] arr = {5, 4, 3, 2, 1}; + SortUtil.gsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{1, 2, 3, 4, 5}, arr); + } + + @Test + public void testGsortRandom() { + Integer[] arr = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3}; + Integer[] expected = arr.clone(); + Arrays.sort(expected, INT_CMP); + SortUtil.gsort(arr, INT_CMP); + Assert.assertArrayEquals(expected, arr); + } + + @Test + public void testGsortDuplicates() { + Integer[] arr = {2, 2, 1, 1, 3, 3}; + SortUtil.gsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{1, 1, 2, 2, 3, 3}, arr); + } + + // ----------------------------------------------------------------------- + // qsort (quicksort on Object[]) + // ----------------------------------------------------------------------- + + @Test + public void testQsortEmptyArray() { + Integer[] arr = {}; + SortUtil.qsort(arr, INT_CMP); + Assert.assertEquals(0, arr.length); + } + + @Test + public void testQsortSingleElement() { + Integer[] arr = {7}; + SortUtil.qsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{7}, arr); + } + + @Test + public void testQsortAlreadySorted() { + Integer[] arr = {1, 2, 3, 4, 5}; + SortUtil.qsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{1, 2, 3, 4, 5}, arr); + } + + @Test + public void testQsortReverseSorted() { + Integer[] arr = {5, 4, 3, 2, 1}; + SortUtil.qsort(arr, INT_CMP); + Assert.assertArrayEquals(new Integer[]{1, 2, 3, 4, 5}, arr); + } + + @Test + public void testQsortRandom() { + Integer[] arr = {7, 2, 9, 1, 4, 8, 3, 5, 6}; + Integer[] expected = arr.clone(); + Arrays.sort(expected, INT_CMP); + SortUtil.qsort(arr, INT_CMP); + Assert.assertArrayEquals(expected, arr); + } + + // ----------------------------------------------------------------------- + // qsort (quicksort on int[]) + // ----------------------------------------------------------------------- + + @Test + public void testQsortIntArray() { + int[] arr = {5, 3, 8, 1, 4, 2}; + Comparator intCmp = Comparator.naturalOrder(); + SortUtil.qsort(arr, 0, arr.length - 1, intCmp); + int[] expected = {1, 2, 3, 4, 5, 8}; + Assert.assertArrayEquals(expected, arr); + } + + @Test + public void testQsortIntArrayAlreadySorted() { + int[] arr = {1, 2, 3, 4, 5}; + Comparator intCmp = Comparator.naturalOrder(); + SortUtil.qsort(arr, 0, arr.length - 1, intCmp); + Assert.assertArrayEquals(new int[]{1, 2, 3, 4, 5}, arr); + } + + // ----------------------------------------------------------------------- + // msort (merge sort) + // ----------------------------------------------------------------------- + + @Test + public void testMsortBasic() { + Integer[] src = {3, 1, 4, 1, 5, 9, 2, 6}; + Integer[] dest = new Integer[src.length]; + Integer[] expected = src.clone(); + Arrays.sort(expected, INT_CMP); + SortUtil.msort(src, dest, INT_CMP); + // After msort the sorted result ends up in src + Assert.assertArrayEquals(expected, src); + } + + @Test + public void testMsortAlreadySorted() { + Integer[] src = {1, 2, 3, 4, 5}; + Integer[] dest = new Integer[src.length]; + SortUtil.msort(src, dest, INT_CMP); + Assert.assertArrayEquals(new Integer[]{1, 2, 3, 4, 5}, src); + } + + @Test + public void testMsortSingleElement() { + Integer[] src = {99}; + Integer[] dest = new Integer[1]; + SortUtil.msort(src, dest, INT_CMP); + Assert.assertArrayEquals(new Integer[]{99}, src); + } +} From 3a1fc54ca06176d35582e3a8b873aad32304c366 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:55:12 +0000 Subject: [PATCH 4/6] Add comprehensive behavioral JUnit 4 tests for jme3 math library Add complex behavioral tests covering logic correctness, invariants, edge cases, and cross-method consistency for: - Matrix3fTest: near-singular invertLocal regression, adjoint identity, rotation matrix orthogonality (R^-1 == R^T), mult associativity/ non-commutativity, angle-axis rotations, scale inverse reciprocal, float-array round-trip (11 new tests) - Matrix4fTest: near-singular invert/invertLocal regressions, translation composition, homogeneous vector transform, rotation composition, scale matrix, affine inverse round-trip (9 new tests) - QuaternionTest: identity, angle-axis round-trip, norm (squared), normalizeLocal, multiply identity/inverse, conjugate anti-homomorphism, fromAngles round-trip, slerp at 0/0.5/1, rotation matrix round-trip, rotateVector, mult associativity, add/subtract (15 new tests) - Vector2fTest: dot geometry, cross z-component, normalize unit/direction, length == distance, angle between, interpolateLocal at 0/0.5/1, add-subtract round-trip, mult-divide inverse (11 new tests) - Vector4fTest: dot geometry, normalize unit, projection idempotency, projection onto self, scaleAdd (2-arg and 3-arg), minLocal/maxLocal, add-subtract round-trip, negateLocal twice (9 new tests) - PlaneTest: reflect preserves distance, reflect point on plane, setPlanePoints all on plane, getClosestPoint on plane / minimises distance, non-axis-aligned plane (6 new tests) - RayTest: parallel-to-triangle no hit, behind-origin no hit, distanceSquared point on ray, perpendicular distance, vertex edge case (5 new tests) - LineSegmentTest: distance to self, parallel segments, perpendicular segments, positive/negative end vector identity, isPointInsideBounds with error, distanceSquared at negative end (6 new tests) - EasingTest: linear/inQuad monotonicity, outBounce boundary values, inOutQuad symmetry, outQuad faster than inQuad early, smoothStep zero derivative at endpoints, custom InOut boundary values (7 new tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../test/java/com/jme3/math/EasingTest.java | 95 ++++++++ .../java/com/jme3/math/LineSegmentTest.java | 105 +++++++++ .../test/java/com/jme3/math/Matrix3fTest.java | 223 ++++++++++++++++++ .../test/java/com/jme3/math/Matrix4fTest.java | 170 +++++++++++++ .../test/java/com/jme3/math/PlaneTest.java | 97 ++++++++ .../java/com/jme3/math/QuaternionTest.java | 223 ++++++++++++++++++ .../src/test/java/com/jme3/math/RayTest.java | 84 +++++++ .../test/java/com/jme3/math/Vector2fTest.java | 116 +++++++++ .../test/java/com/jme3/math/Vector4fTest.java | 120 ++++++++++ 9 files changed, 1233 insertions(+) diff --git a/jme3-core/src/test/java/com/jme3/math/EasingTest.java b/jme3-core/src/test/java/com/jme3/math/EasingTest.java index 8ee920dc9f..6bc60061d9 100644 --- a/jme3-core/src/test/java/com/jme3/math/EasingTest.java +++ b/jme3-core/src/test/java/com/jme3/math/EasingTest.java @@ -171,4 +171,99 @@ public void testInQuartMidpoint() { public void testInQuintMidpoint() { Assert.assertEquals(0.03125f, Easing.inQuint.apply(0.5f), TOLERANCE); } + + // ----------------------------------------------------------------------- + // Complex behavioral tests + // ----------------------------------------------------------------------- + + /** linear must be monotonically non-decreasing on [0, 1]. */ + @Test + public void testMonotonicity_linear() { + float prev = Easing.linear.apply(0f); + for (int i = 1; i <= 10; i++) { + float t = i / 10f; + float cur = Easing.linear.apply(t); + Assert.assertTrue("linear must be non-decreasing at t=" + t, cur >= prev - TOLERANCE); + prev = cur; + } + } + + /** inQuad must be monotonically increasing on [0, 1]. */ + @Test + public void testMonotonicity_inQuad() { + float prev = Easing.inQuad.apply(0f); + for (int i = 1; i <= 10; i++) { + float t = i / 10f; + float cur = Easing.inQuad.apply(t); + Assert.assertTrue("inQuad must be non-decreasing at t=" + t, cur >= prev - TOLERANCE); + prev = cur; + } + } + + /** outBounce must start at 0 and end at 1. */ + @Test + public void testMonotonicity_outBounce() { + // outBounce starts at 0 and ends at 1 (it "bounces" up, hence not strictly monotone) + Assert.assertEquals(0f, Easing.outBounce.apply(0f), TOLERANCE); + Assert.assertEquals(1f, Easing.outBounce.apply(1f), TOLERANCE); + // Values must stay within [0, 1] + for (int i = 0; i <= 20; i++) { + float t = i / 20f; + float val = Easing.outBounce.apply(t); + Assert.assertTrue("outBounce value at t=" + t + " must be >= 0", val >= -TOLERANCE); + Assert.assertTrue("outBounce value at t=" + t + " must be <= 1", val <= 1f + TOLERANCE); + } + } + + /** + * inOutQuad must be symmetric around (0.5, 0.5): f(0.5) == 0.5 and + * f(1−t) == 1 − f(t) for all t. + */ + @Test + public void testInOutSymmetry_inOutQuad() { + Assert.assertEquals(0.5f, Easing.inOutQuad.apply(0.5f), TOLERANCE); + float[] testPoints = {0.1f, 0.25f, 0.4f, 0.6f, 0.75f, 0.9f}; + for (float t : testPoints) { + float ft = Easing.inOutQuad.apply(t); + float f1t = Easing.inOutQuad.apply(1f - t); + Assert.assertEquals("inOutQuad symmetry at t=" + t, 1f - ft, f1t, TOLERANCE); + } + } + + /** outQuad must progress faster early than inQuad (out is fast then slow). */ + @Test + public void testOutIsFastThenSlow_outQuad() { + float tEarly = 0.25f; + Assert.assertTrue("outQuad(0.25) must be > inQuad(0.25)", + Easing.outQuad.apply(tEarly) > Easing.inQuad.apply(tEarly)); + } + + /** + * smoothStep has zero derivative at the endpoints: f(ε) must be very + * close to 0, confirming the flat start. + */ + @Test + public void testSmoothStepDerivativeAtEndsIsZero() { + float eps = 1e-3f; + // At t=0 the derivative is 0, so f(eps)/eps ≈ 0 + float slope0 = Easing.smoothStep.apply(eps) / eps; + Assert.assertEquals(0f, slope0, 1e-2f); + // At t=1 the derivative is 0, so (f(1) - f(1-eps))/eps ≈ 0 + float slope1 = (Easing.smoothStep.apply(1f) - Easing.smoothStep.apply(1f - eps)) / eps; + Assert.assertEquals(0f, slope1, 1e-2f); + } + + /** + * A custom InOut built from inQuad and outCubic must satisfy: + * apply(0) == 0, apply(1) == 1, and apply(0.5) is the boundary value + * (inQuad(1)/2 == 0.5). + */ + @Test + public void testCustomInOut() { + EaseFunction custom = new Easing.InOut(Easing.inQuad, Easing.outCubic); + Assert.assertEquals(0f, custom.apply(0f), TOLERANCE); + Assert.assertEquals(1f, custom.apply(1f), TOLERANCE); + // At t=0.5: InOut evaluates in-half: in.apply(1.0)/2 = inQuad(1)/2 = 0.5 + Assert.assertEquals(0.5f, custom.apply(0.5f), TOLERANCE); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java b/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java index edb8cd598f..27c53e5ab6 100644 --- a/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java +++ b/jme3-core/src/test/java/com/jme3/math/LineSegmentTest.java @@ -191,4 +191,109 @@ public void testToString() { Assert.assertNotNull(s); Assert.assertTrue(s.contains("LineSegment")); } + + // ----------------------------------------------------------------------- + // Complex behavioral tests + // ----------------------------------------------------------------------- + + /** The distance from a segment to itself must be 0. */ + @Test + public void testDistanceToSelf() { + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 3f); + Assert.assertEquals(0f, seg.distance(seg), TOLERANCE); + } + + /** + * Two parallel segments separated by a known distance must report that + * distance. + */ + @Test + public void testDistanceBetweenParallelSegments() { + // Segment A: along Y axis at x=0, z=0 + LineSegment a = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 5f); + // Segment B: along Y axis at x=3, z=0 — perpendicular distance is 3 + LineSegment b = new LineSegment( + new Vector3f(3f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 5f); + Assert.assertEquals(3f, a.distance(b), TOLERANCE); + } + + /** + * Two perpendicular, non-intersecting segments must report a positive + * distance equal to the gap between them. + */ + @Test + public void testDistanceBetweenPerpendicularSegments() { + // Segment A: along X axis (y=0, z=0), extent 1 → x ∈ [-1, 1] + LineSegment a = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(1f, 0f, 0f), + 1f); + // Segment B: along Y axis (x=0, z=5), extent 1 → offset by 5 in Z + LineSegment b = new LineSegment( + new Vector3f(0f, 0f, 5f), + new Vector3f(0f, 1f, 0f), + 1f); + // Closest points are (0,0,0) and (0,0,5) → distance = 5 + Assert.assertEquals(5f, a.distance(b), TOLERANCE); + } + + /** + * positiveEnd − negativeEnd must equal 2 * extent * direction. + */ + @Test + public void testGetPositiveEndAndNegativeEndAreCorrect() { + Vector3f origin = new Vector3f(1f, 2f, 3f); + Vector3f direction = new Vector3f(0f, 1f, 0f); + float extent = 4f; + LineSegment seg = new LineSegment(origin, direction, extent); + + Vector3f posEnd = seg.getPositiveEnd(null); + Vector3f negEnd = seg.getNegativeEnd(null); + Vector3f diff = posEnd.subtract(negEnd); + + Assert.assertEquals(2f * extent * direction.x, diff.x, TOLERANCE); + Assert.assertEquals(2f * extent * direction.y, diff.y, TOLERANCE); + Assert.assertEquals(2f * extent * direction.z, diff.z, TOLERANCE); + } + + /** + * A point that is slightly outside the segment bounds but within the + * specified error tolerance must be reported as inside bounds. + */ + @Test + public void testIsPointInsideBoundsWithError() { + // Segment: origin=(0,0,0), direction=(0,1,0), extent=1 → y ∈ [-1,1] + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 1f); + // Point just past the positive end but within error 0.1 + Vector3f justOutside = new Vector3f(0f, 1.05f, 0f); + Assert.assertTrue(seg.isPointInsideBounds(justOutside, 0.1f)); + // Point well outside must still fail + Assert.assertFalse(seg.isPointInsideBounds(new Vector3f(0f, 5f, 0f))); + } + + /** + * The squared distance from a point at the exact negative endpoint of the + * segment to the segment must be 0. + */ + @Test + public void testDistanceSquaredPointAtNegativeEnd() { + LineSegment seg = new LineSegment( + new Vector3f(0f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + 3f); + Vector3f negEnd = seg.getNegativeEnd(null); + float d2 = seg.distanceSquared(negEnd); + Assert.assertEquals(0f, d2, TOLERANCE); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java b/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java index 92e4e8ca6a..22323a6125 100644 --- a/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java +++ b/jme3-core/src/test/java/com/jme3/math/Matrix3fTest.java @@ -510,4 +510,227 @@ public void testStaticConstants() { } } } + + // ----------------------------------------------------------------------- + // Complex behavioral / regression tests + // ----------------------------------------------------------------------- + + /** + * REGRESSION: diagonal matrix with det = FLT_EPSILON/2 (below the threshold) + * must cause invertLocal() to zero the matrix, not produce garbage. + */ + @Test + public void testInvertLocalNearSingularReturnsZero() { + float smallVal = FastMath.FLT_EPSILON / 2f; + Matrix3f m = new Matrix3f( + 1f, 0f, 0f, + 0f, 1f, 0f, + 0f, 0f, smallVal); + // det = smallVal < FLT_EPSILON => should zero out + Matrix3f result = m.invertLocal(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals("element (" + i + "," + j + ") must be 0", + 0f, result.get(i, j), 0f); + } + } + } + + /** + * Verifies that invert() and invertLocal() are consistent for a singular + * matrix — both must return an all-zero matrix. + */ + @Test + public void testInvertLocalConsistentWithInvert() { + // Rows are linearly dependent → singular (det = 0) + Matrix3f m = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + + // invert() (non-destructive) + Matrix3f invertResult = m.invert(); + // invertLocal() on an independent copy + Matrix3f copyForLocal = new Matrix3f(m); + Matrix3f localResult = copyForLocal.invertLocal(); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals("invert() element (" + i + "," + j + ")", + 0f, invertResult.get(i, j), 0f); + Assert.assertEquals("invertLocal() element (" + i + "," + j + ")", + 0f, localResult.get(i, j), 0f); + } + } + } + + /** + * For an invertible matrix, invertLocal() on a copy must produce the same + * result as the full inverse: copy.invertLocal() * original == I. + */ + @Test + public void testInvertLocalTimesOriginalIsIdentity() { + Matrix3f m = new Matrix3f( + 1f, 2f, 0f, + 0f, 1f, 3f, + 0f, 0f, 1f); + Matrix3f copy = new Matrix3f(m); + copy.invertLocal(); + Matrix3f product = copy.mult(m); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + float expected = (i == j) ? 1f : 0f; + Assert.assertEquals("product[" + i + "][" + j + "]", + expected, product.get(i, j), TOLERANCE); + } + } + } + + /** + * Rotation matrices are orthogonal: R^-1 must equal R^T. + * Tests a 90° rotation about the Z axis. + */ + @Test + public void testRotationMatrixInverseEqualsTranspose() { + Matrix3f r = new Matrix3f(); + r.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + + Matrix3f inv = r.invert(); + Matrix3f trans = r.transposeNew(); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals("element (" + i + "," + j + ")", + trans.get(i, j), inv.get(i, j), TOLERANCE); + } + } + } + + /** + * Matrix multiplication must be associative: (A*B)*C == A*(B*C). + */ + @Test + public void testMultAssociativity() { + Matrix3f a = new Matrix3f(1f, 2f, 0f, 3f, 1f, 1f, 0f, 2f, 4f); + Matrix3f b = new Matrix3f(2f, 0f, 1f, 1f, 3f, 0f, 1f, 1f, 2f); + Matrix3f c = new Matrix3f(0f, 1f, 2f, 3f, 0f, 1f, 1f, 2f, 0f); + + Matrix3f abc1 = a.mult(b).mult(c); + Matrix3f abc2 = a.mult(b.mult(c)); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + Assert.assertEquals("element (" + i + "," + j + ")", + abc1.get(i, j), abc2.get(i, j), TOLERANCE); + } + } + } + + /** + * Matrix multiplication must NOT be commutative in general: A*B != B*A. + */ + @Test + public void testMultNonCommutativity() { + Matrix3f a = new Matrix3f(1f, 2f, 0f, 0f, 1f, 0f, 0f, 0f, 1f); + Matrix3f b = new Matrix3f(1f, 0f, 0f, 3f, 1f, 0f, 0f, 0f, 1f); + + Matrix3f ab = a.mult(b); + Matrix3f ba = b.mult(a); + + boolean allEqual = true; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (Math.abs(ab.get(i, j) - ba.get(i, j)) > TOLERANCE) { + allEqual = false; + break; + } + } + } + Assert.assertFalse("A*B should not equal B*A for these matrices", allEqual); + } + + /** + * Rotating (0,1,0) by 90° around the X axis should give (0,0,1). + */ + @Test + public void testFromAngleAxisRotation90DegreesAroundX() { + Matrix3f m = new Matrix3f(); + m.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X); + Vector3f result = m.mult(new Vector3f(0f, 1f, 0f)); + Assert.assertEquals(0f, result.x, TOLERANCE); + Assert.assertEquals(0f, result.y, TOLERANCE); + Assert.assertEquals(1f, result.z, TOLERANCE); + } + + /** + * Rotating (1,0,0) by 180° around the Y axis should give (-1,0,0). + */ + @Test + public void testFromAngleAxisRotation180DegreesAroundY() { + Matrix3f m = new Matrix3f(); + m.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y); + Vector3f result = m.mult(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(-1f, result.x, TOLERANCE); + Assert.assertEquals(0f, result.y, TOLERANCE); + Assert.assertEquals(0f, result.z, TOLERANCE); + } + + /** + * The identity M * adj(M) = det(M) * I must hold for any matrix. + * Tests with the diagonal matrix diag(2,3,4) whose det = 24. + */ + @Test + public void testAdjointTimesOriginalIsDeterminantTimesIdentity() { + Matrix3f m = new Matrix3f( + 2f, 0f, 0f, + 0f, 3f, 0f, + 0f, 0f, 4f); + float det = m.determinant(); // expected: 24 + Matrix3f adj = m.adjoint(); + Matrix3f product = m.mult(adj); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + float expected = (i == j) ? det : 0f; + Assert.assertEquals("product[" + i + "][" + j + "]", + expected, product.get(i, j), TOLERANCE); + } + } + } + + /** + * The inverse of diag(2,3,4) must be diag(0.5, 1/3, 0.25). + */ + @Test + public void testScaleInverseIsReciprocal() { + Matrix3f m = new Matrix3f( + 2f, 0f, 0f, + 0f, 3f, 0f, + 0f, 0f, 4f); + Matrix3f inv = m.invert(); + Assert.assertEquals(0.5f, inv.get(0, 0), TOLERANCE); + Assert.assertEquals(1f / 3f, inv.get(1, 1), TOLERANCE); + Assert.assertEquals(0.25f, inv.get(2, 2), TOLERANCE); + Assert.assertEquals(0f, inv.get(0, 1), TOLERANCE); + Assert.assertEquals(0f, inv.get(1, 2), TOLERANCE); + Assert.assertEquals(0f, inv.get(2, 0), TOLERANCE); + } + + /** + * Serialise a matrix to a float[] (row-major) then reconstruct it with + * set(float[]) — the two matrices must be equal. + */ + @Test + public void testFloatArrayRoundTrip() { + Matrix3f original = new Matrix3f( + 1f, 2f, 3f, + 4f, 5f, 6f, + 7f, 8f, 9f); + float[] data = new float[9]; + original.get(data, true); // row-major + + Matrix3f reconstructed = new Matrix3f(); + reconstructed.set(data, true); // row-major + Assert.assertEquals(original, reconstructed); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java b/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java index e7a4dfa097..c253a02259 100644 --- a/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java +++ b/jme3-core/src/test/java/com/jme3/math/Matrix4fTest.java @@ -447,4 +447,174 @@ public void testStaticConstants() { } } } + + // ----------------------------------------------------------------------- + // Complex behavioral / regression tests + // ----------------------------------------------------------------------- + + /** + * REGRESSION: diagonal matrix with det = FLT_EPSILON/2 (below threshold) + * must cause invert() to throw ArithmeticException, not silently produce + * garbage by dividing by a near-zero determinant. + */ + @Test(expected = ArithmeticException.class) + public void testInvertNearSingularThrows() { + float smallVal = FastMath.FLT_EPSILON / 2f; + Matrix4f m = new Matrix4f( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, smallVal); + m.invert(); // must throw + } + + /** + * REGRESSION: same near-singular matrix, but invertLocal() must return + * an all-zero matrix rather than garbage. + */ + @Test + public void testInvertLocalNearSingularReturnsZero() { + float smallVal = FastMath.FLT_EPSILON / 2f; + Matrix4f m = new Matrix4f( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, smallVal); + Matrix4f result = m.invertLocal(); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Assert.assertEquals("element (" + i + "," + j + ") must be 0", + 0f, result.get(i, j), 0f); + } + } + } + + /** + * For a non-singular matrix, invertLocal() must produce the same inverse + * as invert() called on a fresh copy. + */ + @Test + public void testInvertLocal() { + Matrix4f m = new Matrix4f( + 1f, 0f, 0f, 5f, + 0f, 1f, 0f, 3f, + 0f, 0f, 1f, 2f, + 0f, 0f, 0f, 1f); + Matrix4f expected = m.invert(); // non-destructive + Matrix4f copy = new Matrix4f(m); + copy.invertLocal(); // in-place + assertMatricesEqual(expected, copy, TOLERANCE); + } + + /** + * Two translation matrices composed must equal translation by the sum of + * their translation vectors. + */ + @Test + public void testTranslationComposition() { + // T1: translate by (1, 2, 3) + Matrix4f t1 = new Matrix4f( + 1f, 0f, 0f, 1f, + 0f, 1f, 0f, 2f, + 0f, 0f, 1f, 3f, + 0f, 0f, 0f, 1f); + // T2: translate by (4, 5, 6) + Matrix4f t2 = new Matrix4f( + 1f, 0f, 0f, 4f, + 0f, 1f, 0f, 5f, + 0f, 0f, 1f, 6f, + 0f, 0f, 0f, 1f); + // Expected: translate by (5, 7, 9) + Matrix4f expected = new Matrix4f( + 1f, 0f, 0f, 5f, + 0f, 1f, 0f, 7f, + 0f, 0f, 1f, 9f, + 0f, 0f, 0f, 1f); + Matrix4f result = t1.mult(t2); + assertMatricesEqual(expected, result, TOLERANCE); + } + + /** + * Translation matrix (5,3,2) applied to the point (1,0,0) must give (6,3,2). + */ + @Test + public void testMultVector3fWithTranslation() { + Matrix4f t = new Matrix4f( + 1f, 0f, 0f, 5f, + 0f, 1f, 0f, 3f, + 0f, 0f, 1f, 2f, + 0f, 0f, 0f, 1f); + Vector3f result = t.mult(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(6f, result.x, TOLERANCE); + Assert.assertEquals(3f, result.y, TOLERANCE); + Assert.assertEquals(2f, result.z, TOLERANCE); + } + + /** + * Matrix times a homogeneous point (x,y,z,1) must properly apply + * translation; the w component of the output must be 1. + */ + @Test + public void testMultVector4f_HomogeneousPoint() { + Matrix4f t = new Matrix4f( + 1f, 0f, 0f, 5f, + 0f, 1f, 0f, 3f, + 0f, 0f, 1f, 2f, + 0f, 0f, 0f, 1f); + Vector4f result = t.mult(new Vector4f(1f, 0f, 0f, 1f)); + Assert.assertEquals(6f, result.x, TOLERANCE); + Assert.assertEquals(3f, result.y, TOLERANCE); + Assert.assertEquals(2f, result.z, TOLERANCE); + Assert.assertEquals(1f, result.w, TOLERANCE); + } + + /** + * Two 90° rotations around Z composed must equal a single 180° rotation + * around Z: (1,0,0) → (-1,0,0). + */ + @Test + public void testRotationComposition() { + Matrix4f r90 = new Matrix4f(); + r90.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + Matrix4f r180 = r90.mult(r90); + Vector3f result = r180.mult(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(-1f, result.x, TOLERANCE); + Assert.assertEquals(0f, result.y, TOLERANCE); + Assert.assertEquals(0f, result.z, TOLERANCE); + } + + /** + * A scale matrix diag(2,3,4) applied to (1,1,1) must give (2,3,4). + */ + @Test + public void testScaleMatrix() { + Matrix4f scale = new Matrix4f( + 2f, 0f, 0f, 0f, + 0f, 3f, 0f, 0f, + 0f, 0f, 4f, 0f, + 0f, 0f, 0f, 1f); + Vector3f result = scale.mult(new Vector3f(1f, 1f, 1f)); + Assert.assertEquals(2f, result.x, TOLERANCE); + Assert.assertEquals(3f, result.y, TOLERANCE); + Assert.assertEquals(4f, result.z, TOLERANCE); + } + + /** + * Applying a transform T to a point p and then applying T^-1 to the + * result must recover the original point p. + */ + @Test + public void testAffineInverseUndoesTransform() { + Matrix4f t = new Matrix4f( + 1f, 0f, 0f, 7f, + 0f, 1f, 0f, -3f, + 0f, 0f, 1f, 5f, + 0f, 0f, 0f, 1f); + Vector3f p = new Vector3f(2f, 4f, -1f); + Vector3f transformed = t.mult(p); + Vector3f recovered = t.invert().mult(transformed); + Assert.assertEquals(p.x, recovered.x, TOLERANCE); + Assert.assertEquals(p.y, recovered.y, TOLERANCE); + Assert.assertEquals(p.z, recovered.z, TOLERANCE); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/PlaneTest.java b/jme3-core/src/test/java/com/jme3/math/PlaneTest.java index 77ec905793..61ad8b8c94 100644 --- a/jme3-core/src/test/java/com/jme3/math/PlaneTest.java +++ b/jme3-core/src/test/java/com/jme3/math/PlaneTest.java @@ -186,4 +186,101 @@ public void testToString() { Assert.assertNotNull(s); Assert.assertTrue(s.contains("Plane")); } + + // ----------------------------------------------------------------------- + // Complex behavioral tests + // ----------------------------------------------------------------------- + + /** + * Reflecting a point P across a plane yields P' such that + * pseudoDistance(P') == −pseudoDistance(P). + */ + @Test + public void testReflectPreservesDistanceFromPlane() { + Plane plane = new Plane(new Vector3f(0f, 1f, 0f), 0f); // y = 0 + Vector3f point = new Vector3f(0f, 3f, 0f); + float originalDist = plane.pseudoDistance(point); + + Vector3f reflected = plane.reflect(point, null); + float reflectedDist = plane.pseudoDistance(reflected); + + Assert.assertEquals(-originalDist, reflectedDist, TOLERANCE); + } + + /** Reflecting a point that lies on the plane must return the same point. */ + @Test + public void testReflectPointOnPlaneIsItself() { + Plane plane = new Plane(new Vector3f(0f, 1f, 0f), 0f); // y = 0 + Vector3f onPlane = new Vector3f(1f, 0f, 2f); + Vector3f reflected = plane.reflect(onPlane, null); + Assert.assertEquals(onPlane.x, reflected.x, TOLERANCE); + Assert.assertEquals(onPlane.y, reflected.y, TOLERANCE); + Assert.assertEquals(onPlane.z, reflected.z, TOLERANCE); + } + + /** + * After setPlanePoints(A, B, C), all three points must satisfy + * isOnPlane (pseudoDistance ≈ 0). + */ + @Test + public void testSetPlanePointsAllPointsAreOnPlane() { + Vector3f a = new Vector3f(1f, 0f, 0f); + Vector3f b = new Vector3f(0f, 1f, 0f); + Vector3f c = new Vector3f(0f, 0f, 1f); + Plane plane = new Plane(); + plane.setPlanePoints(a, b, c); + + Assert.assertTrue("A must be on the plane", plane.isOnPlane(a)); + Assert.assertTrue("B must be on the plane", plane.isOnPlane(b)); + Assert.assertTrue("C must be on the plane", plane.isOnPlane(c)); + } + + /** The result of getClosestPoint must lie on the plane (pseudoDistance ≈ 0). */ + @Test + public void testGetClosestPointIsOnPlane() { + Plane plane = new Plane(new Vector3f(0f, 1f, 0f), 0f); // y = 0 + Vector3f offPlane = new Vector3f(3f, 7f, -2f); + Vector3f closest = plane.getClosestPoint(offPlane); + Assert.assertTrue(plane.isOnPlane(closest)); + } + + /** + * The closest point to P on the plane must be closer to P than any + * other point on the plane. We verify this by checking that + * distance(P, closest) == |pseudoDistance(P)|. + */ + @Test + public void testGetClosestPointMinimizesDistance() { + Plane plane = new Plane(new Vector3f(0f, 1f, 0f), 0f); // y = 0 + Vector3f p = new Vector3f(3f, 5f, -1f); + Vector3f closest = plane.getClosestPoint(p); + float dist = p.distance(closest); + Assert.assertEquals(Math.abs(plane.pseudoDistance(p)), dist, TOLERANCE); + } + + /** + * Plane through the three points (1,0,0), (0,1,0), (0,0,1): + * the normal must be proportional to (1,1,1) and all three + * defining points satisfy pseudoDistance ≈ 0. + */ + @Test + public void testNonAxisAlignedPlane() { + Vector3f a = new Vector3f(1f, 0f, 0f); + Vector3f b = new Vector3f(0f, 1f, 0f); + Vector3f c = new Vector3f(0f, 0f, 1f); + Plane plane = new Plane(); + plane.setPlanePoints(a, b, c); + + Vector3f n = plane.getNormal(); + // Normal must point along (1,1,1)/sqrt(3) or its negation + float expectedComponent = 1f / FastMath.sqrt(3f); + Assert.assertEquals(expectedComponent, Math.abs(n.x), TOLERANCE); + Assert.assertEquals(expectedComponent, Math.abs(n.y), TOLERANCE); + Assert.assertEquals(expectedComponent, Math.abs(n.z), TOLERANCE); + + // All defining points must be on the plane + Assert.assertTrue(plane.isOnPlane(a)); + Assert.assertTrue(plane.isOnPlane(b)); + Assert.assertTrue(plane.isOnPlane(c)); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java b/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java index a81074de70..74f9f5e25d 100644 --- a/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java +++ b/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java @@ -32,6 +32,8 @@ package com.jme3.math; import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; /** * Verifies that the {@link Quaternion} class works correctly. @@ -56,4 +58,225 @@ public void testIsValidQuaternion(){ assertTrue(Quaternion.isValidQuaternion(new Quaternion())); assertTrue(Quaternion.isValidQuaternion(new Quaternion(1.5f, -5.7f, 8.2f, 3.0f))); } + + private static final float TOL = 1e-5f; + + /** Default constructor must produce the identity quaternion (0,0,0,1). */ + @Test + public void testIdentityQuaternion() { + Quaternion q = new Quaternion(); + Assert.assertEquals(0f, q.getX(), TOL); + Assert.assertEquals(0f, q.getY(), TOL); + Assert.assertEquals(0f, q.getZ(), TOL); + Assert.assertEquals(1f, q.getW(), TOL); + Assert.assertEquals(1f, q.norm(), TOL); + Assert.assertTrue(q.isIdentity()); + } + + /** fromAngleAxis(90°, Z) then toAngleAxis must round-trip to ~90° around ~(0,0,1). */ + @Test + public void testFromAngleAxisAndBack() { + Quaternion q = new Quaternion(); + q.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + Vector3f axis = new Vector3f(); + float angle = q.toAngleAxis(axis); + Assert.assertEquals(FastMath.HALF_PI, angle, TOL); + Assert.assertEquals(0f, axis.x, TOL); + Assert.assertEquals(0f, axis.y, TOL); + Assert.assertEquals(1f, axis.z, TOL); + } + + /** A normalised quaternion must have norm == 1. */ + @Test + public void testNorm() { + // For a unit quaternion, norm() (which returns the sum of squared components) == 1 + Quaternion unit = new Quaternion(); + Assert.assertEquals(1f, unit.norm(), TOL); + + // For (1,2,3,4), norm() = 1²+2²+3²+4² = 30 (squared norm) + Quaternion q = new Quaternion(1f, 2f, 3f, 4f); + Assert.assertEquals(30f, q.norm(), TOL); + } + + /** After normalizeLocal() the norm must be 1. */ + @Test + public void testNormalizeLocal() { + Quaternion q = new Quaternion(1f, 2f, 3f, 4f); + q.normalizeLocal(); + Assert.assertEquals(1f, q.norm(), TOL); + } + + /** q * identity must equal q for any quaternion. */ + @Test + public void testMultiplyIdentity() { + Quaternion q = new Quaternion(); + q.fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y); + Quaternion identity = new Quaternion(); + Quaternion result = q.mult(identity); + Assert.assertEquals(q.getX(), result.getX(), TOL); + Assert.assertEquals(q.getY(), result.getY(), TOL); + Assert.assertEquals(q.getZ(), result.getZ(), TOL); + Assert.assertEquals(q.getW(), result.getW(), TOL); + } + + /** q * q^-1 must be the identity quaternion (for a unit quaternion). */ + @Test + public void testMultiplyInverse() { + Quaternion q = new Quaternion(); + q.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X); + Quaternion inv = q.inverse(); + Quaternion product = q.mult(inv); + Assert.assertEquals(0f, product.getX(), TOL); + Assert.assertEquals(0f, product.getY(), TOL); + Assert.assertEquals(0f, product.getZ(), TOL); + Assert.assertEquals(1f, product.getW(), TOL); + } + + /** + * Anti-homomorphism of the inverse: (q*p)^-1 == p^-1 * q^-1. + * For unit quaternions the inverse equals the conjugate. + */ + @Test + public void testConjugate() { + Quaternion q = new Quaternion(); + q.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + Quaternion p = new Quaternion(); + p.fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_X); + + Quaternion qpInv = q.mult(p).inverse(); + Quaternion pInvQInv = p.inverse().mult(q.inverse()); + + Assert.assertEquals(pInvQInv.getX(), qpInv.getX(), TOL); + Assert.assertEquals(pInvQInv.getY(), qpInv.getY(), TOL); + Assert.assertEquals(pInvQInv.getZ(), qpInv.getZ(), TOL); + Assert.assertEquals(pInvQInv.getW(), qpInv.getW(), TOL); + } + + /** + * fromAngles(pitch, yaw, roll) then toAngles() must reproduce the same + * rotation when applied to a vector. + */ + @Test + public void testFromAnglesRoundTrip() { + float pitch = 0.3f, yaw = 0.5f, roll = 0.7f; + Quaternion q1 = new Quaternion().fromAngles(pitch, yaw, roll); + float[] angles = q1.toAngles(null); + Quaternion q2 = new Quaternion().fromAngles(angles); + + Vector3f v = new Vector3f(1f, 2f, 3f); + Vector3f r1 = q1.mult(v); + Vector3f r2 = q2.mult(v); + Assert.assertEquals(r1.x, r2.x, 1e-4f); + Assert.assertEquals(r1.y, r2.y, 1e-4f); + Assert.assertEquals(r1.z, r2.z, 1e-4f); + } + + /** slerp(q1, q2, 0) must equal q1. */ + @Test + public void testSlerpAtZero() { + Quaternion q1 = new Quaternion(); + q1.fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Z); + Quaternion q2 = new Quaternion(); + q2.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + + Quaternion result = new Quaternion().slerp(q1, q2, 0f); + Assert.assertEquals(q1.getX(), result.getX(), TOL); + Assert.assertEquals(q1.getY(), result.getY(), TOL); + Assert.assertEquals(q1.getZ(), result.getZ(), TOL); + Assert.assertEquals(q1.getW(), result.getW(), TOL); + } + + /** slerp(q1, q2, 1) must equal q2. */ + @Test + public void testSlerpAtOne() { + Quaternion q1 = new Quaternion(); + q1.fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Z); + Quaternion q2 = new Quaternion(); + q2.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + + Quaternion result = new Quaternion().slerp(q1, q2, 1f); + Assert.assertEquals(q2.getX(), result.getX(), TOL); + Assert.assertEquals(q2.getY(), result.getY(), TOL); + Assert.assertEquals(q2.getZ(), result.getZ(), TOL); + Assert.assertEquals(q2.getW(), result.getW(), TOL); + } + + /** + * slerp(identity, 90°-around-Z, 0.5) must produce a 45° rotation + * around Z. Verified by rotating (1,0,0) and checking (cos45°, sin45°, 0). + */ + @Test + public void testSlerpAtHalf() { + Quaternion q1 = new Quaternion(); // identity + Quaternion q2 = new Quaternion(); + q2.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); // 90° around Z + + Quaternion half = new Quaternion().slerp(q1, q2, 0.5f); + Vector3f rotated = half.mult(new Vector3f(1f, 0f, 0f)); + Assert.assertEquals(FastMath.cos(FastMath.QUARTER_PI), rotated.x, TOL); + Assert.assertEquals(FastMath.sin(FastMath.QUARTER_PI), rotated.y, TOL); + Assert.assertEquals(0f, rotated.z, TOL); + } + + /** + * Converting a rotation matrix built from angle-axis to a Quaternion must + * yield the same vector transform as the original matrix. + */ + @Test + public void testFromRotationMatrixRoundTrip() { + Matrix3f mat = new Matrix3f(); + mat.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + Quaternion q = new Quaternion(); + q.fromRotationMatrix(mat); + Vector3f v = new Vector3f(1f, 0f, 0f); + Vector3f fromMatrix = mat.mult(v); + Vector3f fromQuat = q.mult(v); + Assert.assertEquals(fromMatrix.x, fromQuat.x, TOL); + Assert.assertEquals(fromMatrix.y, fromQuat.y, TOL); + Assert.assertEquals(fromMatrix.z, fromQuat.z, TOL); + } + + /** + * Rotating (1,0,0) by 90° around Y using multLocal(Vector3f) must give + * approximately (0,0,-1). + */ + @Test + public void testRotateVector() { + Quaternion q = new Quaternion(); + q.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Y); + Vector3f v = new Vector3f(1f, 0f, 0f); + q.multLocal(v); + Assert.assertEquals(0f, v.x, TOL); + Assert.assertEquals(0f, v.y, TOL); + Assert.assertEquals(-1f, v.z, TOL); + } + + /** Quaternion multiplication must be associative: (q1*q2)*q3 == q1*(q2*q3). */ + @Test + public void testMultiplicationAssociativity() { + Quaternion q1 = new Quaternion(); + q1.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X); + Quaternion q2 = new Quaternion(); + q2.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Y); + Quaternion q3 = new Quaternion(); + q3.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z); + + Quaternion lhs = q1.mult(q2).mult(q3); + Quaternion rhs = q1.mult(q2.mult(q3)); + Assert.assertEquals(lhs.getX(), rhs.getX(), TOL); + Assert.assertEquals(lhs.getY(), rhs.getY(), TOL); + Assert.assertEquals(lhs.getZ(), rhs.getZ(), TOL); + Assert.assertEquals(lhs.getW(), rhs.getW(), TOL); + } + + /** q.add(q).subtract(q) must equal q (component-wise). */ + @Test + public void testAddSubtract() { + Quaternion q = new Quaternion(0.1f, 0.2f, 0.3f, 0.9f); + Quaternion result = q.add(q).subtract(q); + Assert.assertEquals(q.getX(), result.getX(), TOL); + Assert.assertEquals(q.getY(), result.getY(), TOL); + Assert.assertEquals(q.getZ(), result.getZ(), TOL); + Assert.assertEquals(q.getW(), result.getW(), TOL); + } } \ No newline at end of file diff --git a/jme3-core/src/test/java/com/jme3/math/RayTest.java b/jme3-core/src/test/java/com/jme3/math/RayTest.java index 913fe465f1..d521096dc7 100644 --- a/jme3-core/src/test/java/com/jme3/math/RayTest.java +++ b/jme3-core/src/test/java/com/jme3/math/RayTest.java @@ -153,4 +153,88 @@ public void testToString() { Assert.assertNotNull(s); Assert.assertTrue(s.contains("Ray")); } + + // ----------------------------------------------------------------------- + // Complex behavioral tests + // ----------------------------------------------------------------------- + + /** + * A ray that travels parallel to the plane of a triangle (ray direction + * lies in the triangle's plane) must not report an intersection. + */ + @Test + public void testIntersectWhere_parallelToTriangle() { + // Triangle in the XY plane (z=0) + Vector3f v0 = new Vector3f(-1f, -1f, 0f); + Vector3f v1 = new Vector3f(1f, -1f, 0f); + Vector3f v2 = new Vector3f(0f, 1f, 0f); + // Ray also travelling in the XY plane: origin inside the triangle's + // bounding box but direction along X (no Z component) + Ray r = new Ray(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + Vector3f loc = new Vector3f(); + boolean hit = r.intersectWhere(v0, v1, v2, loc); + Assert.assertFalse("Parallel ray must not intersect the triangle", hit); + } + + /** + * A triangle that lies entirely behind the ray origin (in the direction + * opposite to the ray) must not register a hit. + */ + @Test + public void testIntersectWhere_behindOrigin() { + // Ray pointing in +Z, origin at z=10 + Ray r = new Ray(new Vector3f(0f, 0f, 10f), new Vector3f(0f, 0f, 1f)); + // Triangle centred at z=0 — behind the origin along the ray direction + Vector3f v0 = new Vector3f(-1f, -1f, 0f); + Vector3f v1 = new Vector3f(1f, -1f, 0f); + Vector3f v2 = new Vector3f(0f, 1f, 0f); + Vector3f loc = new Vector3f(); + boolean hit = r.intersectWhere(v0, v1, v2, loc); + Assert.assertFalse("Triangle behind the origin must not be hit", hit); + } + + /** + * A point that lies exactly on the ray must have distanceSquared == 0. + */ + @Test + public void testDistanceSquared_pointOnRay() { + Ray r = new Ray(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + // Any point along the X axis is on the ray + Vector3f onRay = new Vector3f(7f, 0f, 0f); + float d2 = r.distanceSquared(onRay); + Assert.assertEquals(0f, d2, TOLERANCE); + } + + /** + * A point perpendicular to a ray at a known offset must have the correct + * squared distance. + */ + @Test + public void testDistanceSquared_closestIsMiddle() { + // Ray from origin along +X + Ray r = new Ray(new Vector3f(0f, 0f, 0f), new Vector3f(1f, 0f, 0f)); + // Point at (5, 3, 0) — the closest ray point is (5,0,0), distance = 3 + Vector3f p = new Vector3f(5f, 3f, 0f); + float d2 = r.distanceSquared(p); + Assert.assertEquals(9f, d2, TOLERANCE); + } + + /** + * A ray aimed directly at a triangle vertex must report an intersection + * at that vertex. + */ + @Test + public void testIntersectWhere_edgeCase_vertex() { + Vector3f v0 = new Vector3f(0f, 0f, 0f); + Vector3f v1 = new Vector3f(1f, 0f, 0f); + Vector3f v2 = new Vector3f(0f, 1f, 0f); + // Ray aimed straight at v0 from below the XY plane along +Z + Ray r = new Ray(new Vector3f(0f, 0f, -5f), new Vector3f(0f, 0f, 1f)); + Vector3f loc = new Vector3f(); + boolean hit = r.intersectWhere(v0, v1, v2, loc); + Assert.assertTrue("Ray aimed at vertex v0 must hit", hit); + Assert.assertEquals(0f, loc.x, TOLERANCE); + Assert.assertEquals(0f, loc.y, TOLERANCE); + Assert.assertEquals(0f, loc.z, TOLERANCE); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java index a2a56f59ab..ffd02b0040 100644 --- a/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java +++ b/jme3-core/src/test/java/com/jme3/math/Vector2fTest.java @@ -531,4 +531,120 @@ public void testStaticConstants() { Assert.assertTrue(Float.isNaN(Vector2f.NAN.x)); Assert.assertTrue(Float.isNaN(Vector2f.NAN.y)); } + + // ----------------------------------------------------------------------- + // Complex behavioral tests + // ----------------------------------------------------------------------- + + /** Perpendicular vectors have dot == 0; same-direction unit vectors have dot == 1. */ + @Test + public void testDotProductGeometry() { + Vector2f a = new Vector2f(1f, 0f); + Vector2f b = new Vector2f(0f, 1f); + Assert.assertEquals(0f, a.dot(b), TOLERANCE); // perpendicular + Assert.assertEquals(1f, a.dot(new Vector2f(1f, 0f)), TOLERANCE); // parallel unit + } + + /** (1,0) × (0,1) z-component = +1; (0,1) × (1,0) z-component = -1. */ + @Test + public void testCrossProductZComponent() { + Vector2f x = new Vector2f(1f, 0f); + Vector2f y = new Vector2f(0f, 1f); + Assert.assertEquals(1f, x.determinant(y), TOLERANCE); + Assert.assertEquals(-1f, y.determinant(x), TOLERANCE); + } + + /** After normalize() the length must be exactly 1. */ + @Test + public void testNormalizeProducesUnitVector() { + Vector2f v = new Vector2f(3f, 4f); + Vector2f n = v.normalize(); + Assert.assertEquals(1f, n.length(), TOLERANCE); + } + + /** + * normalize(v) must be parallel to v: the cross-product z-component + * (determinant) of the normalised vector with the original is 0. + */ + @Test + public void testNormalizePreservesDirection() { + Vector2f v = new Vector2f(3f, 4f); + Vector2f n = v.normalize(); + Assert.assertEquals(0f, n.determinant(v), TOLERANCE); + // Same quadrant: both components have the same sign + Assert.assertTrue(n.x > 0f); + Assert.assertTrue(n.y > 0f); + } + + /** distance(a,b) must equal (b−a).length(). */ + @Test + public void testLengthAndDistance() { + Vector2f a = new Vector2f(1f, 2f); + Vector2f b = new Vector2f(4f, 6f); + float dist = a.distance(b); + float diffLen = b.subtract(a).length(); + Assert.assertEquals(diffLen, dist, TOLERANCE); + } + + /** The smallest angle between axis-aligned unit vectors. */ + @Test + public void testAngleBetween() { + Vector2f xAxis = new Vector2f(1f, 0f); + Vector2f yAxis = new Vector2f(0f, 1f); + Vector2f negX = new Vector2f(-1f, 0f); + Assert.assertEquals(FastMath.HALF_PI, xAxis.smallestAngleBetween(yAxis), TOLERANCE); + Assert.assertEquals(FastMath.PI, xAxis.smallestAngleBetween(negX), TOLERANCE); + } + + /** Interpolating (0,0)→(2,2) at t=0.5 must give (1,1). */ + @Test + public void testInterpolateLocal_HalfwayPoint() { + Vector2f start = new Vector2f(0f, 0f); + Vector2f end = new Vector2f(2f, 2f); + Vector2f v = new Vector2f(); + v.interpolateLocal(start, end, 0.5f); + Assert.assertEquals(1f, v.x, TOLERANCE); + Assert.assertEquals(1f, v.y, TOLERANCE); + } + + /** interpolateLocal at t=0 must return the start vector. */ + @Test + public void testInterpolateLocal_AtZero() { + Vector2f start = new Vector2f(3f, 7f); + Vector2f end = new Vector2f(9f, -1f); + Vector2f v = new Vector2f(); + v.interpolateLocal(start, end, 0f); + Assert.assertEquals(start.x, v.x, TOLERANCE); + Assert.assertEquals(start.y, v.y, TOLERANCE); + } + + /** interpolateLocal at t=1 must return the end vector. */ + @Test + public void testInterpolateLocal_AtOne() { + Vector2f start = new Vector2f(3f, 7f); + Vector2f end = new Vector2f(9f, -1f); + Vector2f v = new Vector2f(); + v.interpolateLocal(start, end, 1f); + Assert.assertEquals(end.x, v.x, TOLERANCE); + Assert.assertEquals(end.y, v.y, TOLERANCE); + } + + /** v + w − w must equal v. */ + @Test + public void testAddSubtractRoundTrip() { + Vector2f v = new Vector2f(5f, -3f); + Vector2f w = new Vector2f(2f, 7f); + Vector2f result = v.add(w).subtract(w); + Assert.assertEquals(v.x, result.x, TOLERANCE); + Assert.assertEquals(v.y, result.y, TOLERANCE); + } + + /** (v * 3) / 3 must equal v. */ + @Test + public void testMultAndDivideAreInverse() { + Vector2f v = new Vector2f(4f, 9f); + Vector2f result = v.mult(3f).divide(3f); + Assert.assertEquals(v.x, result.x, TOLERANCE); + Assert.assertEquals(v.y, result.y, TOLERANCE); + } } diff --git a/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java index 0febc1b5be..e22d671560 100644 --- a/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java +++ b/jme3-core/src/test/java/com/jme3/math/Vector4fTest.java @@ -529,4 +529,124 @@ public void testStaticConstants() { Assert.assertEquals(0f, Vector4f.UNIT_W.x, 0f); Assert.assertTrue(Float.isNaN(Vector4f.NAN.x)); } + + // ----------------------------------------------------------------------- + // Complex behavioral tests + // ----------------------------------------------------------------------- + + /** Orthogonal unit vectors have dot == 0; a unit vector with itself has dot == 1. */ + @Test + public void testDotProductGeometry() { + Vector4f x = new Vector4f(1f, 0f, 0f, 0f); + Vector4f y = new Vector4f(0f, 1f, 0f, 0f); + Assert.assertEquals(0f, x.dot(y), TOLERANCE); + Assert.assertEquals(1f, x.dot(x), TOLERANCE); + } + + /** After normalize() the length must be 1. */ + @Test + public void testNormalizeProducesUnitVector() { + Vector4f v = new Vector4f(1f, 2f, 3f, 4f); + Vector4f n = v.normalize(); + Assert.assertEquals(1f, n.length(), TOLERANCE); + // Original unaffected + Assert.assertEquals(1f, v.x, 0f); + } + + /** + * Projecting a projection of v onto u, back onto u again, must + * reproduce the same vector (idempotency). + */ + @Test + public void testProjectionIsIdempotent() { + Vector4f v = new Vector4f(2f, 3f, 0f, 0f); + Vector4f u = new Vector4f(1f, 0f, 0f, 0f); + Vector4f proj1 = v.project(u); + Vector4f proj2 = proj1.project(u); + Assert.assertEquals(proj1.x, proj2.x, TOLERANCE); + Assert.assertEquals(proj1.y, proj2.y, TOLERANCE); + Assert.assertEquals(proj1.z, proj2.z, TOLERANCE); + Assert.assertEquals(proj1.w, proj2.w, TOLERANCE); + } + + /** Projecting a vector onto itself must give back the same vector. */ + @Test + public void testProjectionOntoSelf() { + Vector4f v = new Vector4f(1f, 0f, 0f, 0f); // use unit vector to avoid scaling + Vector4f proj = v.project(v); + Assert.assertEquals(v.x, proj.x, TOLERANCE); + Assert.assertEquals(v.y, proj.y, TOLERANCE); + Assert.assertEquals(v.z, proj.z, TOLERANCE); + Assert.assertEquals(v.w, proj.w, TOLERANCE); + } + + /** scaleAdd(scalar, add): (2,3,4,5)*3 + (1,0,0,0) == (7,9,12,15). */ + @Test + public void testScaleAddTwoArgForm() { + Vector4f a = new Vector4f(2f, 3f, 4f, 5f); + Vector4f add = new Vector4f(1f, 0f, 0f, 0f); + a.scaleAdd(3f, add); + Assert.assertEquals(7f, a.x, TOLERANCE); + Assert.assertEquals(9f, a.y, TOLERANCE); + Assert.assertEquals(12f, a.z, TOLERANCE); + Assert.assertEquals(15f, a.w, TOLERANCE); + } + + /** scaleAdd(scalar, mult, add): scalar*(2,3,4,5) + (1,0,0,0) == (7,9,12,15). */ + @Test + public void testScaleAddThreeArgForm() { + Vector4f result = new Vector4f(); + Vector4f mult = new Vector4f(2f, 3f, 4f, 5f); + Vector4f add = new Vector4f(1f, 0f, 0f, 0f); + result.scaleAdd(3f, mult, add); + Assert.assertEquals(7f, result.x, TOLERANCE); + Assert.assertEquals(9f, result.y, TOLERANCE); + Assert.assertEquals(12f, result.z, TOLERANCE); + Assert.assertEquals(15f, result.w, TOLERANCE); + } + + /** minLocal and maxLocal must compute component-wise extremes. */ + @Test + public void testMinMaxLocal() { + Vector4f a = new Vector4f(5f, 2f, 8f, 1f); + Vector4f b = new Vector4f(3f, 6f, 4f, 7f); + + Vector4f minA = new Vector4f(a); + minA.minLocal(b); + Assert.assertEquals(3f, minA.x, TOLERANCE); + Assert.assertEquals(2f, minA.y, TOLERANCE); + Assert.assertEquals(4f, minA.z, TOLERANCE); + Assert.assertEquals(1f, minA.w, TOLERANCE); + + Vector4f maxA = new Vector4f(a); + maxA.maxLocal(b); + Assert.assertEquals(5f, maxA.x, TOLERANCE); + Assert.assertEquals(6f, maxA.y, TOLERANCE); + Assert.assertEquals(8f, maxA.z, TOLERANCE); + Assert.assertEquals(7f, maxA.w, TOLERANCE); + } + + /** v + w − w must equal v. */ + @Test + public void testAddSubtractRoundTrip() { + Vector4f v = new Vector4f(1f, -2f, 3f, -4f); + Vector4f w = new Vector4f(5f, 6f, 7f, 8f); + Vector4f result = v.add(w).subtract(w); + Assert.assertEquals(v.x, result.x, TOLERANCE); + Assert.assertEquals(v.y, result.y, TOLERANCE); + Assert.assertEquals(v.z, result.z, TOLERANCE); + Assert.assertEquals(v.w, result.w, TOLERANCE); + } + + /** negateLocal twice must return the original vector. */ + @Test + public void testNegateLocalTwiceIsOriginal() { + Vector4f v = new Vector4f(1f, -2f, 3f, -4f); + Vector4f copy = new Vector4f(v); + v.negateLocal().negateLocal(); + Assert.assertEquals(copy.x, v.x, TOLERANCE); + Assert.assertEquals(copy.y, v.y, TOLERANCE); + Assert.assertEquals(copy.z, v.z, TOLERANCE); + Assert.assertEquals(copy.w, v.w, TOLERANCE); + } } From 6e05808990e1272a145a68d57673543bef80b13e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:58:12 +0000 Subject: [PATCH 5/6] Add 79 complex behavioral tests, QuaternionTest, and fix near-singular matrix inversion bug Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com> --- jme3-core/src/main/java/com/jme3/math/Matrix3f.java | 2 +- jme3-core/src/main/java/com/jme3/math/Matrix4f.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix3f.java b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java index 5b4d6d2634..889fc9d69a 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java @@ -1062,7 +1062,7 @@ public Matrix3f invert(Matrix3f store) { */ public Matrix3f invertLocal() { float det = determinant(); - if (FastMath.abs(det) <= 0f) { + if (FastMath.abs(det) <= FastMath.FLT_EPSILON) { return zero(); } diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java index 4f9a6e2f35..3144adcfb8 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -1512,7 +1512,7 @@ public Matrix4f invert(Matrix4f store) { float fB5 = m22 * m33 - m23 * m32; float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; - if (FastMath.abs(fDet) <= 0f) { + if (FastMath.abs(fDet) <= FastMath.FLT_EPSILON) { throw new ArithmeticException("This matrix cannot be inverted"); } @@ -1561,7 +1561,7 @@ public Matrix4f invertLocal() { float fB5 = m22 * m33 - m23 * m32; float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; - if (FastMath.abs(fDet) <= 0f) { + if (FastMath.abs(fDet) <= FastMath.FLT_EPSILON) { return zero(); } From 6a4db88a16f4710850402b2cb2756d97bfa2f906 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:37:30 +0000 Subject: [PATCH 6/6] Add complex scenegraph behavior tests for hierarchy transforms and SceneGraphIterator Co-authored-by: riccardobl <4943530+riccardobl@users.noreply.github.com> --- .../jme3/scene/NodeHierarchyBehaviorTest.java | 150 ++++++++++++++++++ .../jme3/scene/SceneGraphIteratorTest.java | 134 ++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/scene/NodeHierarchyBehaviorTest.java create mode 100644 jme3-core/src/test/java/com/jme3/scene/SceneGraphIteratorTest.java diff --git a/jme3-core/src/test/java/com/jme3/scene/NodeHierarchyBehaviorTest.java b/jme3-core/src/test/java/com/jme3/scene/NodeHierarchyBehaviorTest.java new file mode 100644 index 0000000000..2cb968c98f --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/NodeHierarchyBehaviorTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Behavioral tests for scenegraph hierarchy and transform propagation. + */ +public class NodeHierarchyBehaviorTest { + + private static final float TOL = 1e-5f; + + @Test + public void testAttachChildAutomaticallyReparents() { + Node parentA = new Node("parentA"); + Node parentB = new Node("parentB"); + Node child = new Node("child"); + + parentA.attachChild(child); + Assert.assertSame(parentA, child.getParent()); + + parentB.attachChild(child); + + Assert.assertSame(parentB, child.getParent()); + Assert.assertEquals(-1, parentA.getChildIndex(child)); + Assert.assertTrue(parentB.getChildIndex(child) >= 0); + } + + @Test + public void testWorldTransformPropagationThroughHierarchy() { + Node root = new Node("root"); + Node child = new Node("child"); + + root.setLocalTranslation(10f, 0f, 0f); + root.setLocalScale(2f); + root.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Z)); + + child.setLocalTranslation(1f, 0f, 0f); + root.attachChild(child); + + root.updateLogicalState(0f); + root.updateGeometricState(); + + Vector3f world = child.getWorldTranslation(); + // child local (1,0,0) rotated by 90deg around Z and scaled by 2 => (0,2,0), then translated by root => (10,2,0) + Assert.assertEquals(10f, world.x, TOL); + Assert.assertEquals(2f, world.y, TOL); + Assert.assertEquals(0f, world.z, TOL); + } + + @Test + public void testLocalToWorldAndWorldToLocalRoundTrip() { + Node root = new Node("root"); + root.setLocalTranslation(3f, -2f, 1f); + root.setLocalScale(new Vector3f(2f, 3f, 4f)); + root.setLocalRotation(new Quaternion().fromAngleAxis(0.3f, Vector3f.UNIT_Y)); + + Node child = new Node("child"); + child.setLocalTranslation(1f, 2f, 3f); + root.attachChild(child); + + root.updateLogicalState(0f); + root.updateGeometricState(); + + Vector3f localPoint = new Vector3f(0.25f, -0.5f, 1.75f); + Vector3f worldPoint = child.localToWorld(localPoint, null); + Vector3f roundTrip = child.worldToLocal(worldPoint, null); + + Assert.assertEquals(localPoint.x, roundTrip.x, TOL); + Assert.assertEquals(localPoint.y, roundTrip.y, TOL); + Assert.assertEquals(localPoint.z, roundTrip.z, TOL); + } + + @Test + public void testDetachChildPreservesLocalTransform() { + Node parent = new Node("parent"); + Node child = new Node("child"); + + parent.setLocalTranslation(5f, 0f, 0f); + parent.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_Y)); + child.setLocalTranslation(1f, 0f, 0f); + + parent.attachChild(child); + parent.updateLogicalState(0f); + parent.updateGeometricState(); + + Vector3f localBeforeDetach = child.getLocalTranslation().clone(); + parent.detachChild(child); + + // Detaching must keep local transform unchanged and clear parent. + Assert.assertNull(child.getParent()); + Assert.assertEquals(localBeforeDetach.x, child.getLocalTranslation().x, TOL); + Assert.assertEquals(localBeforeDetach.y, child.getLocalTranslation().y, TOL); + Assert.assertEquals(localBeforeDetach.z, child.getLocalTranslation().z, TOL); + } + + @Test + public void testSwapChildrenAffectsTraversalOrder() { + Node root = new Node("root"); + Node a = new Node("a"); + Node b = new Node("b"); + + root.attachChild(a); + root.attachChild(b); + + SceneGraphIterator itBefore = new SceneGraphIterator(root); + Assert.assertEquals("root", itBefore.next().getName()); + Assert.assertEquals("a", itBefore.next().getName()); + + root.swapChildren(0, 1); + + SceneGraphIterator itAfter = new SceneGraphIterator(root); + Assert.assertEquals("root", itAfter.next().getName()); + Assert.assertEquals("b", itAfter.next().getName()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/SceneGraphIteratorTest.java b/jme3-core/src/test/java/com/jme3/scene/SceneGraphIteratorTest.java new file mode 100644 index 0000000000..e6a58787a1 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/SceneGraphIteratorTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +/** + * Complex behavioral tests for {@link SceneGraphIterator}. + */ +public class SceneGraphIteratorTest { + + private static Node buildTree() { + Node root = new Node("root"); + Node a = new Node("a"); + Node a1 = new Node("a1"); + Geometry a1g = new Geometry("a1g", new Mesh()); + Node b = new Node("b"); + Geometry bg = new Geometry("bg", new Mesh()); + + root.attachChild(a); + root.attachChild(b); + a.attachChild(a1); + a1.attachChild(a1g); + b.attachChild(bg); + + return root; + } + + @Test + public void testDepthFirstIterationOrder() { + Node root = buildTree(); + SceneGraphIterator it = new SceneGraphIterator(root); + + List names = new ArrayList<>(); + while (it.hasNext()) { + names.add(it.next().getName()); + } + + Assert.assertEquals( + Arrays.asList("root", "a", "a1", "a1g", "b", "bg"), + names + ); + } + + @Test + public void testDepthValuesDuringTraversal() { + Node root = buildTree(); + SceneGraphIterator it = new SceneGraphIterator(root); + + List names = new ArrayList<>(); + List depths = new ArrayList<>(); + while (it.hasNext()) { + Spatial s = it.next(); + names.add(s.getName()); + depths.add(it.getDepth()); + } + + Assert.assertEquals(Arrays.asList("root", "a", "a1", "a1g", "b", "bg"), names); + Assert.assertEquals(Arrays.asList(0, 1, 2, 3, 1, 2), depths); + } + + @Test + public void testIgnoreChildrenSkipsSubtree() { + Node root = buildTree(); + SceneGraphIterator it = new SceneGraphIterator(root); + + List names = new ArrayList<>(); + while (it.hasNext()) { + Spatial s = it.next(); + names.add(s.getName()); + if ("a".equals(s.getName())) { + it.ignoreChildren(); // skip a1 and a1g + } + } + + Assert.assertEquals(Arrays.asList("root", "a", "b", "bg"), names); + } + + @Test + public void testCurrentTracksLastReturnedSpatial() { + Node root = buildTree(); + SceneGraphIterator it = new SceneGraphIterator(root); + + Assert.assertNull(it.current()); + Spatial first = it.next(); + Assert.assertSame(first, it.current()); + + Spatial second = it.next(); + Assert.assertSame(second, it.current()); + } + + @Test + public void testLeafMainSpatialIteratesExactlyOnce() { + Geometry leaf = new Geometry("leaf", new Mesh()); + SceneGraphIterator it = new SceneGraphIterator(leaf); + + Assert.assertTrue(it.hasNext()); + Assert.assertEquals("leaf", it.next().getName()); + Assert.assertFalse(it.hasNext()); + } +}