diff --git a/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java index 7a139a43a4..20e3232ad7 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,6 +45,8 @@ import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Curve; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -54,7 +56,7 @@ * Motion path is used to create a path between way points. * @author Nehon */ -public class MotionPath implements Savable { +public class MotionPath implements JmeCloneable, Savable { private Node debugNode; private AssetManager assetManager; @@ -177,6 +179,40 @@ public void read(JmeImporter im) throws IOException { spline = (Spline) in.readSavable("spline", null); } + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned MotionPath into a deep-cloned one, using the specified + * cloner and original to resolve copied fields. + * + * @param cloner the cloner that's cloning this MotionPath (not null) + * @param original the object from which this MotionPath was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + this.debugNode = cloner.clone(debugNode); + this.spline = cloner.clone(spline); + /* + * The clone will share both the asset manager and the list of listeners + * of the original MotionPath. + */ + } + + /** + * Creates a shallow clone for the JME cloner. + * + * @return a new object + */ + @Override + public MotionPath jmeClone() { + try { + MotionPath clone = (MotionPath) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } + /** * compute the index of the waypoint and the interpolation value according to a distance * returns a vector 2 containing the index in the x field and the interpolation value in the y field diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java index 2bd4485768..fa30dff6ba 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -312,6 +312,9 @@ public Object jmeClone() { @Override public void cloneFields(Cloner cloner, Object original) { + this.lookAt = cloner.clone(lookAt); + this.path = cloner.clone(path); + this.rotation = cloner.clone(rotation); this.spatial = cloner.clone(spatial); } diff --git a/jme3-core/src/main/java/com/jme3/math/Spline.java b/jme3-core/src/main/java/com/jme3/math/Spline.java index 5ca85b9081..cdd7f48c77 100644 --- a/jme3-core/src/main/java/com/jme3/math/Spline.java +++ b/jme3-core/src/main/java/com/jme3/math/Spline.java @@ -32,6 +32,8 @@ package com.jme3.math; import com.jme3.export.*; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -41,7 +43,7 @@ * * @author Nehon */ -public class Spline implements Savable { +public class Spline implements JmeCloneable, Savable { public enum SplineType { Linear, @@ -536,4 +538,41 @@ public void read(JmeImporter im) throws IOException { weights = in.readFloatArray("weights", null); basisFunctionDegree = in.readInt("basisFunctionDegree", 0); } + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned spline into a deep-cloned one, using the specified cloner + * and original to resolve copied fields. + * + * @param cloner the cloner that's cloning this spline (not null) + * @param original the object from which this spline was shallow-cloned (not + * null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + this.controlPoints = cloner.clone(controlPoints); + if (segmentsLength != null) { + this.segmentsLength = new ArrayList<>(segmentsLength); + } + this.CRcontrolPoints = cloner.clone(CRcontrolPoints); + if (knots != null) { + this.knots = new ArrayList<>(knots); + } + this.weights = cloner.clone(weights); + } + + /** + * Creates a shallow clone for the JME cloner. + * + * @return a new object + */ + @Override + public Spline jmeClone() { + try { + Spline clone = (Spline) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } } diff --git a/jme3-core/src/test/java/com/jme3/cinematic/MotionPathTest.java b/jme3-core/src/test/java/com/jme3/cinematic/MotionPathTest.java new file mode 100644 index 0000000000..7fa2c71f58 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/cinematic/MotionPathTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 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.cinematic; + +import com.jme3.math.Vector3f; +import com.jme3.util.clone.Cloner; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link MotionPath} class works. + * + * @author Stephen Gold + */ +public class MotionPathTest { + + /** + * Verifies that MotionPath cloning works. + */ + @Test + public void cloneMotionPath() { + MotionPath original = new MotionPath(); + original.setCycle(true); + original.addWayPoint(new Vector3f(20, 3, 0)); + original.addWayPoint(new Vector3f(0, 3, 20)); + original.addWayPoint(new Vector3f(-20, 3, 0)); + original.addWayPoint(new Vector3f(0, 3, -20)); + original.setCurveTension(0.83f); + + MotionPath clone = Cloner.deepClone(original); + + // Verify that the clone is non-null and distinct from the original: + Assert.assertNotNull(clone); + Assert.assertTrue(clone != original); + + // Compare the return values of various getters: + Assert.assertEquals( + clone.getCurveTension(), original.getCurveTension(), 0f); + Assert.assertEquals(clone.getLength(), original.getLength(), 0f); + Assert.assertEquals(clone.getNbWayPoints(), original.getNbWayPoints()); + Assert.assertEquals( + clone.getPathSplineType(), original.getPathSplineType()); + Assert.assertEquals(clone.getWayPoint(0), original.getWayPoint(0)); + Assert.assertEquals(clone.isCycle(), original.isCycle()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/SplineTest.java b/jme3-core/src/test/java/com/jme3/math/SplineTest.java index 3678282069..447bf1d7bb 100644 --- a/jme3-core/src/test/java/com/jme3/math/SplineTest.java +++ b/jme3-core/src/test/java/com/jme3/math/SplineTest.java @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.asset.DesktopAssetManager; import com.jme3.export.binary.BinaryExporter; +import com.jme3.util.clone.Cloner; import java.util.ArrayList; import java.util.List; import org.junit.Assert; @@ -52,6 +53,47 @@ public class SplineTest { // ************************************************************************* // tests + /** + * Verifies that spline cloning works correctly. + */ + @Test + public void cloneSplines() { + // Clone a Bézier spline: + { + Spline test1 = createBezier(); + Spline copy1 = Cloner.deepClone(test1); + assertSplineEquals(test1, copy1); + } + + // Clone a NURB spline: + { + Spline test2 = createNurb(); + Spline copy2 = Cloner.deepClone(test2); + assertSplineEquals(test2, copy2); + } + + // Clone a Catmull-Rom spline: + { + Spline test3 = createCatmullRom(); + Spline copy3 = Cloner.deepClone(test3); + assertSplineEquals(test3, copy3); + } + + // Clone a linear spline: + { + Spline test4 = createLinear(); + Spline copy4 = Cloner.deepClone(test4); + assertSplineEquals(test4, copy4); + } + + // Clone a default spline: + { + Spline test5 = new Spline(); + Spline copy5 = Cloner.deepClone(test5); + assertSplineEquals(test5, copy5); + } + } + /** * Verifies that spline serialization/deserialization works correctly. */ @@ -59,63 +101,28 @@ public class SplineTest { public void saveAndLoadSplines() { // Serialize and deserialize a Bezier spline: { - Vector3f[] controlPoints1 = { - new Vector3f(0f, 1f, 0f), new Vector3f(1f, 2f, 1f), - new Vector3f(1.5f, 1.5f, 1.5f), new Vector3f(2f, 0f, 1f) - }; - - Spline test1 = new Spline( - Spline.SplineType.Bezier, controlPoints1, 0.1f, true); + Spline test1 = createBezier(); Spline copy1 = BinaryExporter.saveAndLoad(assetManager, test1); assertSplineEquals(test1, copy1); } // Serialize and deserialize a NURB spline: { - List controlPoints2 = new ArrayList<>(5); - controlPoints2.add(new Vector4f(0f, 1f, 2f, 3f)); - controlPoints2.add(new Vector4f(3f, 1f, 4f, 0f)); - controlPoints2.add(new Vector4f(2f, 5f, 3f, 0f)); - controlPoints2.add(new Vector4f(3f, 2f, 3f, 1f)); - controlPoints2.add(new Vector4f(0.5f, 1f, 0.6f, 5f)); - List nurbKnots = new ArrayList<>(6); - nurbKnots.add(0.2f); - nurbKnots.add(0.3f); - nurbKnots.add(0.4f); - nurbKnots.add(0.43f); - nurbKnots.add(0.51f); - nurbKnots.add(0.52f); - - Spline test2 = new Spline(controlPoints2, nurbKnots); + Spline test2 = createNurb(); Spline copy2 = BinaryExporter.saveAndLoad(assetManager, test2); assertSplineEquals(test2, copy2); } // Serialize and deserialize a Catmull-Rom spline: { - List controlPoints3 = new ArrayList<>(6); - controlPoints3.add(new Vector3f(0f, 1f, 2f)); - controlPoints3.add(new Vector3f(3f, -1f, 4f)); - controlPoints3.add(new Vector3f(2f, 5f, 3f)); - controlPoints3.add(new Vector3f(3f, -2f, 3f)); - controlPoints3.add(new Vector3f(0.5f, 1f, 0.6f)); - controlPoints3.add(new Vector3f(-0.5f, 4f, 0.2f)); - - Spline test3 = new Spline( - Spline.SplineType.CatmullRom, controlPoints3, 0.01f, false); + Spline test3 = createCatmullRom(); Spline copy3 = BinaryExporter.saveAndLoad(assetManager, test3); assertSplineEquals(test3, copy3); } // Serialize and deserialize a linear spline: { - List controlPoints4 = new ArrayList<>(3); - controlPoints4.add(new Vector3f(3f, -1f, 4f)); - controlPoints4.add(new Vector3f(2f, 0f, 3f)); - controlPoints4.add(new Vector3f(3f, -2f, 3f)); - - Spline test4 = new Spline( - Spline.SplineType.Linear, controlPoints4, 0f, true); + Spline test4 = createLinear(); Spline copy4 = BinaryExporter.saveAndLoad(assetManager, test4); assertSplineEquals(test4, copy4); } @@ -131,14 +138,22 @@ public void saveAndLoadSplines() { // private helper methods /** - * Verify that the specified lists are equivalent. + * Verifies that the specified lists are equivalent but distinct. * * @param s1 the first list to compare (may be null, unaffected) * @param s2 the 2nd list to compare (may be null, unaffected) */ private static void assertListEquals(List a1, List a2) { - if (a1 != a2) { + if (a1 == null || a2 == null) { + // If either list is null, verify that both are null: + Assert.assertNull(a1); + Assert.assertNull(a2); + + } else { + // Verify that the lists are distinct and and of equal length: + Assert.assertTrue(a1 != a2); Assert.assertEquals(a1.size(), a2.size()); + for (int i = 0; i < a1.size(); ++i) { Assert.assertEquals(a1.get(i), a2.get(i)); } @@ -172,4 +187,80 @@ private static void assertSplineEquals(Spline s1, Spline s2) { s1.getTotalLength(), s2.getTotalLength(), 0f); Assert.assertArrayEquals(s1.getWeights(), s2.getWeights(), 0f); } + + /** + * Generates a simple cyclic Bézier spline for testing. + * + * @return a new Spline + */ + private static Spline createBezier() { + Vector3f[] controlPoints1 = { + new Vector3f(0f, 1f, 0f), new Vector3f(1f, 2f, 1f), + new Vector3f(1.5f, 1.5f, 1.5f), new Vector3f(2f, 0f, 1f) + }; + + Spline result = new Spline( + Spline.SplineType.Bezier, controlPoints1, 0.1f, true); + return result; + } + + /** + * Generates a simple acyclic Catmull-Rom spline for testing. + * + * @return a new Spline + */ + private static Spline createCatmullRom() { + List controlPoints3 = new ArrayList<>(6); + controlPoints3.add(new Vector3f(0f, 1f, 2f)); + controlPoints3.add(new Vector3f(3f, -1f, 4f)); + controlPoints3.add(new Vector3f(2f, 5f, 3f)); + controlPoints3.add(new Vector3f(3f, -2f, 3f)); + controlPoints3.add(new Vector3f(0.5f, 1f, 0.6f)); + controlPoints3.add(new Vector3f(-0.5f, 4f, 0.2f)); + + Spline result = new Spline( + Spline.SplineType.CatmullRom, controlPoints3, 0.01f, false); + return result; + } + + /** + * Generates a simple cyclic linear spline for testing. + * + * @return a new Spline + */ + private static Spline createLinear() { + List controlPoints4 = new ArrayList<>(3); + controlPoints4.add(new Vector3f(3f, -1f, 4f)); + controlPoints4.add(new Vector3f(2f, 0f, 3f)); + controlPoints4.add(new Vector3f(3f, -2f, 3f)); + + Spline result = new Spline( + Spline.SplineType.Linear, controlPoints4, 0f, true); + return result; + } + + /** + * Generates a simple NURB spline for testing. + * + * @return a new Spline + */ + private static Spline createNurb() { + List controlPoints2 = new ArrayList<>(5); + controlPoints2.add(new Vector4f(0f, 1f, 2f, 3f)); + controlPoints2.add(new Vector4f(3f, 1f, 4f, 0f)); + controlPoints2.add(new Vector4f(2f, 5f, 3f, 0f)); + controlPoints2.add(new Vector4f(3f, 2f, 3f, 1f)); + controlPoints2.add(new Vector4f(0.5f, 1f, 0.6f, 5f)); + + List nurbKnots = new ArrayList<>(6); + nurbKnots.add(0.2f); + nurbKnots.add(0.3f); + nurbKnots.add(0.4f); + nurbKnots.add(0.43f); + nurbKnots.add(0.51f); + nurbKnots.add(0.52f); + + Spline result = new Spline(controlPoints2, nurbKnots); + return result; + } }