Browse Source

First round of getting JmeCloneable implemented... added
support for Cloner to the controls that implemented cloneForSpatial().
Unused until spatial cloning is implemented.

Paul Speed 10 years ago
parent
commit
8b1ddbe60f
27 changed files with 547 additions and 24 deletions
  1. 9 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java
  2. 11 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java
  3. 26 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
  4. 22 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java
  5. 14 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java
  6. 36 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
  7. 60 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
  8. 30 1
      jme3-core/src/main/java/com/jme3/animation/AnimControl.java
  9. 30 1
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  10. 23 1
      jme3-core/src/main/java/com/jme3/animation/AudioTrack.java
  11. 2 1
      jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java
  12. 33 0
      jme3-core/src/main/java/com/jme3/animation/EffectTrack.java
  13. 12 1
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  14. 26 1
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  15. 17 1
      jme3-core/src/main/java/com/jme3/animation/TrackInfo.java
  16. 13 1
      jme3-core/src/main/java/com/jme3/app/StatsView.java
  17. 28 1
      jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
  18. 17 1
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  19. 20 1
      jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
  20. 17 1
      jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java
  21. 13 2
      jme3-core/src/main/java/com/jme3/scene/control/LodControl.java
  22. 13 0
      jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java
  23. 17 1
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  24. 13 1
      jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java
  25. 11 0
      jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java
  26. 9 0
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java
  27. 25 1
      jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java

+ 9 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java

@@ -41,6 +41,8 @@ import com.jme3.math.Vector3f;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -49,7 +51,7 @@ import java.io.IOException;
  *
  * @author normenhansen
  */
-public abstract class AbstractPhysicsControl implements PhysicsControl {
+public abstract class AbstractPhysicsControl implements PhysicsControl, JmeCloneable {
 
     private final Quaternion tmp_inverseWorldRotation = new Quaternion();
     protected Spatial spatial;
@@ -161,6 +163,12 @@ public abstract class AbstractPhysicsControl implements PhysicsControl {
 
     }
     
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.spatial = cloner.clone(spatial);
+        createSpatialData(this.spatial);
+    }
+         
     public void setSpatial(Spatial spatial) {
         if (this.spatial != null && this.spatial != spatial) {
             removeSpatialData(this.spatial);

+ 11 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java

@@ -50,6 +50,8 @@ import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.List;
 import java.util.logging.Level;
@@ -68,7 +70,7 @@ import java.util.logging.Logger;
  *
  * @author normenhansen
  */
-public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener {
+public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener, JmeCloneable {
 
     protected static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName());
     protected PhysicsRigidBody rigidBody;
@@ -670,6 +672,14 @@ public class BetterCharacterControl extends AbstractPhysicsControl implements Ph
         return control;
     }
 
+    @Override
+    public Object jmeClone() {
+        BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
+        control.setJumpForce(jumpForce);
+        control.spatial = this.spatial;
+        return control;
+    }     
+
     @Override
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);

+ 26 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java

@@ -44,13 +44,15 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
  * You might want to try <code>BetterCharacterControl</code> as well.
  * @author normenhansen
  */
-public class CharacterControl extends PhysicsCharacter implements PhysicsControl {
+public class CharacterControl extends PhysicsCharacter implements PhysicsControl, JmeCloneable {
 
     protected Spatial spatial;
     protected boolean enabled = true;
@@ -104,6 +106,29 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
         return control;
     }
 
+    @Override
+    public Object jmeClone() {
+        CharacterControl control = new CharacterControl(collisionShape, stepHeight);
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setFallSpeed(getFallSpeed());
+        control.setGravity(getGravity());
+        control.setJumpSpeed(getJumpSpeed());
+        control.setMaxSlope(getMaxSlope());
+        control.setPhysicsLocation(getPhysicsLocation());
+        control.setUpAxis(getUpAxis());
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+        control.spatial = this.spatial;
+        return control;
+    }     
+
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.spatial = cloner.clone(spatial);
+    }
+         
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         setUserObject(spatial);

+ 22 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java

@@ -44,6 +44,8 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -51,7 +53,7 @@ import java.io.IOException;
  * overlaps with other physics objects (e.g. aggro radius).
  * @author normenhansen
  */
-public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
+public class GhostControl extends PhysicsGhostObject implements PhysicsControl, JmeCloneable {
 
     protected Spatial spatial;
     protected boolean enabled = true;
@@ -106,6 +108,25 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
         return control;
     }
 
+    @Override   
+    public Object jmeClone() {
+        GhostControl control = new GhostControl(collisionShape);
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setPhysicsLocation(getPhysicsLocation());
+        control.setPhysicsRotation(getPhysicsRotationMatrix());
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+        control.spatial = this.spatial;
+        return control;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.spatial = cloner.clone(spatial);
+    }
+         
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         setUserObject(spatial);

+ 14 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java

@@ -61,6 +61,8 @@ import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.*;
 import java.util.logging.Level;
@@ -92,7 +94,7 @@ import java.util.logging.Logger;
  *
  * @author Normen Hansen and Rémy Bouquet (Nehon)
  */
-public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener {
+public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable {
 
     protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
     protected List<RagdollCollisionListener> listeners;
@@ -920,6 +922,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         return control;
     }
    
+    @Override   
+    public Object jmeClone() {
+        KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold);        
+        control.setMode(mode);
+        control.setRootMass(rootMass);
+        control.setWeightThreshold(weightThreshold);
+        control.setApplyPhysicsLocal(applyLocal);
+        control.spatial = this.spatial;
+        return control;
+    }     
+
     public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) {
         Vector3f target = worldPos.subtract(targetModel.getWorldTranslation());
         ikTargets.put(bone.getName(), target);

+ 36 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java

@@ -51,13 +51,15 @@ import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.shape.Box;
 import com.jme3.scene.shape.Sphere;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
  *
  * @author normenhansen
  */
-public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl {
+public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl, JmeCloneable {
 
     protected Spatial spatial;
     protected boolean enabled = true;
@@ -116,6 +118,39 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
         return control;
     }
 
+    @Override   
+    public Object jmeClone() {
+        RigidBodyControl control = new RigidBodyControl(collisionShape, mass);
+        control.setAngularFactor(getAngularFactor());
+        control.setAngularSleepingThreshold(getAngularSleepingThreshold());
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setDamping(getLinearDamping(), getAngularDamping());
+        control.setFriction(getFriction());
+        control.setGravity(getGravity());
+        control.setKinematic(isKinematic());
+        control.setKinematicSpatial(isKinematicSpatial());
+        control.setLinearSleepingThreshold(getLinearSleepingThreshold());
+        control.setPhysicsLocation(getPhysicsLocation(null));
+        control.setPhysicsRotation(getPhysicsRotationMatrix(null));
+        control.setRestitution(getRestitution());
+
+        if (mass > 0) {
+            control.setAngularVelocity(getAngularVelocity());
+            control.setLinearVelocity(getLinearVelocity());
+        }
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+        control.spatial = this.spatial;
+        return control;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.spatial = cloner.clone(spatial);
+    }
+         
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         setUserObject(spatial);

+ 60 - 1
jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java

@@ -46,6 +46,8 @@ import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.Iterator;
 
@@ -53,7 +55,7 @@ import java.util.Iterator;
  *
  * @author normenhansen
  */
-public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
+public class VehicleControl extends PhysicsVehicle implements PhysicsControl, JmeCloneable {
 
     protected Spatial spatial;
     protected boolean enabled = true;
@@ -156,6 +158,63 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
         return control;
     }
 
+    @Override   
+    public Object jmeClone() {
+        VehicleControl control = new VehicleControl(collisionShape, mass);
+        control.setAngularFactor(getAngularFactor());
+        control.setAngularSleepingThreshold(getAngularSleepingThreshold());
+        control.setAngularVelocity(getAngularVelocity());
+        control.setCcdMotionThreshold(getCcdMotionThreshold());
+        control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
+        control.setCollideWithGroups(getCollideWithGroups());
+        control.setCollisionGroup(getCollisionGroup());
+        control.setDamping(getLinearDamping(), getAngularDamping());
+        control.setFriction(getFriction());
+        control.setGravity(getGravity());
+        control.setKinematic(isKinematic());
+        control.setLinearSleepingThreshold(getLinearSleepingThreshold());
+        control.setLinearVelocity(getLinearVelocity());
+        control.setPhysicsLocation(getPhysicsLocation());
+        control.setPhysicsRotation(getPhysicsRotationMatrix());
+        control.setRestitution(getRestitution());
+
+        control.setFrictionSlip(getFrictionSlip());
+        control.setMaxSuspensionTravelCm(getMaxSuspensionTravelCm());
+        control.setSuspensionStiffness(getSuspensionStiffness());
+        control.setSuspensionCompression(tuning.suspensionCompression);
+        control.setSuspensionDamping(tuning.suspensionDamping);
+        control.setMaxSuspensionForce(getMaxSuspensionForce());
+    
+        for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+            VehicleWheel wheel = it.next();
+            VehicleWheel newWheel = control.addWheel(wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), wheel.isFrontWheel());
+            newWheel.setFrictionSlip(wheel.getFrictionSlip());
+            newWheel.setMaxSuspensionTravelCm(wheel.getMaxSuspensionTravelCm());
+            newWheel.setSuspensionStiffness(wheel.getSuspensionStiffness());
+            newWheel.setWheelsDampingCompression(wheel.getWheelsDampingCompression());
+            newWheel.setWheelsDampingRelaxation(wheel.getWheelsDampingRelaxation());
+            newWheel.setMaxSuspensionForce(wheel.getMaxSuspensionForce());
+
+            // Copy the wheel spatial reference directly for now.  They'll
+            // get fixed up in the cloneFields() method
+            newWheel.setWheelSpatial(wheel.getWheelSpatial());
+        }
+        control.setApplyPhysicsLocal(isApplyPhysicsLocal());
+        
+        control.spatial = spatial;
+        return control;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.spatial = cloner.clone(spatial);
+         
+        for( VehicleWheel wheel : wheels ) {
+            Spatial spatial = cloner.clone(wheel.getWheelSpatial());
+            wheel.setWheelSpatial(spatial);
+        }        
+    }
+         
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         setUserObject(spatial);

+ 30 - 1
jme3-core/src/main/java/com/jme3/animation/AnimControl.java

@@ -38,11 +38,14 @@ import com.jme3.scene.Mesh;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Map.Entry;
 
 /**
@@ -65,7 +68,7 @@ import java.util.Map.Entry;
  *
  * @author Kirill Vainer
  */
-public final class AnimControl extends AbstractControl implements Cloneable {
+public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable {
 
     /**
      * Skeleton object must contain corresponding data for the targets' weight buffers.
@@ -131,6 +134,32 @@ public final class AnimControl extends AbstractControl implements Cloneable {
         }
     }
 
+    @Override   
+    public Object jmeClone() {
+        AnimControl clone = (AnimControl) super.jmeClone();
+        clone.channels = new ArrayList<AnimChannel>();
+        clone.listeners = new ArrayList<AnimEventListener>();
+
+        return clone;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+        super.cloneFields(cloner, original);
+        
+        this.skeleton = cloner.clone(skeleton);
+ 
+        // Note cloneForSpatial() never actually cloned the animation map... just its reference       
+        HashMap<String, Animation> newMap = new HashMap<>();
+         
+        // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial
+        for( Map.Entry<String, Animation> e : animationMap.entrySet() ) {
+            newMap.put(e.getKey(), cloner.clone(e.getValue()));
+        }
+        
+        this.animationMap = newMap;
+    }
+         
     /**
      * @param animations Set the animations that this <code>AnimControl</code>
      * will be capable of playing. The animations should be compatible

+ 30 - 1
jme3-core/src/main/java/com/jme3/animation/Animation.java

@@ -35,6 +35,8 @@ import com.jme3.export.*;
 import com.jme3.scene.Spatial;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -42,7 +44,7 @@ import java.io.IOException;
  * 
  * @author Kirill Vainer, Marcin Roguski (Kaelthas)
  */
-public class Animation implements Savable, Cloneable {
+public class Animation implements Savable, Cloneable, JmeCloneable {
 
     /** 
      * The name of the animation. 
@@ -190,6 +192,33 @@ public class Animation implements Savable, Cloneable {
         }
     }
 
+    @Override   
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch( CloneNotSupportedException e ) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+         
+        // There is some logic here that I'm copying but I'm not sure if
+        // it's a mistake or not.  If a track is not a CloneableTrack then it
+        // isn't cloned at all... even though they all implement clone() methods. -pspeed
+        SafeArrayList<Track> newTracks = new SafeArrayList<>(Track.class);
+        for( Track track : tracks ) {
+            if( track instanceof ClonableTrack ) {
+                newTracks.add(cloner.clone(track));
+            } else {
+                // this is the part that seems fishy 
+                newTracks.add(track);
+            }
+        }
+        this.tracks = newTracks;
+    }
+         
     @Override
     public String toString() {
         return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';

+ 23 - 1
jme3-core/src/main/java/com/jme3/animation/AudioTrack.java

@@ -39,6 +39,8 @@ import com.jme3.export.OutputCapsule;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -193,7 +195,27 @@ public class AudioTrack implements ClonableTrack {
         return audioTrack;
     }
 
-    /**
+    @Override   
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch( CloneNotSupportedException e ) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }     
+
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+        // Duplicating the old cloned state from cloneForSpatial()
+        this.initialized = false;
+        this.started = false;
+        this.played = false; 
+        this.audio = cloner.clone(audio);
+    }
+         
+         
+    /**    
      * recursive function responsible for finding the newly cloned AudioNode
      *
      * @param spat

+ 2 - 1
jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java

@@ -32,6 +32,7 @@
 package com.jme3.animation;
 
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.JmeCloneable;
 
 /**
  * An interface that allow to clone a Track for a given Spatial.
@@ -43,7 +44,7 @@ import com.jme3.scene.Spatial;
  *
  * @author Nehon
  */
-public interface ClonableTrack extends Track {
+public interface ClonableTrack extends Track, JmeCloneable {
 
     /**
      * Allows to clone the track for a given Spatial.

+ 33 - 0
jme3-core/src/main/java/com/jme3/animation/EffectTrack.java

@@ -44,6 +44,8 @@ import com.jme3.scene.Spatial.CullHint;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -116,6 +118,22 @@ public class EffectTrack implements ClonableTrack {
             }
         }
 
+        @Override   
+        public Object jmeClone() {
+            KillParticleControl c = new KillParticleControl();
+            //this control should be removed as it shouldn't have been persisted in the first place
+            //In the quest to find the less hackish solution to achieve this, 
+            //making it remove itself from the spatial in the first update loop when loaded was the less bad. 
+            c.remove = true;
+            c.spatial = spatial;
+            return c;
+        }     
+
+        @Override   
+        public void cloneFields( Cloner cloner, Object original ) { 
+            this.spatial = cloner.clone(spatial);
+        }
+         
         @Override
         protected void controlRender(RenderManager rm, ViewPort vp) {
         }
@@ -284,6 +302,21 @@ public class EffectTrack implements ClonableTrack {
         return effectTrack;
     }
 
+    @Override   
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch( CloneNotSupportedException e ) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }     
+
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.emitter = cloner.clone(emitter);
+    }
+         
     /**
      * recursive function responsible for finding the newly cloned Emitter
      *

+ 12 - 1
jme3-core/src/main/java/com/jme3/animation/Skeleton.java

@@ -34,6 +34,8 @@ package com.jme3.animation;
 import com.jme3.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.JmeCloneable;
+import com.jme3.util.clone.Cloner;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
@@ -45,7 +47,7 @@ import java.util.List;
  * 
  * @author Kirill Vainer
  */
-public final class Skeleton implements Savable {
+public final class Skeleton implements Savable, JmeCloneable {
 
     private Bone[] rootBones;
     private Bone[] boneList;
@@ -118,6 +120,15 @@ public final class Skeleton implements Savable {
     public Skeleton() {
     }
 
+    @Override   
+    public Object jmeClone() {
+        return new Skeleton(this);
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+    }
+
     private void createSkinningMatrices() {
         skinningMatrixes = new Matrix4f[boneList.length];
         for (int i = 0; i < skinningMatrixes.length; i++) {

+ 26 - 1
jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java

@@ -46,6 +46,8 @@ import com.jme3.scene.control.Control;
 import com.jme3.shader.VarType;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
@@ -63,7 +65,7 @@ import java.util.logging.Logger;
  *
  * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
  */
-public class SkeletonControl extends AbstractControl implements Cloneable {
+public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable {
 
     /**
      * The skeleton of the model.
@@ -386,6 +388,29 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
         return clone;
     }
 
+    @Override   
+    public Object jmeClone() {
+        return super.jmeClone();
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+        super.cloneFields(cloner, original);
+         
+        this.skeleton = cloner.clone(skeleton);
+        
+        // If the targets were cloned then this will clone them.  If the targets
+        // were shared then this will share them.
+        this.targets = cloner.clone(targets);
+        
+        // Not automatic set cloning yet
+        Set<Material> newMaterials = new HashSet<Material>();
+        for( Material m : this.materials ) {
+            newMaterials.add(cloner.clone(m));
+        }
+        this.materials = newMaterials;
+    }
+         
     /**
      *
      * @param boneName the name of the bone

+ 17 - 1
jme3-core/src/main/java/com/jme3/animation/TrackInfo.java

@@ -36,6 +36,8 @@ import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.Savable;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.ArrayList;
 
@@ -48,7 +50,7 @@ import java.util.ArrayList;
  *
  * @author Nehon
  */
-public class TrackInfo implements Savable {
+public class TrackInfo implements Savable, JmeCloneable {
 
     ArrayList<Track> tracks = new ArrayList<Track>();
 
@@ -72,4 +74,18 @@ public class TrackInfo implements Savable {
     public void addTrack(Track track) {
         tracks.add(track);
     }
+    
+    @Override   
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch( CloneNotSupportedException e ) {
+            throw new RuntimeException("Error cloning", e);
+        }
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.tracks = cloner.clone(tracks); 
+    }             
 }

+ 13 - 1
jme3-core/src/main/java/com/jme3/app/StatsView.java

@@ -41,6 +41,8 @@ import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 
 /**
  * The <code>StatsView</code> provides a heads-up display (HUD) of various
@@ -58,7 +60,7 @@ import com.jme3.scene.control.Control;
  * rootNode.attachChild(statsView);<br/>
  * </code>
  */
-public class StatsView extends Node implements Control {
+public class StatsView extends Node implements Control, JmeCloneable {
 
     private BitmapText statText;
     private Statistics statistics;
@@ -120,6 +122,16 @@ public class StatsView extends Node implements Control {
         return (Control) spatial;
     }
 
+    @Override   
+    public Object jmeClone() {
+        throw new UnsupportedOperationException("Not yet implemented.");
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        throw new UnsupportedOperationException("Not yet implemented.");
+    }
+         
     public void setSpatial(Spatial spatial) {
     }
 

+ 28 - 1
jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java

@@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -56,7 +58,7 @@ import java.io.IOException;
  *
  * @author Nehon
  */
-public class MotionEvent extends AbstractCinematicEvent implements Control {
+public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable {
 
     protected Spatial spatial;
     protected int currentWayPoint;
@@ -292,6 +294,31 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
         return control;
     }
 
+    @Override   
+    public Object jmeClone() {
+        MotionEvent control = new MotionEvent();
+        control.path = path;
+        control.playState = playState;
+        control.currentWayPoint = currentWayPoint;
+        control.currentValue = currentValue;
+        control.direction = direction.clone();
+        control.lookAt = lookAt.clone();
+        control.upVector = upVector.clone();
+        control.rotation = rotation.clone();
+        control.initialDuration = initialDuration;
+        control.speed = speed;
+        control.loopMode = loopMode;
+        control.directionType = directionType;
+        control.spatial = spatial;
+
+        return control;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.spatial = cloner.clone(spatial);
+    }
+         
     @Override
     public void onPlay() {
         traveledDistance = 0;

+ 17 - 1
jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java

@@ -54,6 +54,8 @@ import com.jme3.scene.Geometry;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -108,7 +110,7 @@ public class ParticleEmitter extends Geometry {
     private transient Vector3f temp = new Vector3f();
     private transient Vector3f lastPos;
 
-    public static class ParticleEmitterControl implements Control {
+    public static class ParticleEmitterControl implements Control, JmeCloneable {
 
         ParticleEmitter parentEmitter;
 
@@ -125,6 +127,20 @@ public class ParticleEmitter extends Geometry {
             // fixed automatically by ParticleEmitter.clone() method.
         }
 
+        @Override   
+        public Object jmeClone() {
+            try {
+                return super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new RuntimeException("Error cloning", e);
+            }
+        }     
+
+        @Override   
+        public void cloneFields( Cloner cloner, Object original ) { 
+            this.parentEmitter = cloner.clone(parentEmitter);
+        }
+             
         public void setSpatial(Spatial spatial) {
         }
 

+ 20 - 1
jme3-core/src/main/java/com/jme3/input/ChaseCamera.java

@@ -43,13 +43,15 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
  * A camera that follows a spatial and can turn around it by dragging the mouse
  * @author nehon
  */
-public class ChaseCamera implements ActionListener, AnalogListener, Control {
+public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable {
 
     protected Spatial target = null;
     protected float minVerticalRotation = 0.00f;
@@ -575,6 +577,23 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
         return cc;
     }
 
+    @Override   
+    public Object jmeClone() {
+        ChaseCamera cc = new ChaseCamera(cam, inputManager);
+        cc.target = target;
+        cc.setMaxDistance(getMaxDistance());
+        cc.setMinDistance(getMinDistance());
+        return cc;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.target = cloner.clone(target);
+        computePosition();
+        prevPos = new Vector3f(target.getWorldTranslation());
+        cam.setLocation(pos);
+    }
+         
     /**
      * Sets the spacial for the camera control, should only be used internally
      * @param spatial

+ 17 - 1
jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java

@@ -38,6 +38,8 @@ import com.jme3.export.OutputCapsule;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -45,7 +47,7 @@ import java.io.IOException;
  *
  * @author Kirill Vainer
  */
-public abstract class AbstractControl implements Control, Cloneable {
+public abstract class AbstractControl implements Control, JmeCloneable {
 
     protected boolean enabled = true;
     protected Spatial spatial;
@@ -105,6 +107,20 @@ public abstract class AbstractControl implements Control, Cloneable {
         } 
     }
 
+    @Override
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch( CloneNotSupportedException e ) {
+            throw new RuntimeException( "Can't clone control for spatial", e );
+        }
+    }     
+
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) { 
+        this.spatial = cloner.clone(spatial);
+    }
+         
     public void update(float tpf) {
         if (!enabled)
             return;

+ 13 - 2
jme3-core/src/main/java/com/jme3/scene/control/LodControl.java

@@ -43,6 +43,8 @@ import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
@@ -56,7 +58,7 @@ import java.io.IOException;
  * and will update the spatial's LOD if the camera has moved by a specified
  * amount.
  */
-public class LodControl extends AbstractControl implements Cloneable {
+public class LodControl extends AbstractControl implements Cloneable, JmeCloneable {
 
     private float trisPerPixel = 1f;
     private float distTolerance = 1f;
@@ -140,7 +142,16 @@ public class LodControl extends AbstractControl implements Cloneable {
         clone.lastLevel = 0;
         clone.numTris = numTris != null ? numTris.clone() : null;
         return clone;
-   }
+    }
+
+    @Override
+    public Object jmeClone() {
+        LodControl clone = (LodControl)super.jmeClone();
+        clone.lastDistance = 0;
+        clone.lastLevel = 0;
+        clone.numTris = numTris != null ? numTris.clone() : null;
+        return clone;
+    }     
 
     @Override
     protected void controlUpdate(float tpf) {

+ 13 - 0
jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java

@@ -35,6 +35,8 @@ import com.jme3.app.AppTask;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Future;
@@ -94,4 +96,15 @@ public class UpdateControl extends AbstractControl {
         return control;
     }
     
+    @Override
+    public Object jmeClone() {
+        UpdateControl clone = (UpdateControl)super.jmeClone();
+        
+        // This is kind of questionable since the tasks aren't cloned and have
+        // no reference to the new spatial or anything.  They'll get run again
+        // but it's not clear to me why that would be desired.  I'm doing it
+        // because the old cloneForSpatial() code does.  FIXME?   -pspeed
+        clone.taskQueue.addAll(taskQueue);
+        return clone;
+    }     
 }

+ 17 - 1
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java

@@ -44,6 +44,8 @@ import com.jme3.scene.control.Control;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.material.MatParam;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.HashMap;
 
@@ -106,7 +108,7 @@ public class InstancedNode extends GeometryGroupNode {
         }
     }
     
-    private static class InstancedNodeControl implements Control {
+    private static class InstancedNodeControl implements Control, JmeCloneable {
 
         private InstancedNode node;
         
@@ -124,6 +126,20 @@ public class InstancedNode extends GeometryGroupNode {
             // fixed automatically by InstancedNode.clone() method.
         }
         
+        @Override
+        public Object jmeClone() {
+            try {
+                return super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new RuntimeException("Error cloning control", e);
+            }
+        }     
+
+        @Override
+        public void cloneFields( Cloner cloner, Object original ) { 
+            this.node = cloner.clone(node);
+        }
+         
         public void setSpatial(Spatial spatial){
         }
         

+ 13 - 1
jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java

@@ -46,13 +46,15 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 /**
  * PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank
  * @author normenhansen
  */
-public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener {
+public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener, JmeCloneable {
 
     protected Spatial spatial;
     protected boolean enabled = true;
@@ -99,6 +101,16 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro
         throw new UnsupportedOperationException("Not supported yet.");
     }
 
+    @Override   
+    public Object jmeClone() {
+        throw new UnsupportedOperationException("Not yet implemented.");
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) { 
+        throw new UnsupportedOperationException("Not yet implemented.");
+    }
+         
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         setUserObject(spatial);

+ 11 - 0
jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java

@@ -67,6 +67,8 @@ import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
 import com.jme3.util.SkyFactory;
 import com.jme3.util.TangentBinormalGenerator;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 public class TestPssmShadow extends SimpleApplication implements ActionListener {
@@ -249,6 +251,15 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
             time = 0;
         }
 
+        @Override   
+        public Object jmeClone() {
+            return null;
+        }     
+
+        @Override   
+        public void cloneFields( Cloner cloner, Object original ) { 
+        }
+             
         @Override
         protected void controlRender(RenderManager rm, ViewPort vp) {
         }

+ 9 - 0
jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java

@@ -40,6 +40,8 @@ import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 
 
@@ -67,6 +69,13 @@ public class NormalRecalcControl extends AbstractControl {
 
     }
 
+    @Override   
+    public Object jmeClone() {
+        NormalRecalcControl control = (NormalRecalcControl)super.jmeClone();
+        control.setEnabled(true);
+        return control; 
+    }     
+
     @Override
     public Control cloneForSpatial(Spatial spatial) {
         NormalRecalcControl control = new NormalRecalcControl(terrain);

+ 25 - 1
jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java

@@ -46,6 +46,8 @@ import com.jme3.scene.control.Control;
 import com.jme3.terrain.Terrain;
 import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
 import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -298,7 +300,29 @@ public class TerrainLodControl extends AbstractControl {
     
     
     
-    
+ 
+    @Override   
+    public Object jmeClone() {
+        if (spatial instanceof Terrain) {
+            TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras);
+            cloned.setLodCalculator(lodCalculator.clone());
+            cloned.spatial = spatial;
+            return cloned;
+        }
+        return null;
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.lodCalculator = cloner.clone(lodCalculator);
+ 
+        try {       
+            // Not deep clone of the cameras themselves
+            this.cameras = cloner.javaClone(cameras);
+        } catch( CloneNotSupportedException e ) {
+            throw new RuntimeException("Error cloning", e);
+        } 
+    }     
     
     
     @Override