浏览代码

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

Paul Speed 9 年之前
父节点
当前提交
8b1ddbe60f
共有 27 个文件被更改,包括 547 次插入24 次删除
  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