浏览代码

Merge branch 'master' into PBRisComing

Nehon 9 年之前
父节点
当前提交
754c256a66
共有 100 个文件被更改,包括 3956 次插入1276 次删除
  1. 1 1
      common.gradle
  2. 1 1
      jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java
  3. 10 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  4. 0 11
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  5. 23 20
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  6. 9 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java
  7. 12 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java
  8. 27 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
  9. 23 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java
  10. 15 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java
  11. 37 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
  12. 61 1
      jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
  13. 4 0
      jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java
  14. 31 1
      jme3-core/src/main/java/com/jme3/animation/AnimControl.java
  15. 30 1
      jme3-core/src/main/java/com/jme3/animation/Animation.java
  16. 24 1
      jme3-core/src/main/java/com/jme3/animation/AudioTrack.java
  17. 2 1
      jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java
  18. 34 0
      jme3-core/src/main/java/com/jme3/animation/EffectTrack.java
  19. 12 1
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  20. 27 1
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  21. 17 1
      jme3-core/src/main/java/com/jme3/animation/TrackInfo.java
  22. 22 9
      jme3-core/src/main/java/com/jme3/app/StatsView.java
  23. 14 12
      jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java
  24. 31 6
      jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java
  25. 18 1
      jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java
  26. 21 1
      jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
  27. 38 18
      jme3-core/src/main/java/com/jme3/light/LightList.java
  28. 32 3
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  29. 49 9
      jme3-core/src/main/java/com/jme3/material/RenderState.java
  30. 17 13
      jme3-core/src/main/java/com/jme3/math/Spline.java
  31. 4 2
      jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java
  32. 1 1
      jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
  33. 6 2
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  34. 15 2
      jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java
  35. 73 33
      jme3-core/src/main/java/com/jme3/scene/BatchNode.java
  36. 13 1
      jme3-core/src/main/java/com/jme3/scene/CameraNode.java
  37. 63 52
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  38. 15 3
      jme3-core/src/main/java/com/jme3/scene/LightNode.java
  39. 215 179
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  40. 79 65
      jme3-core/src/main/java/com/jme3/scene/Node.java
  41. 114 66
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  42. 17 1
      jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java
  43. 7 6
      jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java
  44. 8 7
      jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java
  45. 8 7
      jme3-core/src/main/java/com/jme3/scene/control/LightControl.java
  46. 13 2
      jme3-core/src/main/java/com/jme3/scene/control/LodControl.java
  47. 14 0
      jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java
  48. 68 57
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
  49. 94 41
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java
  50. 46 1
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java
  51. 85 20
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
  52. 2 0
      jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java
  53. 2 0
      jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java
  54. 48 11
      jme3-core/src/main/java/com/jme3/util/IntMap.java
  55. 81 0
      jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java
  56. 348 0
      jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
  57. 58 0
      jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java
  58. 99 0
      jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java
  59. 70 0
      jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java
  60. 7 10
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  61. 2 3
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag
  62. 2 4
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  63. 6 2
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  64. 14 4
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag
  65. 6 2
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md
  66. 27 7
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
  67. 0 80
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag
  68. 0 82
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert
  69. 31 2
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag
  70. 7 3
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md
  71. 42 8
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag
  72. 153 102
      jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib
  73. 0 242
      jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib
  74. 2 1
      jme3-core/src/main/resources/com/jme3/system/version.properties
  75. 21 4
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  76. 2 0
      jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java
  77. 2 1
      jme3-core/src/test/resources/texture-parameters-newstyle.j3m
  78. 1 1
      jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert
  79. 3 3
      jme3-examples/src/main/java/jme3test/TestChooser.java
  80. 367 0
      jme3-examples/src/main/java/jme3test/app/TestCloner.java
  81. 15 1
      jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java
  82. 1 1
      jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java
  83. 1 1
      jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
  84. 29 10
      jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
  85. 22 3
      jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java
  86. 12 0
      jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java
  87. 1 1
      jme3-examples/src/main/java/jme3test/light/TestSpotLight.java
  88. 1 6
      jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java
  89. 1 1
      jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java
  90. 0 1
      jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java
  91. 100 0
      jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java
  92. 6 6
      jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
  93. 14 0
      jme3-examples/src/main/java/jme3test/network/TestChatClient.java
  94. 20 1
      jme3-examples/src/main/java/jme3test/network/TestChatServer.java
  95. 87 0
      jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java
  96. 1 0
      jme3-plugins/build.gradle
  97. 78 0
      jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java
  98. 383 0
      jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java
  99. 82 0
      jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java
  100. 99 0
      jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java

+ 1 - 1
common.gradle

@@ -8,7 +8,7 @@ apply plugin: 'maven'
 group = 'org.jmonkeyengine'
 group = 'org.jmonkeyengine'
 version = jmePomVersion
 version = jmePomVersion
 
 
-sourceCompatibility = '1.6'
+sourceCompatibility = '1.7'
 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
 
 
 repositories {
 repositories {

+ 1 - 1
jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java

@@ -430,7 +430,7 @@ public class AndroidTouchInput implements TouchInput {
             return;
             return;
         }
         }
 
 
-        logger.log(Level.INFO, "event: {0}", event);
+        //logger.log(Level.INFO, "event: {0}", event);
 
 
         inputEventQueue.add(event);
         inputEventQueue.add(event);
         if (event instanceof TouchEvent) {
         if (event instanceof TouchEvent) {

+ 10 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java

@@ -215,8 +215,8 @@ public class Edge {
     
     
 	/**
 	/**
 	 * The method computes the crossing pint of this edge and another edge. If
 	 * The method computes the crossing pint of this edge and another edge. If
-	 * there is no crossing then null is returned. This method also allows to
-	 * get the crossing point of the straight lines that contain these edges if
+	 * there is no crossing then null is returned. Also null is returned if the edges are parallel.
+	 * This method also allows to get the crossing point of the straight lines that contain these edges if
 	 * you set the 'extend' parameter to true.
 	 * you set the 'extend' parameter to true.
 	 * 
 	 * 
 	 * @param edge
 	 * @param edge
@@ -227,7 +227,7 @@ public class Edge {
 	 * @param extendSecondEdge
 	 * @param extendSecondEdge
 	 *            set to <b>true</b> to find a crossing point along the whole
 	 *            set to <b>true</b> to find a crossing point along the whole
 	 *            straight that contains the given edge
 	 *            straight that contains the given edge
-	 * @return cross point on null if none exist
+	 * @return cross point on null if none exist or the edges are parallel
 	 */
 	 */
 	public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) {
 	public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) {
 		Vector3d P1 = new Vector3d(this.getFirstVertex());
 		Vector3d P1 = new Vector3d(this.getFirstVertex());
@@ -235,6 +235,11 @@ public class Edge {
 		Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal();
 		Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal();
 		Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal();
 		Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal();
 		
 		
+		if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) {
+			// the edges are parallel; do not care about the crossing point
+			return null;
+		}
+		
 		double t1 = 0, t2 = 0;
 		double t1 = 0, t2 = 0;
 		if(u.x == 0 && v.x == 0) {
 		if(u.x == 0 && v.x == 0) {
 			t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y);
 			t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y);
@@ -262,11 +267,11 @@ public class Edge {
 			// the lines cross, check if p1 and p2 are within the edges
 			// the lines cross, check if p1 and p2 are within the edges
             Vector3d p = p1.subtract(P1);
             Vector3d p = p1.subtract(P1);
             double cos = p.dot(u) / p.length();
             double cos = p.dot(u) / p.length();
-            if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= this.getLength()) {
+            if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) {
                 // p1 is inside the first edge, lets check the other edge now
                 // p1 is inside the first edge, lets check the other edge now
                 p = p2.subtract(P2);
                 p = p2.subtract(P2);
                 cos = p.dot(v) / p.length();
                 cos = p.dot(v) / p.length();
-                if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= edge.getLength()) {
+                if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) {
                 	return p1.toVector3f();
                 	return p1.toVector3f();
                 }
                 }
             }
             }

+ 0 - 11
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java

@@ -146,7 +146,6 @@ public class Face implements Comparator<Integer> {
     /**
     /**
      * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
      * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
      */
      */
-    @SuppressWarnings("unchecked")
     public List<List<Integer>> getCurrentIndexes() {
     public List<List<Integer>> getCurrentIndexes() {
         if (triangulatedFaces == null) {
         if (triangulatedFaces == null) {
             return Arrays.asList(indexes.getAll());
             return Arrays.asList(indexes.getAll());
@@ -279,16 +278,6 @@ public class Face implements Comparator<Integer> {
                 // two special cases will improve the computations speed
                 // two special cases will improve the computations speed
                 if(face.getIndexes().size() == 3) {
                 if(face.getIndexes().size() == 3) {
                 	triangulatedFaces.add(face.getIndexes().clone());
                 	triangulatedFaces.add(face.getIndexes().clone());
-                } else if(face.getIndexes().size() == 4) {
-                	// in case face has 4 verts we use the plain triangulation
-                	indexes[0] = face.getIndex(0);
-                    indexes[1] = face.getIndex(1);
-                    indexes[2] = face.getIndex(2);
-                	triangulatedFaces.add(new IndexesLoop(indexes));
-                	
-                    indexes[1] = face.getIndex(2);
-                    indexes[2] = face.getIndex(3);
-                	triangulatedFaces.add(new IndexesLoop(indexes));
                 } else {
                 } else {
                 	int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
                 	int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
                     while (face.vertexCount() > 0) {
                     while (face.vertexCount() > 0) {

+ 23 - 20
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java

@@ -286,30 +286,33 @@ public class MeshHelper extends AbstractBlenderHelper {
         List<Map<String, Float>> result = new ArrayList<Map<String, Float>>();
         List<Map<String, Float>> result = new ArrayList<Map<String, Float>>();
 
 
         Structure parent = blenderContext.peekParent();
         Structure parent = blenderContext.peekParent();
-        Structure defbase = (Structure) parent.getFieldValue("defbase");
-        List<String> groupNames = new ArrayList<String>();
-        List<Structure> defs = defbase.evaluateListBase();
-        for (Structure def : defs) {
-            groupNames.add(def.getFieldValue("name").toString());
-        }
+        if(parent != null) {
+        	// the mesh might be saved without its parent (it is then unused)
+        	Structure defbase = (Structure) parent.getFieldValue("defbase");
+            List<String> groupNames = new ArrayList<String>();
+            List<Structure> defs = defbase.evaluateListBase();
+            for (Structure def : defs) {
+                groupNames.add(def.getFieldValue("name").toString());
+            }
 
 
-        Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
-        if (pDvert.isNotNull()) {// assigning weights and bone indices
-            List<Structure> dverts = pDvert.fetchData();
-            for (Structure dvert : dverts) {
-                Map<String, Float> weightsForVertex = new HashMap<String, Float>();
-                Pointer pDW = (Pointer) dvert.getFieldValue("dw");
-                if (pDW.isNotNull()) {
-                    List<Structure> dw = pDW.fetchData();
-                    for (Structure deformWeight : dw) {
-                        int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
-                        float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
-                        String groupName = groupNames.get(groupIndex);
+            Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
+            if (pDvert.isNotNull()) {// assigning weights and bone indices
+                List<Structure> dverts = pDvert.fetchData();
+                for (Structure dvert : dverts) {
+                    Map<String, Float> weightsForVertex = new HashMap<String, Float>();
+                    Pointer pDW = (Pointer) dvert.getFieldValue("dw");
+                    if (pDW.isNotNull()) {
+                        List<Structure> dw = pDW.fetchData();
+                        for (Structure deformWeight : dw) {
+                            int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
+                            float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
+                            String groupName = groupNames.get(groupIndex);
 
 
-                        weightsForVertex.put(groupName, weight);
+                            weightsForVertex.put(groupName, weight);
+                        }
                     }
                     }
+                    result.add(weightsForVertex);
                 }
                 }
-                result.add(weightsForVertex);
             }
             }
         }
         }
         return result;
         return result;

+ 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.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -49,7 +51,7 @@ import java.io.IOException;
  *
  *
  * @author normenhansen
  * @author normenhansen
  */
  */
-public abstract class AbstractPhysicsControl implements PhysicsControl {
+public abstract class AbstractPhysicsControl implements PhysicsControl, JmeCloneable {
 
 
     private final Quaternion tmp_inverseWorldRotation = new Quaternion();
     private final Quaternion tmp_inverseWorldRotation = new Quaternion();
     protected Spatial spatial;
     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) {
     public void setSpatial(Spatial spatial) {
         if (this.spatial != null && this.spatial != spatial) {
         if (this.spatial != null && this.spatial != spatial) {
             removeSpatialData(this.spatial);
             removeSpatialData(this.spatial);

+ 12 - 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.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.List;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Level;
@@ -68,7 +70,7 @@ import java.util.logging.Logger;
  *
  *
  * @author normenhansen
  * @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 static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName());
     protected PhysicsRigidBody rigidBody;
     protected PhysicsRigidBody rigidBody;
@@ -663,12 +665,21 @@ public class BetterCharacterControl extends AbstractPhysicsControl implements Ph
         rigidBody.setUserObject(null);
         rigidBody.setUserObject(null);
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
         BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
         control.setJumpForce(jumpForce);
         control.setJumpForce(jumpForce);
         return control;
         return control;
     }
     }
 
 
+    @Override
+    public Object jmeClone() {
+        BetterCharacterControl control = new BetterCharacterControl(radius, height, mass);
+        control.setJumpForce(jumpForce);
+        control.spatial = this.spatial;
+        return control;
+    }     
+
     @Override
     @Override
     public void write(JmeExporter ex) throws IOException {
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         super.write(ex);

+ 27 - 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.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
  * You might want to try <code>BetterCharacterControl</code> as well.
  * You might want to try <code>BetterCharacterControl</code> as well.
  * @author normenhansen
  * @author normenhansen
  */
  */
-public class CharacterControl extends PhysicsCharacter implements PhysicsControl {
+public class CharacterControl extends PhysicsCharacter implements PhysicsControl, JmeCloneable {
 
 
     protected Spatial spatial;
     protected Spatial spatial;
     protected boolean enabled = true;
     protected boolean enabled = true;
@@ -87,6 +89,7 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
         return spatial.getWorldTranslation();
         return spatial.getWorldTranslation();
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         CharacterControl control = new CharacterControl(collisionShape, stepHeight);
         CharacterControl control = new CharacterControl(collisionShape, stepHeight);
         control.setCcdMotionThreshold(getCcdMotionThreshold());
         control.setCcdMotionThreshold(getCcdMotionThreshold());
@@ -103,6 +106,29 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl
         return control;
         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) {
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         this.spatial = spatial;
         setUserObject(spatial);
         setUserObject(spatial);

+ 23 - 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.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -51,7 +53,7 @@ import java.io.IOException;
  * overlaps with other physics objects (e.g. aggro radius).
  * overlaps with other physics objects (e.g. aggro radius).
  * @author normenhansen
  * @author normenhansen
  */
  */
-public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
+public class GhostControl extends PhysicsGhostObject implements PhysicsControl, JmeCloneable {
 
 
     protected Spatial spatial;
     protected Spatial spatial;
     protected boolean enabled = true;
     protected boolean enabled = true;
@@ -93,6 +95,7 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
         return spatial.getWorldRotation();
         return spatial.getWorldRotation();
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         GhostControl control = new GhostControl(collisionShape);
         GhostControl control = new GhostControl(collisionShape);
         control.setCcdMotionThreshold(getCcdMotionThreshold());
         control.setCcdMotionThreshold(getCcdMotionThreshold());
@@ -105,6 +108,25 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl {
         return control;
         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) {
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         this.spatial = spatial;
         setUserObject(spatial);
         setUserObject(spatial);

+ 15 - 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.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.*;
 import java.util.*;
 import java.util.logging.Level;
 import java.util.logging.Level;
@@ -92,7 +94,7 @@ import java.util.logging.Logger;
  *
  *
  * @author Normen Hansen and Rémy Bouquet (Nehon)
  * @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 static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName());
     protected List<RagdollCollisionListener> listeners;
     protected List<RagdollCollisionListener> listeners;
@@ -910,6 +912,7 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
     public void render(RenderManager rm, ViewPort vp) {
     public void render(RenderManager rm, ViewPort vp) {
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold);
         KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold);
         control.setMode(mode);
         control.setMode(mode);
@@ -919,6 +922,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P
         return control;
         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) {
     public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) {
         Vector3f target = worldPos.subtract(targetModel.getWorldTranslation());
         Vector3f target = worldPos.subtract(targetModel.getWorldTranslation());
         ikTargets.put(bone.getName(), target);
         ikTargets.put(bone.getName(), target);

+ 37 - 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.control.Control;
 import com.jme3.scene.shape.Box;
 import com.jme3.scene.shape.Box;
 import com.jme3.scene.shape.Sphere;
 import com.jme3.scene.shape.Sphere;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
  *
  *
  * @author normenhansen
  * @author normenhansen
  */
  */
-public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl {
+public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl, JmeCloneable {
 
 
     protected Spatial spatial;
     protected Spatial spatial;
     protected boolean enabled = true;
     protected boolean enabled = true;
@@ -89,6 +91,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
         super(shape, mass);
         super(shape, mass);
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         RigidBodyControl control = new RigidBodyControl(collisionShape, mass);
         RigidBodyControl control = new RigidBodyControl(collisionShape, mass);
         control.setAngularFactor(getAngularFactor());
         control.setAngularFactor(getAngularFactor());
@@ -115,6 +118,39 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl
         return control;
         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) {
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         this.spatial = spatial;
         setUserObject(spatial);
         setUserObject(spatial);

+ 61 - 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.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Iterator;
 import java.util.Iterator;
 
 
@@ -53,7 +55,7 @@ import java.util.Iterator;
  *
  *
  * @author normenhansen
  * @author normenhansen
  */
  */
-public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
+public class VehicleControl extends PhysicsVehicle implements PhysicsControl, JmeCloneable {
 
 
     protected Spatial spatial;
     protected Spatial spatial;
     protected boolean enabled = true;
     protected boolean enabled = true;
@@ -106,6 +108,7 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
         return spatial.getWorldRotation();
         return spatial.getWorldRotation();
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         VehicleControl control = new VehicleControl(collisionShape, mass);
         VehicleControl control = new VehicleControl(collisionShape, mass);
         control.setAngularFactor(getAngularFactor());
         control.setAngularFactor(getAngularFactor());
@@ -155,6 +158,63 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl {
         return control;
         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) {
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         this.spatial = spatial;
         setUserObject(spatial);
         setUserObject(spatial);

+ 4 - 0
jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java

@@ -70,6 +70,10 @@ public class ConeCollisionShape extends CollisionShape {
     public float getRadius() {
     public float getRadius() {
         return radius;
         return radius;
     }
     }
+    
+    public float getHeight() {
+        return height;
+    }
 
 
     public void write(JmeExporter ex) throws IOException {
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         super.write(ex);

+ 31 - 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.Spatial;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Map.Entry;
 
 
 /**
 /**
@@ -65,7 +68,7 @@ import java.util.Map.Entry;
  *
  *
  * @author Kirill Vainer
  * @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.
      * Skeleton object must contain corresponding data for the targets' weight buffers.
@@ -108,6 +111,7 @@ public final class AnimControl extends AbstractControl implements Cloneable {
     /**
     /**
      * Internal use only.
      * Internal use only.
      */
      */
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         try {
         try {
             AnimControl clone = (AnimControl) super.clone();
             AnimControl clone = (AnimControl) super.clone();
@@ -130,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>
      * @param animations Set the animations that this <code>AnimControl</code>
      * will be capable of playing. The animations should be compatible
      * 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.scene.Spatial;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -42,7 +44,7 @@ import java.io.IOException;
  * 
  * 
  * @author Kirill Vainer, Marcin Roguski (Kaelthas)
  * @author Kirill Vainer, Marcin Roguski (Kaelthas)
  */
  */
-public class Animation implements Savable, Cloneable {
+public class Animation implements Savable, Cloneable, JmeCloneable {
 
 
     /** 
     /** 
      * The name of the animation. 
      * 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
     @Override
     public String toString() {
     public String toString() {
         return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';
         return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';

+ 24 - 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.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
@@ -174,6 +176,7 @@ public class AudioTrack implements ClonableTrack {
      * @param spatial the Spatial holding the AnimControl
      * @param spatial the Spatial holding the AnimControl
      * @return the cloned Track with proper reference
      * @return the cloned Track with proper reference
      */
      */
+    @Override
     public Track cloneForSpatial(Spatial spatial) {
     public Track cloneForSpatial(Spatial spatial) {
         AudioTrack audioTrack = new AudioTrack();
         AudioTrack audioTrack = new AudioTrack();
         audioTrack.length = this.length;
         audioTrack.length = this.length;
@@ -192,7 +195,27 @@ public class AudioTrack implements ClonableTrack {
         return audioTrack;
         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
      * recursive function responsible for finding the newly cloned AudioNode
      *
      *
      * @param spat
      * @param spat

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

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

+ 34 - 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.AbstractControl;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 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
         @Override
         protected void controlRender(RenderManager rm, ViewPort vp) {
         protected void controlRender(RenderManager rm, ViewPort vp) {
         }
         }
@@ -263,6 +281,7 @@ public class EffectTrack implements ClonableTrack {
      * @param spatial the Spatial holding the AnimControl
      * @param spatial the Spatial holding the AnimControl
      * @return the cloned Track with proper reference
      * @return the cloned Track with proper reference
      */
      */
+    @Override
     public Track cloneForSpatial(Spatial spatial) {
     public Track cloneForSpatial(Spatial spatial) {
         EffectTrack effectTrack = new EffectTrack();
         EffectTrack effectTrack = new EffectTrack();
         effectTrack.particlesPerSeconds = this.particlesPerSeconds;
         effectTrack.particlesPerSeconds = this.particlesPerSeconds;
@@ -283,6 +302,21 @@ public class EffectTrack implements ClonableTrack {
         return effectTrack;
         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
      * 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.export.*;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Matrix4f;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.JmeCloneable;
+import com.jme3.util.clone.Cloner;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
@@ -45,7 +47,7 @@ import java.util.List;
  * 
  * 
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
-public final class Skeleton implements Savable {
+public final class Skeleton implements Savable, JmeCloneable {
 
 
     private Bone[] rootBones;
     private Bone[] rootBones;
     private Bone[] boneList;
     private Bone[] boneList;
@@ -118,6 +120,15 @@ public final class Skeleton implements Savable {
     public Skeleton() {
     public Skeleton() {
     }
     }
 
 
+    @Override   
+    public Object jmeClone() {
+        return new Skeleton(this);
+    }     
+
+    @Override   
+    public void cloneFields( Cloner cloner, Object original ) {
+    }
+
     private void createSkinningMatrices() {
     private void createSkinningMatrices() {
         skinningMatrixes = new Matrix4f[boneList.length];
         skinningMatrixes = new Matrix4f[boneList.length];
         for (int i = 0; i < skinningMatrixes.length; i++) {
         for (int i = 0; i < skinningMatrixes.length; i++) {

+ 27 - 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.shader.VarType;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteBuffer;
@@ -63,7 +65,7 @@ import java.util.logging.Logger;
  *
  *
  * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
  * @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.
      * The skeleton of the model.
@@ -345,6 +347,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
         }
         }
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         Node clonedNode = (Node) spatial;
         Node clonedNode = (Node) spatial;
         SkeletonControl clone = new SkeletonControl();
         SkeletonControl clone = new SkeletonControl();
@@ -385,6 +388,29 @@ public class SkeletonControl extends AbstractControl implements Cloneable {
         return clone;
         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
      * @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.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.Savable;
 import com.jme3.export.Savable;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.ArrayList;
 
 
@@ -48,7 +50,7 @@ import java.util.ArrayList;
  *
  *
  * @author Nehon
  * @author Nehon
  */
  */
-public class TrackInfo implements Savable {
+public class TrackInfo implements Savable, JmeCloneable {
 
 
     ArrayList<Track> tracks = new ArrayList<Track>();
     ArrayList<Track> tracks = new ArrayList<Track>();
 
 
@@ -72,4 +74,18 @@ public class TrackInfo implements Savable {
     public void addTrack(Track track) {
     public void addTrack(Track track) {
         tracks.add(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); 
+    }             
 }
 }

+ 22 - 9
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.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 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
  * 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/>
  * rootNode.attachChild(statsView);<br/>
  * </code>
  * </code>
  */
  */
-public class StatsView extends Node implements Control {
+public class StatsView extends Node implements Control, JmeCloneable {
 
 
     private BitmapText statText;
     private BitmapText statText;
     private Statistics statistics;
     private Statistics statistics;
@@ -67,7 +69,7 @@ public class StatsView extends Node implements Control {
     private int[] statData;
     private int[] statData;
 
 
     private boolean enabled = true;
     private boolean enabled = true;
-    
+
     private final StringBuilder stringBuilder = new StringBuilder();
     private final StringBuilder stringBuilder = new StringBuilder();
 
 
     public StatsView(String name, AssetManager manager, Statistics stats){
     public StatsView(String name, AssetManager manager, Statistics stats){
@@ -93,32 +95,43 @@ public class StatsView extends Node implements Control {
     public float getHeight() {
     public float getHeight() {
         return statText.getLineHeight() * statLabels.length;
         return statText.getLineHeight() * statLabels.length;
     }
     }
-    
+
     public void update(float tpf) {
     public void update(float tpf) {
-    
-        if (!isEnabled()) 
+
+        if (!isEnabled())
             return;
             return;
-            
+
         statistics.getData(statData);
         statistics.getData(statData);
         stringBuilder.setLength(0);
         stringBuilder.setLength(0);
-        
-        // Need to walk through it backwards, as the first label 
+
+        // Need to walk through it backwards, as the first label
         // should appear at the bottom, not the top.
         // should appear at the bottom, not the top.
         for (int i = statLabels.length - 1; i >= 0; i--) {
         for (int i = statLabels.length - 1; i >= 0; i--) {
             stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n');
             stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n');
         }
         }
         statText.setText(stringBuilder);
         statText.setText(stringBuilder);
-        
+
         // Moved to ResetStatsState to make sure it is
         // Moved to ResetStatsState to make sure it is
         // done even if there is no StatsView or the StatsView
         // done even if there is no StatsView or the StatsView
         // is disable.
         // is disable.
         //statistics.clearFrame();
         //statistics.clearFrame();
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         return (Control) spatial;
         return (Control) spatial;
     }
     }
 
 
+    @Override
+    public StatsView 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) {
     public void setSpatial(Spatial spatial) {
     }
     }
 
 

+ 14 - 12
jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java

@@ -249,21 +249,23 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen
             }
             }
             logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath());
             logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath());
 
 
-            OutputStream outStream = null;
             try {
             try {
-                outStream = new FileOutputStream(file);
-                JmeSystem.writeImageFile(outStream, "png", outBuf, width, height);
+                writeImageFile(file);
             } catch (IOException ex) {
             } catch (IOException ex) {
                 logger.log(Level.SEVERE, "Error while saving screenshot", ex);
                 logger.log(Level.SEVERE, "Error while saving screenshot", ex);
-            } finally {
-                if (outStream != null){
-                    try {
-                        outStream.close();
-                    } catch (IOException ex) {
-                        logger.log(Level.SEVERE, "Error while saving screenshot", ex);
-                    }
-                }
-            }
+            }                
         }
         }
     }
     }
+    
+    /**
+     *  Called by postFrame() once the screen has been captured to outBuf.
+     */
+    protected void writeImageFile( File file ) throws IOException {
+        OutputStream outStream = new FileOutputStream(file);
+        try {
+            JmeSystem.writeImageFile(outStream, "png", outBuf, width, height);
+        } finally {
+            outStream.close();
+        }
+    } 
 }
 }

+ 31 - 6
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.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -56,7 +58,7 @@ import java.io.IOException;
  *
  *
  * @author Nehon
  * @author Nehon
  */
  */
-public class MotionEvent extends AbstractCinematicEvent implements Control {
+public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable {
 
 
     protected Spatial spatial;
     protected Spatial spatial;
     protected int currentWayPoint;
     protected int currentWayPoint;
@@ -118,7 +120,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
      */
      */
     public MotionEvent(Spatial spatial, MotionPath path) {
     public MotionEvent(Spatial spatial, MotionPath path) {
         super();
         super();
-        this.spatial = spatial;
         spatial.addControl(this);
         spatial.addControl(this);
         this.path = path;
         this.path = path;
     }
     }
@@ -130,7 +131,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
      */
      */
     public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) {
     public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) {
         super(initialDuration);
         super(initialDuration);
-        this.spatial = spatial;
         spatial.addControl(this);
         spatial.addControl(this);
         this.path = path;
         this.path = path;
     }
     }
@@ -142,7 +142,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
      */
      */
     public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) {
     public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) {
         super();
         super();
-        this.spatial = spatial;
         spatial.addControl(this);
         spatial.addControl(this);
         this.path = path;
         this.path = path;
         this.loopMode = loopMode;
         this.loopMode = loopMode;
@@ -155,7 +154,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
      */
      */
     public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
     public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
         super(initialDuration);
         super(initialDuration);
-        this.spatial = spatial;
         spatial.addControl(this);
         spatial.addControl(this);
         this.path = path;
         this.path = path;
         this.loopMode = loopMode;
         this.loopMode = loopMode;
@@ -274,8 +272,10 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
      * @param spatial
      * @param spatial
      * @return
      * @return
      */
      */
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
-        MotionEvent control = new MotionEvent(spatial, path);
+        MotionEvent control = new MotionEvent();
+        control.setPath(path);
         control.playState = playState;
         control.playState = playState;
         control.currentWayPoint = currentWayPoint;
         control.currentWayPoint = currentWayPoint;
         control.currentValue = currentValue;
         control.currentValue = currentValue;
@@ -291,6 +291,31 @@ public class MotionEvent extends AbstractCinematicEvent implements Control {
         return 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
     @Override
     public void onPlay() {
     public void onPlay() {
         traveledDistance = 0;
         traveledDistance = 0;

+ 18 - 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.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -108,7 +110,7 @@ public class ParticleEmitter extends Geometry {
     private transient Vector3f temp = new Vector3f();
     private transient Vector3f temp = new Vector3f();
     private transient Vector3f lastPos;
     private transient Vector3f lastPos;
 
 
-    public static class ParticleEmitterControl implements Control {
+    public static class ParticleEmitterControl implements Control, JmeCloneable {
 
 
         ParticleEmitter parentEmitter;
         ParticleEmitter parentEmitter;
 
 
@@ -119,11 +121,26 @@ public class ParticleEmitter extends Geometry {
             this.parentEmitter = parentEmitter;
             this.parentEmitter = parentEmitter;
         }
         }
 
 
+        @Override
         public Control cloneForSpatial(Spatial spatial) {
         public Control cloneForSpatial(Spatial spatial) {
             return this; // WARNING: Sets wrong control on spatial. Will be
             return this; // WARNING: Sets wrong control on spatial. Will be
             // fixed automatically by ParticleEmitter.clone() method.
             // 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) {
         public void setSpatial(Spatial spatial) {
         }
         }
 
 

+ 21 - 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.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
  * A camera that follows a spatial and can turn around it by dragging the mouse
  * A camera that follows a spatial and can turn around it by dragging the mouse
  * @author nehon
  * @author nehon
  */
  */
-public class ChaseCamera implements ActionListener, AnalogListener, Control {
+public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable {
 
 
     protected Spatial target = null;
     protected Spatial target = null;
     protected float minVerticalRotation = 0.00f;
     protected float minVerticalRotation = 0.00f;
@@ -567,6 +569,7 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
      * @param spatial
      * @param spatial
      * @return
      * @return
      */
      */
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager);
         ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager);
         cc.setMaxDistance(getMaxDistance());
         cc.setMaxDistance(getMaxDistance());
@@ -574,6 +577,23 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control {
         return cc;
         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
      * Sets the spacial for the camera control, should only be used internally
      * @param spatial
      * @param spatial

+ 38 - 18
jme3-core/src/main/java/com/jme3/light/LightList.java

@@ -33,6 +33,8 @@ package com.jme3.light;
 
 
 import com.jme3.export.*;
 import com.jme3.export.*;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.SortUtil;
 import com.jme3.util.SortUtil;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.*;
 import java.util.*;
@@ -40,10 +42,10 @@ import java.util.*;
 /**
 /**
  * <code>LightList</code> is used internally by {@link Spatial}s to manage
  * <code>LightList</code> is used internally by {@link Spatial}s to manage
  * lights that are attached to them.
  * lights that are attached to them.
- * 
+ *
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
-public final class LightList implements Iterable<Light>, Savable, Cloneable {
+public final class LightList implements Iterable<Light>, Savable, Cloneable, JmeCloneable {
 
 
     private Light[] list, tlist;
     private Light[] list, tlist;
     private float[] distToOwner;
     private float[] distToOwner;
@@ -74,7 +76,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
     /**
     /**
      * Creates a <code>LightList</code> for the given {@link Spatial}.
      * Creates a <code>LightList</code> for the given {@link Spatial}.
-     * 
+     *
      * @param owner The spatial owner
      * @param owner The spatial owner
      */
      */
     public LightList(Spatial owner) {
     public LightList(Spatial owner) {
@@ -87,7 +89,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
     /**
     /**
      * Set the owner of the LightList. Only used for cloning.
      * Set the owner of the LightList. Only used for cloning.
-     * @param owner 
+     * @param owner
      */
      */
     public void setOwner(Spatial owner){
     public void setOwner(Spatial owner){
         this.owner = owner;
         this.owner = owner;
@@ -118,7 +120,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
     /**
     /**
      * Remove the light at the given index.
      * Remove the light at the given index.
-     * 
+     *
      * @param index
      * @param index
      */
      */
     public void remove(int index){
     public void remove(int index){
@@ -139,7 +141,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
     /**
     /**
      * Removes the given light from the LightList.
      * Removes the given light from the LightList.
-     * 
+     *
      * @param l the light to remove
      * @param l the light to remove
      */
      */
     public void remove(Light l){
     public void remove(Light l){
@@ -187,12 +189,12 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
     /**
     /**
      * Sorts the elements in the list according to their Comparator.
      * Sorts the elements in the list according to their Comparator.
-     * There are two reasons why lights should be resorted. 
-     * First, if the lights have moved, that means their distance to 
-     * the spatial changed. 
-     * Second, if the spatial itself moved, it means the distance from it to 
+     * There are two reasons why lights should be resorted.
+     * First, if the lights have moved, that means their distance to
+     * the spatial changed.
+     * Second, if the spatial itself moved, it means the distance from it to
      * the individual lights might have changed.
      * the individual lights might have changed.
-     * 
+     *
      *
      *
      * @param transformChanged Whether the spatial's transform has changed
      * @param transformChanged Whether the spatial's transform has changed
      */
      */
@@ -252,7 +254,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
                 list[p] = parent.list[i];
                 list[p] = parent.list[i];
                 distToOwner[p] = Float.NEGATIVE_INFINITY;
                 distToOwner[p] = Float.NEGATIVE_INFINITY;
             }
             }
-            
+
             listSize = local.listSize + parent.listSize;
             listSize = local.listSize + parent.listSize;
         }else{
         }else{
             listSize = local.listSize;
             listSize = local.listSize;
@@ -261,7 +263,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
     /**
     /**
      * Returns an iterator that can be used to iterate over this LightList.
      * Returns an iterator that can be used to iterate over this LightList.
-     * 
+     *
      * @return an iterator that can be used to iterate over this LightList.
      * @return an iterator that can be used to iterate over this LightList.
      */
      */
     public Iterator<Light> iterator() {
     public Iterator<Light> iterator() {
@@ -276,10 +278,10 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
             public Light next() {
             public Light next() {
                 if (!hasNext())
                 if (!hasNext())
                     throw new NoSuchElementException();
                     throw new NoSuchElementException();
-                
+
                 return list[index++];
                 return list[index++];
             }
             }
-            
+
             public void remove() {
             public void remove() {
                 LightList.this.remove(--index);
                 LightList.this.remove(--index);
             }
             }
@@ -290,7 +292,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
     public LightList clone(){
     public LightList clone(){
         try{
         try{
             LightList clone = (LightList) super.clone();
             LightList clone = (LightList) super.clone();
-            
+
             clone.owner = null;
             clone.owner = null;
             clone.list = list.clone();
             clone.list = list.clone();
             clone.distToOwner = distToOwner.clone();
             clone.distToOwner = distToOwner.clone();
@@ -302,6 +304,24 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
         }
         }
     }
     }
 
 
+    @Override
+    public LightList jmeClone() {
+        try{
+            LightList clone = (LightList)super.clone();
+            clone.tlist = null; // list used for sorting only
+            return clone;
+        }catch (CloneNotSupportedException ex){
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.owner = cloner.clone(owner);
+        this.list = cloner.clone(list);
+        this.distToOwner = cloner.clone(distToOwner);
+    }
+
     public void write(JmeExporter ex) throws IOException {
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
         OutputCapsule oc = ex.getCapsule(this);
 //        oc.write(owner, "owner", null);
 //        oc.write(owner, "owner", null);
@@ -319,7 +339,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
 
 
         List<Light> lights = ic.readSavableArrayList("lights", null);
         List<Light> lights = ic.readSavableArrayList("lights", null);
         listSize = lights.size();
         listSize = lights.size();
-        
+
         // NOTE: make sure the array has a length of at least 1
         // NOTE: make sure the array has a length of at least 1
         int arraySize = Math.max(DEFAULT_SIZE, listSize);
         int arraySize = Math.max(DEFAULT_SIZE, listSize);
         list = new Light[arraySize];
         list = new Light[arraySize];
@@ -328,7 +348,7 @@ public final class LightList implements Iterable<Light>, Savable, Cloneable {
         for (int i = 0; i < listSize; i++){
         for (int i = 0; i < listSize; i++){
             list[i] = lights.get(i);
             list[i] = lights.get(i);
         }
         }
-        
+
         Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
         Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
     }
     }
 
 

+ 32 - 3
jme3-core/src/main/java/com/jme3/material/MatParam.java

@@ -248,16 +248,45 @@ When arrays can be inserted in J3M files
                 if (texKey.isFlipY()) {
                 if (texKey.isFlipY()) {
                     ret += "Flip ";
                     ret += "Flip ";
                 }
                 }
-                if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) {
-                    ret += "Repeat ";
+
+                //Wrap mode
+                ret += getWrapMode(texVal, Texture.WrapAxis.S);
+                ret += getWrapMode(texVal, Texture.WrapAxis.T);
+                ret += getWrapMode(texVal, Texture.WrapAxis.R);
+
+                //Min and Mag filter
+                Texture.MinFilter def =  Texture.MinFilter.BilinearNoMipMaps;
+                if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){
+                    def = Texture.MinFilter.Trilinear;
+                }
+                if(texVal.getMinFilter() != def){
+                    ret += "Min" + texVal.getMinFilter().name()+ " ";
+                }
+
+                if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){
+                    ret += "Mag" + texVal.getMagFilter().name()+ " ";
                 }
                 }
 
 
-                return ret + texKey.getName();
+                return ret + "\"" + texKey.getName() + "\"";
             default:
             default:
                 return null; // parameter type not supported in J3M
                 return null; // parameter type not supported in J3M
         }
         }
     }
     }
 
 
+    private String getWrapMode(Texture texVal, Texture.WrapAxis axis) {
+        WrapMode mode = WrapMode.EdgeClamp;
+        try{
+            mode = texVal.getWrap(axis);
+        }catch (IllegalArgumentException e){
+            //this axis doesn't exist on the texture
+            return "";
+        }
+        if(mode != WrapMode.EdgeClamp){
+            return"Wrap"+ mode.name() + "_" + axis.name() + " ";
+        }
+        return "";
+    }
+
     @Override
     @Override
     public MatParam clone() {
     public MatParam clone() {
         try {
         try {

+ 49 - 9
jme3-core/src/main/java/com/jme3/material/RenderState.java

@@ -311,6 +311,8 @@ public class RenderState implements Cloneable, Savable {
     boolean applyPolyOffset = true;
     boolean applyPolyOffset = true;
     boolean stencilTest = false;
     boolean stencilTest = false;
     boolean applyStencilTest = false;
     boolean applyStencilTest = false;
+    float lineWidth = 1;
+    boolean applyLineWidth = false;
     TestFunction depthFunc = TestFunction.LessOrEqual;
     TestFunction depthFunc = TestFunction.LessOrEqual;
     //by default depth func will be applied anyway if depth test is applied
     //by default depth func will be applied anyway if depth test is applied
     boolean applyDepthFunc = false;    
     boolean applyDepthFunc = false;    
@@ -350,6 +352,9 @@ public class RenderState implements Cloneable, Savable {
         oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
         oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
         oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
         oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
         oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
         oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
+        oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
+        oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);
+        oc.write(lineWidth, "lineWidth", 1);
 
 
         // Only "additional render state" has them set to false by default
         // Only "additional render state" has them set to false by default
         oc.write(applyPointSprite, "applyPointSprite", true);
         oc.write(applyPointSprite, "applyPointSprite", true);
@@ -364,8 +369,7 @@ public class RenderState implements Cloneable, Savable {
         oc.write(applyPolyOffset, "applyPolyOffset", true);
         oc.write(applyPolyOffset, "applyPolyOffset", true);
         oc.write(applyDepthFunc, "applyDepthFunc", true);
         oc.write(applyDepthFunc, "applyDepthFunc", true);
         oc.write(applyAlphaFunc, "applyAlphaFunc", false);
         oc.write(applyAlphaFunc, "applyAlphaFunc", false);
-        oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
-        oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);  
+        oc.write(applyLineWidth, "applyLineWidth", true);
 
 
     }
     }
 
 
@@ -394,6 +398,8 @@ public class RenderState implements Cloneable, Savable {
         backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
         backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
         depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
         depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
         alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater);
         alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater);
+        lineWidth = ic.readFloat("lineWidth", 1);
+
 
 
         applyPointSprite = ic.readBoolean("applyPointSprite", true);
         applyPointSprite = ic.readBoolean("applyPointSprite", true);
         applyWireFrame = ic.readBoolean("applyWireFrame", true);
         applyWireFrame = ic.readBoolean("applyWireFrame", true);
@@ -407,6 +413,8 @@ public class RenderState implements Cloneable, Savable {
         applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
         applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
         applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
         applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
         applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false);
         applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false);
+        applyLineWidth = ic.readBoolean("applyLineWidth", true);
+
         
         
     }
     }
 
 
@@ -528,6 +536,10 @@ public class RenderState implements Cloneable, Savable {
             }
             }
         }
         }
 
 
+        if(lineWidth != rs.lineWidth){
+            return false;
+        }
+
         return true;
         return true;
     }
     }
 
 
@@ -803,8 +815,17 @@ public class RenderState implements Cloneable, Savable {
         this.alphaFunc = alphaFunc;
         this.alphaFunc = alphaFunc;
         cachedHashCode = -1;
         cachedHashCode = -1;
     }
     }
-    
-    
+
+    /**
+     * Sets the mesh line width.
+     * This is to use in conjunction with {@link #setWireframe(boolean)} or with a mesh in {@link Mesh.Mode#Lines} mode.
+     * @param lineWidth the line width.
+     */
+    public void setLineWidth(float lineWidth) {
+        this.lineWidth = lineWidth;
+        this.applyLineWidth = true;
+        cachedHashCode = -1;
+    }
 
 
     /**
     /**
      * Check if stencil test is enabled.
      * Check if stencil test is enabled.
@@ -1118,8 +1139,16 @@ public class RenderState implements Cloneable, Savable {
     public TestFunction getAlphaFunc() {
     public TestFunction getAlphaFunc() {
         return alphaFunc;
         return alphaFunc;
     }
     }
-    
-    
+
+    /**
+     * returns the wireframe line width
+     *
+     * @return the line width
+     */
+    public float getLineWidth() {
+        return lineWidth;
+    }
+
 
 
     public boolean isApplyAlphaFallOff() {
     public boolean isApplyAlphaFallOff() {
         return applyAlphaFallOff;
         return applyAlphaFallOff;
@@ -1168,8 +1197,10 @@ public class RenderState implements Cloneable, Savable {
     public boolean isApplyAlphaFunc() {
     public boolean isApplyAlphaFunc() {
         return applyAlphaFunc;
         return applyAlphaFunc;
     }
     }
-    
-    
+
+    public boolean isApplyLineWidth() {
+        return applyLineWidth;
+    }
 
 
     /**
     /**
      *
      *
@@ -1200,6 +1231,7 @@ public class RenderState implements Cloneable, Savable {
             hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
             hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
             hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
             hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
             hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
             hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
+            hash = 79 * hash + Float.floatToIntBits(this.lineWidth);
             cachedHashCode = hash;
             cachedHashCode = hash;
         }
         }
         return cachedHashCode;
         return cachedHashCode;
@@ -1324,6 +1356,11 @@ public class RenderState implements Cloneable, Savable {
             state.frontStencilFunction = frontStencilFunction;
             state.frontStencilFunction = frontStencilFunction;
             state.backStencilFunction = backStencilFunction;
             state.backStencilFunction = backStencilFunction;
         }
         }
+        if (additionalState.applyLineWidth) {
+            state.lineWidth = additionalState.lineWidth;
+        } else {
+            state.lineWidth = lineWidth;
+        }
         state.cachedHashCode = -1;
         state.cachedHashCode = -1;
         return state;
         return state;
     }
     }
@@ -1351,6 +1388,7 @@ public class RenderState implements Cloneable, Savable {
         backStencilFunction = state.backStencilFunction;
         backStencilFunction = state.backStencilFunction;
         depthFunc = state.depthFunc;
         depthFunc = state.depthFunc;
         alphaFunc = state.alphaFunc;
         alphaFunc = state.alphaFunc;
+        lineWidth = state.lineWidth;
 
 
         applyPointSprite = true;
         applyPointSprite = true;
         applyWireFrame =  true;
         applyWireFrame =  true;
@@ -1364,6 +1402,7 @@ public class RenderState implements Cloneable, Savable {
         applyPolyOffset =  true;
         applyPolyOffset =  true;
         applyDepthFunc =  true;
         applyDepthFunc =  true;
         applyAlphaFunc =  false;
         applyAlphaFunc =  false;
+        applyLineWidth = true;
     }
     }
 
 
     @Override
     @Override
@@ -1392,7 +1431,8 @@ public class RenderState implements Cloneable, Savable {
                 + "\noffsetEnabled=" + offsetEnabled
                 + "\noffsetEnabled=" + offsetEnabled
                 + "\napplyPolyOffset=" + applyPolyOffset
                 + "\napplyPolyOffset=" + applyPolyOffset
                 + "\noffsetFactor=" + offsetFactor
                 + "\noffsetFactor=" + offsetFactor
-                + "\noffsetUnits=" + offsetUnits      
+                + "\noffsetUnits=" + offsetUnits
+                + "\nlineWidth=" + lineWidth
                 + "\n]";
                 + "\n]";
     }
     }
 }
 }

+ 17 - 13
jme3-core/src/main/java/com/jme3/math/Spline.java

@@ -90,7 +90,7 @@ public class Spline implements Savable {
         type = splineType;
         type = splineType;
         this.curveTension = curveTension;
         this.curveTension = curveTension;
         this.cycle = cycle;
         this.cycle = cycle;
-        this.computeTotalLentgh();
+        this.computeTotalLength();
     }
     }
 
 
     /**
     /**
@@ -116,7 +116,7 @@ public class Spline implements Savable {
         this.controlPoints.addAll(controlPoints);
         this.controlPoints.addAll(controlPoints);
         this.curveTension = curveTension;
         this.curveTension = curveTension;
         this.cycle = cycle;
         this.cycle = cycle;
-        this.computeTotalLentgh();
+        this.computeTotalLength();
     }
     }
     
     
     /**
     /**
@@ -144,7 +144,7 @@ public class Spline implements Savable {
         	this.weights[i] = controlPoint.w;
         	this.weights[i] = controlPoint.w;
         }
         }
         CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
         CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
-        this.computeTotalLentgh();
+        this.computeTotalLength();
     }
     }
 
 
     private void initCatmullRomWayPoints(List<Vector3f> list) {
     private void initCatmullRomWayPoints(List<Vector3f> list) {
@@ -186,7 +186,7 @@ public class Spline implements Savable {
             controlPoints.add(controlPoints.get(0).clone());
             controlPoints.add(controlPoints.get(0).clone());
         }
         }
         if (controlPoints.size() > 1) {
         if (controlPoints.size() > 1) {
-            this.computeTotalLentgh();
+            this.computeTotalLength();
         }
         }
     }
     }
 
 
@@ -197,7 +197,7 @@ public class Spline implements Savable {
     public void removeControlPoint(Vector3f controlPoint) {
     public void removeControlPoint(Vector3f controlPoint) {
         controlPoints.remove(controlPoint);
         controlPoints.remove(controlPoint);
         if (controlPoints.size() > 1) {
         if (controlPoints.size() > 1) {
-            this.computeTotalLentgh();
+            this.computeTotalLength();
         }
         }
     }
     }
     
     
@@ -209,7 +209,7 @@ public class Spline implements Savable {
     /**
     /**
      * This method computes the total length of the curve.
      * This method computes the total length of the curve.
      */
      */
-    private void computeTotalLentgh() {
+    private void computeTotalLength() {
         totalLength = 0;
         totalLength = 0;
         float l = 0;
         float l = 0;
         if (segmentsLength == null) {
         if (segmentsLength == null) {
@@ -317,7 +317,7 @@ public class Spline implements Savable {
     public void setCurveTension(float curveTension) {
     public void setCurveTension(float curveTension) {
         this.curveTension = curveTension;
         this.curveTension = curveTension;
         if(type==SplineType.CatmullRom && !getControlPoints().isEmpty()) {            
         if(type==SplineType.CatmullRom && !getControlPoints().isEmpty()) {            
-        	this.computeTotalLentgh();
+        	this.computeTotalLength();
         }
         }
     }
     }
 
 
@@ -342,7 +342,7 @@ public class Spline implements Savable {
     				controlPoints.add(controlPoints.get(0));
     				controlPoints.add(controlPoints.get(0));
     			}
     			}
     			this.cycle = cycle;
     			this.cycle = cycle;
-    			this.computeTotalLentgh();
+    			this.computeTotalLength();
     		} else {
     		} else {
     			this.cycle = cycle;
     			this.cycle = cycle;
     		}
     		}
@@ -369,7 +369,7 @@ public class Spline implements Savable {
      */
      */
     public void setType(SplineType type) {
     public void setType(SplineType type) {
         this.type = type;
         this.type = type;
-        this.computeTotalLentgh();
+        this.computeTotalLength();
     }
     }
 
 
     /**
     /**
@@ -435,9 +435,13 @@ public class Spline implements Savable {
         OutputCapsule oc = ex.getCapsule(this);
         OutputCapsule oc = ex.getCapsule(this);
         oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
         oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
         oc.write(type, "type", SplineType.CatmullRom);
         oc.write(type, "type", SplineType.CatmullRom);
-        float list[] = new float[segmentsLength.size()];
-        for (int i = 0; i < segmentsLength.size(); i++) {
-            list[i] = segmentsLength.get(i);
+        
+        float list[] = null;
+        if (segmentsLength != null) {
+            list = new float[segmentsLength.size()];
+            for (int i = 0; i < segmentsLength.size(); i++) {
+                list[i] = segmentsLength.get(i);
+            }
         }
         }
         oc.write(list, "segmentsLength", null);
         oc.write(list, "segmentsLength", null);
 
 
@@ -454,7 +458,7 @@ public class Spline implements Savable {
     public void read(JmeImporter im) throws IOException {
     public void read(JmeImporter im) throws IOException {
         InputCapsule in = im.getCapsule(this);
         InputCapsule in = im.getCapsule(this);
 
 
-        controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("wayPoints", null);
+        controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("controlPoints", new ArrayList<Vector3f>()); /* Empty List as default, prevents null pointers */
         float list[] = in.readFloatArray("segmentsLength", null);
         float list[] = in.readFloatArray("segmentsLength", null);
         if (list != null) {
         if (list != null) {
             segmentsLength = new ArrayList<Float>();
             segmentsLength = new ArrayList<Float>();

+ 4 - 2
jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java

@@ -401,8 +401,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable {
             viewPort.getCamera().setViewPort(left, right, bottom, top);
             viewPort.getCamera().setViewPort(left, right, bottom, top);
             viewPort.setOutputFrameBuffer(outputBuffer);
             viewPort.setOutputFrameBuffer(outputBuffer);
             viewPort = null;
             viewPort = null;
-            
-            renderFrameBuffer.dispose();
+
+            if(renderFrameBuffer != null){
+                renderFrameBuffer.dispose();
+            }
             if(depthTexture!=null){
             if(depthTexture!=null){
                depthTexture.getImage().dispose();
                depthTexture.getImage().dispose();
             }
             }

+ 1 - 1
jme3-core/src/main/java/com/jme3/renderer/RenderContext.java

@@ -101,7 +101,7 @@ public class RenderContext {
     public float pointSize = 1;
     public float pointSize = 1;
     
     
     /**
     /**
-     * @see Mesh#setLineWidth(float) 
+     * @see RenderState#setLineWidth(float)
      */
      */
     public float lineWidth = 1;
     public float lineWidth = 1;
 
 

+ 6 - 2
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -779,6 +779,10 @@ public final class GLRenderer implements Renderer {
                 gl.glDisable(GL.GL_STENCIL_TEST);
                 gl.glDisable(GL.GL_STENCIL_TEST);
             }
             }
         }
         }
+        if (context.lineWidth != state.getLineWidth()) {
+            gl.glLineWidth(state.getLineWidth());
+            context.lineWidth = state.getLineWidth();
+        }
     }
     }
 
 
     private int convertStencilOperation(StencilOperation stencilOp) {
     private int convertStencilOperation(StencilOperation stencilOp) {
@@ -2681,8 +2685,8 @@ public final class GLRenderer implements Renderer {
             return;
             return;
         }
         }
 
 
-
-        if (context.lineWidth != mesh.getLineWidth()) {
+        //this is kept for backward compatibility.
+        if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) {
             gl.glLineWidth(mesh.getLineWidth());
             gl.glLineWidth(mesh.getLineWidth());
             context.lineWidth = mesh.getLineWidth();
             context.lineWidth = mesh.getLineWidth();
         }
         }

+ 15 - 2
jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java

@@ -39,6 +39,7 @@ import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.binary.BinaryImporter;
 import com.jme3.export.binary.BinaryImporter;
+import com.jme3.util.clone.Cloner;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.*;
 import java.util.*;
@@ -50,7 +51,7 @@ import java.util.logging.Logger;
  * The AssetLinkNode does not store its children when exported to file.
  * The AssetLinkNode does not store its children when exported to file.
  * Instead, you can add a list of AssetKeys that will be loaded and attached
  * Instead, you can add a list of AssetKeys that will be loaded and attached
  * when the AssetLinkNode is restored.
  * when the AssetLinkNode is restored.
- * 
+ *
  * @author normenhansen
  * @author normenhansen
  */
  */
 public class AssetLinkNode extends Node {
 public class AssetLinkNode extends Node {
@@ -70,6 +71,18 @@ public class AssetLinkNode extends Node {
         assetLoaderKeys.add(key);
         assetLoaderKeys.add(key);
     }
     }
 
 
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        // This is a change in behavior because the old version did not clone
+        // this list... changes to one clone would be reflected in all.
+        // I think that's probably undesirable. -pspeed
+        this.assetLoaderKeys = cloner.clone(assetLoaderKeys);
+        this.assetChildren = new HashMap<ModelKey, Spatial>();
+    }
+
     /**
     /**
      * Add a "linked" child. These are loaded from the assetManager when the
      * Add a "linked" child. These are loaded from the assetManager when the
      * AssetLinkNode is loaded from a binary file.
      * AssetLinkNode is loaded from a binary file.
@@ -166,7 +179,7 @@ public class AssetLinkNode extends Node {
                 children.add(child);
                 children.add(child);
                 assetChildren.put(modelKey, child);
                 assetChildren.put(modelKey, child);
             } else {
             } else {
-                Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", 
+                Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
                                                                     new Object[]{ modelKey, key });
                                                                     new Object[]{ modelKey, key });
             }
             }
         }
         }

+ 73 - 33
jme3-core/src/main/java/com/jme3/scene/BatchNode.java

@@ -48,6 +48,8 @@ import com.jme3.math.Vector3f;
 import com.jme3.scene.mesh.IndexBuffer;
 import com.jme3.scene.mesh.IndexBuffer;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 
 
 /**
 /**
  * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph.
  * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph.
@@ -60,7 +62,7 @@ import com.jme3.util.TempVars;
  * Sub geoms can be removed but it may be slower than the normal spatial removing
  * Sub geoms can be removed but it may be slower than the normal spatial removing
  * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
  * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries.
  * To integrate them in the batch you have to call the batch() method again on the batchNode.
  * To integrate them in the batch you have to call the batch() method again on the batchNode.
- * 
+ *
  * TODO normal or tangents or both looks a bit weird
  * TODO normal or tangents or both looks a bit weird
  * TODO more automagic (batch when needed in the updateLogicalState)
  * TODO more automagic (batch when needed in the updateLogicalState)
  * @author Nehon
  * @author Nehon
@@ -77,7 +79,7 @@ public class BatchNode extends GeometryGroupNode {
      */
      */
     protected Map<Geometry, Batch> batchesByGeom = new HashMap<Geometry, Batch>();
     protected Map<Geometry, Batch> batchesByGeom = new HashMap<Geometry, Batch>();
     /**
     /**
-     * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer 
+     * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
      */
      */
     private float[] tmpFloat;
     private float[] tmpFloat;
     private float[] tmpFloatN;
     private float[] tmpFloatN;
@@ -96,7 +98,7 @@ public class BatchNode extends GeometryGroupNode {
     public BatchNode(String name) {
     public BatchNode(String name) {
         super(name);
         super(name);
     }
     }
-    
+
     @Override
     @Override
     public void onTransformChange(Geometry geom) {
     public void onTransformChange(Geometry geom) {
         updateSubBatch(geom);
         updateSubBatch(geom);
@@ -123,7 +125,7 @@ public class BatchNode extends GeometryGroupNode {
     protected Matrix4f getTransformMatrix(Geometry g){
     protected Matrix4f getTransformMatrix(Geometry g){
         return g.cachedWorldMat;
         return g.cachedWorldMat;
     }
     }
-    
+
     protected void updateSubBatch(Geometry bg) {
     protected void updateSubBatch(Geometry bg) {
         Batch batch = batchesByGeom.get(bg);
         Batch batch = batchesByGeom.get(bg);
         if (batch != null) {
         if (batch != null) {
@@ -134,13 +136,13 @@ public class BatchNode extends GeometryGroupNode {
             FloatBuffer posBuf = (FloatBuffer) pvb.getData();
             FloatBuffer posBuf = (FloatBuffer) pvb.getData();
             VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
             VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
             FloatBuffer normBuf = (FloatBuffer) nvb.getData();
             FloatBuffer normBuf = (FloatBuffer) nvb.getData();
-          
+
             VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
             VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position);
             FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
             FloatBuffer oposBuf = (FloatBuffer) opvb.getData();
             VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
             VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal);
             FloatBuffer onormBuf = (FloatBuffer) onvb.getData();
             FloatBuffer onormBuf = (FloatBuffer) onvb.getData();
             Matrix4f transformMat = getTransformMatrix(bg);
             Matrix4f transformMat = getTransformMatrix(bg);
-            
+
             if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
             if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
 
 
                 VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
                 VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
@@ -184,12 +186,12 @@ public class BatchNode extends GeometryGroupNode {
             }
             }
             batches.clear();
             batches.clear();
             batchesByGeom.clear();
             batchesByGeom.clear();
-        }        
+        }
         //only reset maxVertCount if there is something new to batch
         //only reset maxVertCount if there is something new to batch
         if (matMap.size() > 0) {
         if (matMap.size() > 0) {
             maxVertCount = 0;
             maxVertCount = 0;
         }
         }
-        
+
         for (Map.Entry<Material, List<Geometry>> entry : matMap.entrySet()) {
         for (Map.Entry<Material, List<Geometry>> entry : matMap.entrySet()) {
             Mesh m = new Mesh();
             Mesh m = new Mesh();
             Material material = entry.getKey();
             Material material = entry.getKey();
@@ -255,7 +257,7 @@ public class BatchNode extends GeometryGroupNode {
 
 
     /**
     /**
      * recursively visit the subgraph and unbatch geometries
      * recursively visit the subgraph and unbatch geometries
-     * @param s 
+     * @param s
      */
      */
     private void unbatchSubGraph(Spatial s) {
     private void unbatchSubGraph(Spatial s) {
         if (s instanceof Node) {
         if (s instanceof Node) {
@@ -269,8 +271,8 @@ public class BatchNode extends GeometryGroupNode {
             }
             }
         }
         }
     }
     }
-    
-    
+
+
     private void gatherGeometries(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
     private void gatherGeometries(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
 
 
         if (n instanceof Geometry) {
         if (n instanceof Geometry) {
@@ -283,7 +285,7 @@ public class BatchNode extends GeometryGroupNode {
                     }
                     }
                     List<Geometry> list = map.get(g.getMaterial());
                     List<Geometry> list = map.get(g.getMaterial());
                     if (list == null) {
                     if (list == null) {
-                        //trying to compare materials with the isEqual method 
+                        //trying to compare materials with the isEqual method
                         for (Map.Entry<Material, List<Geometry>> mat : map.entrySet()) {
                         for (Map.Entry<Material, List<Geometry>> mat : map.entrySet()) {
                             if (g.getMaterial().contentEquals(mat.getKey())) {
                             if (g.getMaterial().contentEquals(mat.getKey())) {
                                 list = mat.getValue();
                                 list = mat.getValue();
@@ -331,7 +333,7 @@ public class BatchNode extends GeometryGroupNode {
     /**
     /**
      * Sets the material to the all the batches of this BatchNode
      * Sets the material to the all the batches of this BatchNode
      * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
      * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
-     * 
+     *
      * @param material the material to use for this geometry
      * @param material the material to use for this geometry
      */
      */
     @Override
     @Override
@@ -341,12 +343,12 @@ public class BatchNode extends GeometryGroupNode {
 
 
     /**
     /**
      * Returns the material that is used for the first batch of this BatchNode
      * Returns the material that is used for the first batch of this BatchNode
-     * 
+     *
      * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
      * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
-     * 
+     *
      * @return the material that is used for the first batch of this BatchNode
      * @return the material that is used for the first batch of this BatchNode
-     * 
-     * @see #setMaterial(com.jme3.material.Material) 
+     *
+     * @see #setMaterial(com.jme3.material.Material)
      */
      */
     public Material getMaterial() {
     public Material getMaterial() {
         if (!batches.isEmpty()) {
         if (!batches.isEmpty()) {
@@ -359,7 +361,7 @@ public class BatchNode extends GeometryGroupNode {
     /**
     /**
      * Merges all geometries in the collection into
      * Merges all geometries in the collection into
      * the output mesh. Does not take into account materials.
      * the output mesh. Does not take into account materials.
-     * 
+     *
      * @param geometries
      * @param geometries
      * @param outMesh
      * @param outMesh
      */
      */
@@ -383,7 +385,7 @@ public class BatchNode extends GeometryGroupNode {
                 maxVertCount = geom.getVertexCount();
                 maxVertCount = geom.getVertexCount();
             }
             }
             Mesh.Mode listMode;
             Mesh.Mode listMode;
-            float listLineWidth = 1f;
+            //float listLineWidth = 1f;
             int components;
             int components;
             switch (geom.getMesh().getMode()) {
             switch (geom.getMesh().getMode()) {
                 case Points:
                 case Points:
@@ -394,7 +396,7 @@ public class BatchNode extends GeometryGroupNode {
                 case LineStrip:
                 case LineStrip:
                 case Lines:
                 case Lines:
                     listMode = Mesh.Mode.Lines;
                     listMode = Mesh.Mode.Lines;
-                    listLineWidth = geom.getMesh().getLineWidth();
+                    //listLineWidth = geom.getMesh().getLineWidth();
                     components = 2;
                     components = 2;
                     break;
                     break;
                 case TriangleFan:
                 case TriangleFan:
@@ -418,7 +420,7 @@ public class BatchNode extends GeometryGroupNode {
                 formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat();
                 formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat();
                 normForBuf[vb.getBufferType().ordinal()] = vb.isNormalized();
                 normForBuf[vb.getBufferType().ordinal()] = vb.isNormalized();
             }
             }
-            
+
             maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights());
             maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights());
 
 
             if (mode != null && mode != listMode) {
             if (mode != null && mode != listMode) {
@@ -426,19 +428,20 @@ public class BatchNode extends GeometryGroupNode {
                         + " primitive types: " + mode + " != " + listMode);
                         + " primitive types: " + mode + " != " + listMode);
             }
             }
             mode = listMode;
             mode = listMode;
-            if (mode == Mesh.Mode.Lines) {
-                if (lineWidth != 1f && listLineWidth != lineWidth) {
-                    throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
-                            + lineWidth + " != " + listLineWidth);
-                }
-                lineWidth = listLineWidth;
-            }
+            //Not needed anymore as lineWidth is now in RenderState and will be taken into account when merging according to the material
+//            if (mode == Mesh.Mode.Lines) {
+//                if (lineWidth != 1f && listLineWidth != lineWidth) {
+//                    throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width "
+//                            + lineWidth + " != " + listLineWidth);
+//                }
+//                lineWidth = listLineWidth;
+//            }
             compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
             compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
         }
         }
 
 
         outMesh.setMaxNumWeights(maxWeights);
         outMesh.setMaxNumWeights(maxWeights);
         outMesh.setMode(mode);
         outMesh.setMode(mode);
-        outMesh.setLineWidth(lineWidth);
+        //outMesh.setLineWidth(lineWidth);
         if (totalVerts >= 65536) {
         if (totalVerts >= 65536) {
             // make sure we create an UnsignedInt buffer so we can fit all of the meshes
             // make sure we create an UnsignedInt buffer so we can fit all of the meshes
             formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
             formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
@@ -585,7 +588,7 @@ public class BatchNode extends GeometryGroupNode {
         int offset = start * 3;
         int offset = start * 3;
         int tanOffset = start * 4;
         int tanOffset = start * 4;
 
 
-        
+
         bindBufPos.rewind();
         bindBufPos.rewind();
         bindBufNorm.rewind();
         bindBufNorm.rewind();
         bindBufTangents.rewind();
         bindBufTangents.rewind();
@@ -661,10 +664,10 @@ public class BatchNode extends GeometryGroupNode {
         vars.release();
         vars.release();
     }
     }
 
 
-    protected class Batch {
+    protected class Batch implements JmeCloneable {
         /**
         /**
          * update the batchesByGeom map for this batch with the given List of geometries
          * update the batchesByGeom map for this batch with the given List of geometries
-         * @param list 
+         * @param list
          */
          */
         void updateGeomList(List<Geometry> list) {
         void updateGeomList(List<Geometry> list) {
             for (Geometry geom : list) {
             for (Geometry geom : list) {
@@ -674,6 +677,25 @@ public class BatchNode extends GeometryGroupNode {
             }
             }
         }
         }
         Geometry geometry;
         Geometry geometry;
+
+        public final Geometry getGeometry() {
+            return geometry;
+        }
+
+        @Override
+        public Batch jmeClone() {
+            try {
+                return (Batch)super.clone();
+            } catch (CloneNotSupportedException ex) {
+                throw new AssertionError();
+            }
+        }
+
+        @Override
+        public void cloneFields( Cloner cloner, Object original ) {
+            this.geometry = cloner.clone(geometry);
+        }
+
     }
     }
 
 
     protected void setNeedsFullRebatch(boolean needsFullRebatch) {
     protected void setNeedsFullRebatch(boolean needsFullRebatch) {
@@ -699,7 +721,25 @@ public class BatchNode extends GeometryGroupNode {
         }
         }
         return clone;
         return clone;
     }
     }
-    
+
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.batches = cloner.clone(batches);
+        this.tmpFloat = cloner.clone(tmpFloat);
+        this.tmpFloatN = cloner.clone(tmpFloatN);
+        this.tmpFloatT = cloner.clone(tmpFloatT);
+
+
+        HashMap<Geometry, Batch> newBatchesByGeom = new HashMap<Geometry, Batch>();
+        for( Map.Entry<Geometry, Batch> e : batchesByGeom.entrySet() ) {
+            newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
+        }
+        this.batchesByGeom = newBatchesByGeom;
+    }
+
     @Override
     @Override
     public int collideWith(Collidable other, CollisionResults results) {
     public int collideWith(Collidable other, CollisionResults results) {
         int total = 0;
         int total = 0;

+ 13 - 1
jme3-core/src/main/java/com/jme3/scene/CameraNode.java

@@ -36,6 +36,7 @@ import com.jme3.export.JmeImporter;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.Camera;
 import com.jme3.scene.control.CameraControl;
 import com.jme3.scene.control.CameraControl;
 import com.jme3.scene.control.CameraControl.ControlDirection;
 import com.jme3.scene.control.CameraControl.ControlDirection;
+import com.jme3.util.clone.Cloner;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -93,7 +94,18 @@ public class CameraNode extends Node {
 //        this.lookAt(position, upVector);
 //        this.lookAt(position, upVector);
 //        camControl.getCamera().lookAt(position, upVector);
 //        camControl.getCamera().lookAt(position, upVector);
 //    }
 //    }
-    
+
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        // A change in behavior... I think previously CameraNode was probably
+        // not really cloneable... or at least its camControl would be pointing
+        // to the wrong control. -pspeed
+        this.camControl = cloner.clone(camControl);
+    }
+
     @Override
     @Override
     public void read(JmeImporter im) throws IOException {
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         super.read(im);

+ 63 - 52
jme3-core/src/main/java/com/jme3/scene/Geometry.java

@@ -43,6 +43,7 @@ import com.jme3.material.Material;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Matrix4f;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.Camera;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.clone.Cloner;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Queue;
 import java.util.Queue;
@@ -54,12 +55,12 @@ import java.util.logging.Logger;
  * contains the geometric data for rendering objects. It manages all rendering
  * contains the geometric data for rendering objects. It manages all rendering
  * information such as a {@link Material} object to define how the surface
  * information such as a {@link Material} object to define how the surface
  * should be shaded and the {@link Mesh} data to contain the actual geometry.
  * should be shaded and the {@link Mesh} data to contain the actual geometry.
- * 
+ *
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
 public class Geometry extends Spatial {
 public class Geometry extends Spatial {
 
 
-    // Version #1: removed shared meshes. 
+    // Version #1: removed shared meshes.
     // models loaded with shared mesh will be automatically fixed.
     // models loaded with shared mesh will be automatically fixed.
     public static final int SAVABLE_VERSION = 1;
     public static final int SAVABLE_VERSION = 1;
     private static final Logger logger = Logger.getLogger(Geometry.class.getName());
     private static final Logger logger = Logger.getLogger(Geometry.class.getName());
@@ -71,19 +72,19 @@ public class Geometry extends Spatial {
      */
      */
     protected boolean ignoreTransform = false;
     protected boolean ignoreTransform = false;
     protected transient Matrix4f cachedWorldMat = new Matrix4f();
     protected transient Matrix4f cachedWorldMat = new Matrix4f();
-    
+
     /**
     /**
      * Specifies which {@link GeometryGroupNode} this <code>Geometry</code>
      * Specifies which {@link GeometryGroupNode} this <code>Geometry</code>
      * is managed by.
      * is managed by.
      */
      */
     protected GeometryGroupNode groupNode;
     protected GeometryGroupNode groupNode;
-    
+
     /**
     /**
-     * The start index of this <code>Geometry's</code> inside 
+     * The start index of this <code>Geometry's</code> inside
      * the {@link GeometryGroupNode}.
      * the {@link GeometryGroupNode}.
      */
      */
     protected int startIndex = -1;
     protected int startIndex = -1;
-        
+
     /**
     /**
      * Serialization only. Do not use.
      * Serialization only. Do not use.
      */
      */
@@ -95,37 +96,37 @@ public class Geometry extends Spatial {
      * Create a geometry node without any mesh data.
      * Create a geometry node without any mesh data.
      * Both the mesh and the material are null, the geometry
      * Both the mesh and the material are null, the geometry
      * cannot be rendered until those are set.
      * cannot be rendered until those are set.
-     * 
+     *
      * @param name The name of this geometry
      * @param name The name of this geometry
      */
      */
     public Geometry(String name) {
     public Geometry(String name) {
         super(name);
         super(name);
-        
+
         // For backwards compatibility, only clear the "requires
         // For backwards compatibility, only clear the "requires
         // update" flag if we are not a subclass of Geometry.
         // update" flag if we are not a subclass of Geometry.
         // This prevents subclass from silently failing to receive
         // This prevents subclass from silently failing to receive
         // updates when they upgrade.
         // updates when they upgrade.
-        setRequiresUpdates(Geometry.class != getClass()); 
+        setRequiresUpdates(Geometry.class != getClass());
     }
     }
 
 
     /**
     /**
      * Create a geometry node with mesh data.
      * Create a geometry node with mesh data.
      * The material of the geometry is null, it cannot
      * The material of the geometry is null, it cannot
      * be rendered until it is set.
      * be rendered until it is set.
-     * 
+     *
      * @param name The name of this geometry
      * @param name The name of this geometry
      * @param mesh The mesh data for this geometry
      * @param mesh The mesh data for this geometry
      */
      */
     public Geometry(String name, Mesh mesh) {
     public Geometry(String name, Mesh mesh) {
         this(name);
         this(name);
-        
+
         if (mesh == null) {
         if (mesh == null) {
             throw new IllegalArgumentException("mesh cannot be null");
             throw new IllegalArgumentException("mesh cannot be null");
         }
         }
 
 
         this.mesh = mesh;
         this.mesh = mesh;
     }
     }
-    
+
     @Override
     @Override
     public boolean checkCulling(Camera cam) {
     public boolean checkCulling(Camera cam) {
         if (isGrouped()) {
         if (isGrouped()) {
@@ -137,8 +138,8 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * @return If ignoreTransform mode is set.
      * @return If ignoreTransform mode is set.
-     * 
-     * @see Geometry#setIgnoreTransform(boolean) 
+     *
+     * @see Geometry#setIgnoreTransform(boolean)
      */
      */
     public boolean isIgnoreTransform() {
     public boolean isIgnoreTransform() {
         return ignoreTransform;
         return ignoreTransform;
@@ -156,7 +157,7 @@ public class Geometry extends Spatial {
      * Level 0 indicates that the default index buffer should be used,
      * Level 0 indicates that the default index buffer should be used,
      * levels [1, LodLevels + 1] represent the levels set on the mesh
      * levels [1, LodLevels + 1] represent the levels set on the mesh
      * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
      * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
-     * 
+     *
      * @param lod The lod level to set
      * @param lod The lod level to set
      */
      */
     @Override
     @Override
@@ -170,7 +171,7 @@ public class Geometry extends Spatial {
         }
         }
 
 
         lodLevel = lod;
         lodLevel = lod;
-        
+
         if (isGrouped()) {
         if (isGrouped()) {
             groupNode.onMeshChange(this);
             groupNode.onMeshChange(this);
         }
         }
@@ -178,7 +179,7 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Returns the LOD level set with {@link #setLodLevel(int) }.
      * Returns the LOD level set with {@link #setLodLevel(int) }.
-     * 
+     *
      * @return the LOD level set
      * @return the LOD level set
      */
      */
     public int getLodLevel() {
     public int getLodLevel() {
@@ -187,10 +188,10 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Returns this geometry's mesh vertex count.
      * Returns this geometry's mesh vertex count.
-     * 
+     *
      * @return this geometry's mesh vertex count.
      * @return this geometry's mesh vertex count.
-     * 
-     * @see Mesh#getVertexCount() 
+     *
+     * @see Mesh#getVertexCount()
      */
      */
     public int getVertexCount() {
     public int getVertexCount() {
         return mesh.getVertexCount();
         return mesh.getVertexCount();
@@ -198,10 +199,10 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Returns this geometry's mesh triangle count.
      * Returns this geometry's mesh triangle count.
-     * 
+     *
      * @return this geometry's mesh triangle count.
      * @return this geometry's mesh triangle count.
-     * 
-     * @see Mesh#getTriangleCount() 
+     *
+     * @see Mesh#getTriangleCount()
      */
      */
     public int getTriangleCount() {
     public int getTriangleCount() {
         return mesh.getTriangleCount();
         return mesh.getTriangleCount();
@@ -209,9 +210,9 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Sets the mesh to use for this geometry when rendering.
      * Sets the mesh to use for this geometry when rendering.
-     * 
+     *
      * @param mesh the mesh to use for this geometry
      * @param mesh the mesh to use for this geometry
-     * 
+     *
      * @throws IllegalArgumentException If mesh is null
      * @throws IllegalArgumentException If mesh is null
      */
      */
     public void setMesh(Mesh mesh) {
     public void setMesh(Mesh mesh) {
@@ -221,7 +222,7 @@ public class Geometry extends Spatial {
 
 
         this.mesh = mesh;
         this.mesh = mesh;
         setBoundRefresh();
         setBoundRefresh();
-        
+
         if (isGrouped()) {
         if (isGrouped()) {
             groupNode.onMeshChange(this);
             groupNode.onMeshChange(this);
         }
         }
@@ -229,10 +230,10 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Returns the mesh to use for this geometry
      * Returns the mesh to use for this geometry
-     * 
+     *
      * @return the mesh to use for this geometry
      * @return the mesh to use for this geometry
-     * 
-     * @see #setMesh(com.jme3.scene.Mesh) 
+     *
+     * @see #setMesh(com.jme3.scene.Mesh)
      */
      */
     public Mesh getMesh() {
     public Mesh getMesh() {
         return mesh;
         return mesh;
@@ -240,13 +241,13 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Sets the material to use for this geometry.
      * Sets the material to use for this geometry.
-     * 
+     *
      * @param material the material to use for this geometry
      * @param material the material to use for this geometry
      */
      */
     @Override
     @Override
     public void setMaterial(Material material) {
     public void setMaterial(Material material) {
         this.material = material;
         this.material = material;
-        
+
         if (isGrouped()) {
         if (isGrouped()) {
             groupNode.onMaterialChange(this);
             groupNode.onMaterialChange(this);
         }
         }
@@ -254,10 +255,10 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Returns the material that is used for this geometry.
      * Returns the material that is used for this geometry.
-     * 
+     *
      * @return the material that is used for this geometry
      * @return the material that is used for this geometry
-     * 
-     * @see #setMaterial(com.jme3.material.Material) 
+     *
+     * @see #setMaterial(com.jme3.material.Material)
      */
      */
     public Material getMaterial() {
     public Material getMaterial() {
         return material;
         return material;
@@ -310,9 +311,9 @@ public class Geometry extends Spatial {
         computeWorldMatrix();
         computeWorldMatrix();
 
 
         if (isGrouped()) {
         if (isGrouped()) {
-            groupNode.onTransformChange(this);   
+            groupNode.onTransformChange(this);
         }
         }
-        
+
         // geometry requires lights to be sorted
         // geometry requires lights to be sorted
         worldLights.sort(true);
         worldLights.sort(true);
     }
     }
@@ -326,9 +327,9 @@ public class Geometry extends Spatial {
 
 
     /**
     /**
      * Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
      * Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
-     * 
+     *
      * Should only be called by the parent {@link GeometryGroupNode}.
      * Should only be called by the parent {@link GeometryGroupNode}.
-     * 
+     *
      * @param node Which {@link GeometryGroupNode} to associate with.
      * @param node Which {@link GeometryGroupNode} to associate with.
      * @param startIndex The starting index of this geometry in the group.
      * @param startIndex The starting index of this geometry in the group.
      */
      */
@@ -336,26 +337,26 @@ public class Geometry extends Spatial {
         if (isGrouped()) {
         if (isGrouped()) {
             unassociateFromGroupNode();
             unassociateFromGroupNode();
         }
         }
-        
+
         this.groupNode = node;
         this.groupNode = node;
         this.startIndex = startIndex;
         this.startIndex = startIndex;
     }
     }
 
 
     /**
     /**
-     * Removes the {@link GeometryGroupNode} association from this 
+     * Removes the {@link GeometryGroupNode} association from this
      * <code>Geometry</code>.
      * <code>Geometry</code>.
-     * 
+     *
      * Should only be called by the parent {@link GeometryGroupNode}.
      * Should only be called by the parent {@link GeometryGroupNode}.
      */
      */
     public void unassociateFromGroupNode() {
     public void unassociateFromGroupNode() {
         if (groupNode != null) {
         if (groupNode != null) {
-            // Once the geometry is removed 
+            // Once the geometry is removed
             // from the parent, the group node needs to be updated.
             // from the parent, the group node needs to be updated.
             groupNode.onGeometryUnassociated(this);
             groupNode.onGeometryUnassociated(this);
             groupNode = null;
             groupNode = null;
-            
+
             // change the default to -1 to make error detection easier
             // change the default to -1 to make error detection easier
-            startIndex = -1; 
+            startIndex = -1;
         }
         }
     }
     }
 
 
@@ -367,7 +368,7 @@ public class Geometry extends Spatial {
     @Override
     @Override
     protected void setParent(Node parent) {
     protected void setParent(Node parent) {
         super.setParent(parent);
         super.setParent(parent);
-        
+
         // If the geometry is managed by group node we need to unassociate.
         // If the geometry is managed by group node we need to unassociate.
         if (parent == null && isGrouped()) {
         if (parent == null && isGrouped()) {
             unassociateFromGroupNode();
             unassociateFromGroupNode();
@@ -413,7 +414,7 @@ public class Geometry extends Spatial {
      * {@link Geometry#getWorldTransform() world transform} of this geometry.
      * {@link Geometry#getWorldTransform() world transform} of this geometry.
      * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
      * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
      * before using this method.
      * before using this method.
-     * 
+     *
      * @return Matrix to transform from local space to world space
      * @return Matrix to transform from local space to world space
      */
      */
     public Matrix4f getWorldMatrix() {
     public Matrix4f getWorldMatrix() {
@@ -425,7 +426,7 @@ public class Geometry extends Spatial {
      * This alters the bound used on the mesh as well via
      * This alters the bound used on the mesh as well via
      * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
      * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
      * forces the world bounding volume to be recomputed.
      * forces the world bounding volume to be recomputed.
-     * 
+     *
      * @param modelBound The model bound to set
      * @param modelBound The model bound to set
      */
      */
     @Override
     @Override
@@ -472,15 +473,15 @@ public class Geometry extends Spatial {
     }
     }
 
 
     /**
     /**
-     * Determine whether this <code>Geometry</code> is managed by a 
+     * Determine whether this <code>Geometry</code> is managed by a
      * {@link GeometryGroupNode} or not.
      * {@link GeometryGroupNode} or not.
-     * 
+     *
      * @return True if managed by a {@link GeometryGroupNode}.
      * @return True if managed by a {@link GeometryGroupNode}.
      */
      */
     public boolean isGrouped() {
     public boolean isGrouped() {
         return groupNode != null;
         return groupNode != null;
     }
     }
-    
+
     /**
     /**
      * @deprecated Use {@link #isGrouped()} instead.
      * @deprecated Use {@link #isGrouped()} instead.
      */
      */
@@ -499,14 +500,14 @@ public class Geometry extends Spatial {
     @Override
     @Override
     public Geometry clone(boolean cloneMaterial) {
     public Geometry clone(boolean cloneMaterial) {
         Geometry geomClone = (Geometry) super.clone(cloneMaterial);
         Geometry geomClone = (Geometry) super.clone(cloneMaterial);
-        
+
         // This geometry is managed,
         // This geometry is managed,
         // but the cloned one is not attached to anything, hence not managed.
         // but the cloned one is not attached to anything, hence not managed.
         if (geomClone.isGrouped()) {
         if (geomClone.isGrouped()) {
             geomClone.groupNode = null;
             geomClone.groupNode = null;
             geomClone.startIndex = -1;
             geomClone.startIndex = -1;
         }
         }
-        
+
         geomClone.cachedWorldMat = cachedWorldMat.clone();
         geomClone.cachedWorldMat = cachedWorldMat.clone();
         if (material != null) {
         if (material != null) {
             if (cloneMaterial) {
             if (cloneMaterial) {
@@ -546,6 +547,16 @@ public class Geometry extends Spatial {
         return geomClone;
         return geomClone;
     }
     }
 
 
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.mesh = cloner.clone(mesh);
+        this.material = cloner.clone(material);
+        this.groupNode = cloner.clone(groupNode);
+    }
+
     @Override
     @Override
     public void write(JmeExporter ex) throws IOException {
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         super.write(ex);

+ 15 - 3
jme3-core/src/main/java/com/jme3/scene/LightNode.java

@@ -36,11 +36,12 @@ import com.jme3.export.JmeImporter;
 import com.jme3.light.Light;
 import com.jme3.light.Light;
 import com.jme3.scene.control.LightControl;
 import com.jme3.scene.control.LightControl;
 import com.jme3.scene.control.LightControl.ControlDirection;
 import com.jme3.scene.control.LightControl.ControlDirection;
+import com.jme3.util.clone.Cloner;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
  * <code>LightNode</code> is used to link together a {@link Light} object
  * <code>LightNode</code> is used to link together a {@link Light} object
- * with a {@link Node} object. 
+ * with a {@link Node} object.
  *
  *
  * @author Tim8Dev
  * @author Tim8Dev
  */
  */
@@ -66,7 +67,7 @@ public class LightNode extends Node {
 
 
     /**
     /**
      * Enable or disable the <code>LightNode</code> functionality.
      * Enable or disable the <code>LightNode</code> functionality.
-     * 
+     *
      * @param enabled If false, the functionality of LightNode will
      * @param enabled If false, the functionality of LightNode will
      * be disabled.
      * be disabled.
      */
      */
@@ -93,7 +94,18 @@ public class LightNode extends Node {
     public Light getLight() {
     public Light getLight() {
         return lightControl.getLight();
         return lightControl.getLight();
     }
     }
-    
+
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        // A change in behavior... I think previously LightNode was probably
+        // not really cloneable... or at least its lightControl would be pointing
+        // to the wrong control. -pspeed
+        this.lightControl = cloner.clone(lightControl);
+    }
+
     @Override
     @Override
     public void read(JmeImporter im) throws IOException {
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         super.read(im);

文件差异内容过多而无法显示
+ 215 - 179
jme3-core/src/main/java/com/jme3/scene/Mesh.java


+ 79 - 65
jme3-core/src/main/java/com/jme3/scene/Node.java

@@ -40,6 +40,7 @@ import com.jme3.export.Savable;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
@@ -53,7 +54,7 @@ import java.util.logging.Logger;
  * node maintains a collection of children and handles merging said children
  * node maintains a collection of children and handles merging said children
  * into a single bound to allow for very fast culling of multiple nodes. Node
  * into a single bound to allow for very fast culling of multiple nodes. Node
  * allows for any number of children to be attached.
  * allows for any number of children to be attached.
- * 
+ *
  * @author Mark Powell
  * @author Mark Powell
  * @author Gregg Patton
  * @author Gregg Patton
  * @author Joshua Slack
  * @author Joshua Slack
@@ -62,26 +63,26 @@ public class Node extends Spatial {
 
 
     private static final Logger logger = Logger.getLogger(Node.class.getName());
     private static final Logger logger = Logger.getLogger(Node.class.getName());
 
 
-    /** 
+    /**
      * This node's children.
      * This node's children.
      */
      */
     protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
     protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
 
 
     /**
     /**
      * If this node is a root, this list will contain the current
      * If this node is a root, this list will contain the current
-     * set of children (and children of children) that require 
+     * set of children (and children of children) that require
      * updateLogicalState() to be called as indicated by their
      * updateLogicalState() to be called as indicated by their
      * requiresUpdate() method.
      * requiresUpdate() method.
      */
      */
     private SafeArrayList<Spatial> updateList = null;
     private SafeArrayList<Spatial> updateList = null;
-    
+
     /**
     /**
      * False if the update list requires rebuilding.  This is Node.class
      * False if the update list requires rebuilding.  This is Node.class
      * specific and therefore not included as part of the Spatial update flags.
      * specific and therefore not included as part of the Spatial update flags.
      * A flag is used instead of nulling the updateList to avoid reallocating
      * A flag is used instead of nulling the updateList to avoid reallocating
      * a whole list every time the scene graph changes.
      * a whole list every time the scene graph changes.
-     */     
-    private boolean updateListValid = false;    
+     */
+    private boolean updateListValid = false;
 
 
     /**
     /**
      * Serialization only. Do not use.
      * Serialization only. Do not use.
@@ -93,29 +94,29 @@ public class Node extends Spatial {
     /**
     /**
      * Constructor instantiates a new <code>Node</code> with a default empty
      * Constructor instantiates a new <code>Node</code> with a default empty
      * list for containing children.
      * list for containing children.
-     * 
+     *
      * @param name the name of the scene element. This is required for
      * @param name the name of the scene element. This is required for
      * identification and comparison purposes.
      * identification and comparison purposes.
      */
      */
     public Node(String name) {
     public Node(String name) {
         super(name);
         super(name);
-        
+
         // For backwards compatibility, only clear the "requires
         // For backwards compatibility, only clear the "requires
         // update" flag if we are not a subclass of Node.
         // update" flag if we are not a subclass of Node.
         // This prevents subclass from silently failing to receive
         // This prevents subclass from silently failing to receive
         // updates when they upgrade.
         // updates when they upgrade.
-        setRequiresUpdates(Node.class != getClass()); 
+        setRequiresUpdates(Node.class != getClass());
     }
     }
 
 
     /**
     /**
-     * 
+     *
      * <code>getQuantity</code> returns the number of children this node
      * <code>getQuantity</code> returns the number of children this node
      * maintains.
      * maintains.
-     * 
+     *
      * @return the number of children this node maintains.
      * @return the number of children this node maintains.
      */
      */
     public int getQuantity() {
     public int getQuantity() {
-        return children.size();        
+        return children.size();
     }
     }
 
 
     @Override
     @Override
@@ -143,7 +144,7 @@ public class Node extends Spatial {
     @Override
     @Override
     protected void updateWorldBound(){
     protected void updateWorldBound(){
         super.updateWorldBound();
         super.updateWorldBound();
-        
+
         // for a node, the world bound is a combination of all it's children
         // for a node, the world bound is a combination of all it's children
         // bounds
         // bounds
         BoundingVolume resultBound = null;
         BoundingVolume resultBound = null;
@@ -167,7 +168,7 @@ public class Node extends Spatial {
     protected void setParent(Node parent) {
     protected void setParent(Node parent) {
         if( this.parent == null && parent != null ) {
         if( this.parent == null && parent != null ) {
             // We were a root before and now we aren't... make sure if
             // We were a root before and now we aren't... make sure if
-            // we had an updateList then we clear it completely to 
+            // we had an updateList then we clear it completely to
             // avoid holding the dead array.
             // avoid holding the dead array.
             updateList = null;
             updateList = null;
             updateListValid = false;
             updateListValid = false;
@@ -204,15 +205,15 @@ public class Node extends Spatial {
             return updateList;
             return updateList;
         }
         }
         if( updateList == null ) {
         if( updateList == null ) {
-            updateList = new SafeArrayList<Spatial>(Spatial.class);            
+            updateList = new SafeArrayList<Spatial>(Spatial.class);
         } else {
         } else {
             updateList.clear();
             updateList.clear();
         }
         }
 
 
         // Build the list
         // Build the list
         addUpdateChildren(updateList);
         addUpdateChildren(updateList);
-        updateListValid = true;       
-        return updateList;   
+        updateListValid = true;
+        return updateList;
     }
     }
 
 
     @Override
     @Override
@@ -238,7 +239,7 @@ public class Node extends Spatial {
             // This branch has no geometric state that requires updates.
             // This branch has no geometric state that requires updates.
             return;
             return;
         }
         }
-        
+
         if ((refreshFlags & RF_LIGHTLIST) != 0){
         if ((refreshFlags & RF_LIGHTLIST) != 0){
             updateWorldLightList();
             updateWorldLightList();
         }
         }
@@ -250,7 +251,7 @@ public class Node extends Spatial {
         }
         }
 
 
         refreshFlags &= ~RF_CHILD_LIGHTLIST;
         refreshFlags &= ~RF_CHILD_LIGHTLIST;
-        
+
         if (!children.isEmpty()) {
         if (!children.isEmpty()) {
             // the important part- make sure child geometric state is refreshed
             // the important part- make sure child geometric state is refreshed
             // first before updating own world bound. This saves
             // first before updating own world bound. This saves
@@ -260,7 +261,7 @@ public class Node extends Spatial {
             for (Spatial child : children.getArray()) {
             for (Spatial child : children.getArray()) {
                 child.updateGeometricState();
                 child.updateGeometricState();
             }
             }
-        }            
+        }
 
 
         if ((refreshFlags & RF_BOUND) != 0){
         if ((refreshFlags & RF_BOUND) != 0){
             updateWorldBound();
             updateWorldBound();
@@ -272,7 +273,7 @@ public class Node extends Spatial {
     /**
     /**
      * <code>getTriangleCount</code> returns the number of triangles contained
      * <code>getTriangleCount</code> returns the number of triangles contained
      * in all sub-branches of this node that contain geometry.
      * in all sub-branches of this node that contain geometry.
-     * 
+     *
      * @return the triangle count of this branch.
      * @return the triangle count of this branch.
      */
      */
     @Override
     @Override
@@ -286,11 +287,11 @@ public class Node extends Spatial {
 
 
         return count;
         return count;
     }
     }
-    
+
     /**
     /**
      * <code>getVertexCount</code> returns the number of vertices contained
      * <code>getVertexCount</code> returns the number of vertices contained
      * in all sub-branches of this node that contain geometry.
      * in all sub-branches of this node that contain geometry.
-     * 
+     *
      * @return the vertex count of this branch.
      * @return the vertex count of this branch.
      */
      */
     @Override
     @Override
@@ -311,7 +312,7 @@ public class Node extends Spatial {
      * returned.
      * returned.
      * <br>
      * <br>
      * If the child already had a parent it is detached from that former parent.
      * If the child already had a parent it is detached from that former parent.
-     * 
+     *
      * @param child
      * @param child
      *            the child to attach to this node.
      *            the child to attach to this node.
      * @return the number of children maintained by this node.
      * @return the number of children maintained by this node.
@@ -320,15 +321,15 @@ public class Node extends Spatial {
     public int attachChild(Spatial child) {
     public int attachChild(Spatial child) {
         return attachChildAt(child, children.size());
         return attachChildAt(child, children.size());
     }
     }
-    
+
     /**
     /**
-     * 
+     *
      * <code>attachChildAt</code> attaches a child to this node at an index. This node
      * <code>attachChildAt</code> attaches a child to this node at an index. This node
      * becomes the child's parent. The current number of children maintained is
      * becomes the child's parent. The current number of children maintained is
      * returned.
      * returned.
      * <br>
      * <br>
      * If the child already had a parent it is detached from that former parent.
      * If the child already had a parent it is detached from that former parent.
-     * 
+     *
      * @param child
      * @param child
      *            the child to attach to this node.
      *            the child to attach to this node.
      * @return the number of children maintained by this node.
      * @return the number of children maintained by this node.
@@ -344,7 +345,7 @@ public class Node extends Spatial {
             }
             }
             child.setParent(this);
             child.setParent(this);
             children.add(index, child);
             children.add(index, child);
-            
+
             // XXX: Not entirely correct? Forces bound update up the
             // XXX: Not entirely correct? Forces bound update up the
             // tree stemming from the attached child. Also forces
             // tree stemming from the attached child. Also forces
             // transform update down the tree-
             // transform update down the tree-
@@ -354,17 +355,17 @@ public class Node extends Spatial {
                 logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
                 logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
                         new Object[]{child.getName(), getName()});
                         new Object[]{child.getName(), getName()});
             }
             }
-            
+
             invalidateUpdateList();
             invalidateUpdateList();
         }
         }
-        
+
         return children.size();
         return children.size();
     }
     }
 
 
     /**
     /**
      * <code>detachChild</code> removes a given child from the node's list.
      * <code>detachChild</code> removes a given child from the node's list.
      * This child will no longer be maintained.
      * This child will no longer be maintained.
-     * 
+     *
      * @param child
      * @param child
      *            the child to remove.
      *            the child to remove.
      * @return the index the child was at. -1 if the child was not in the list.
      * @return the index the child was at. -1 if the child was not in the list.
@@ -379,16 +380,16 @@ public class Node extends Spatial {
                 detachChildAt(index);
                 detachChildAt(index);
             }
             }
             return index;
             return index;
-        } 
-            
-        return -1;        
+        }
+
+        return -1;
     }
     }
 
 
     /**
     /**
      * <code>detachChild</code> removes a given child from the node's list.
      * <code>detachChild</code> removes a given child from the node's list.
      * This child will no longe be maintained. Only the first child with a
      * This child will no longe be maintained. Only the first child with a
      * matching name is removed.
      * matching name is removed.
-     * 
+     *
      * @param childName
      * @param childName
      *            the child to remove.
      *            the child to remove.
      * @return the index the child was at. -1 if the child was not in the list.
      * @return the index the child was at. -1 if the child was not in the list.
@@ -408,10 +409,10 @@ public class Node extends Spatial {
     }
     }
 
 
     /**
     /**
-     * 
+     *
      * <code>detachChildAt</code> removes a child at a given index. That child
      * <code>detachChildAt</code> removes a child at a given index. That child
      * is returned for saving purposes.
      * is returned for saving purposes.
-     * 
+     *
      * @param index
      * @param index
      *            the index of the child to be removed.
      *            the index of the child to be removed.
      * @return the child at the supplied index.
      * @return the child at the supplied index.
@@ -432,14 +433,14 @@ public class Node extends Spatial {
             child.setTransformRefresh();
             child.setTransformRefresh();
             // lights are also inherited from parent
             // lights are also inherited from parent
             child.setLightListRefresh();
             child.setLightListRefresh();
-            
+
             invalidateUpdateList();
             invalidateUpdateList();
         }
         }
         return child;
         return child;
     }
     }
 
 
     /**
     /**
-     * 
+     *
      * <code>detachAllChildren</code> removes all children attached to this
      * <code>detachAllChildren</code> removes all children attached to this
      * node.
      * node.
      */
      */
@@ -458,7 +459,7 @@ public class Node extends Spatial {
      * in this node's list of children.
      * in this node's list of children.
      * @param sp
      * @param sp
      *          The spatial to look up
      *          The spatial to look up
-     * @return 
+     * @return
      *          The index of the spatial in the node's children, or -1
      *          The index of the spatial in the node's children, or -1
      *          if the spatial is not attached to this node
      *          if the spatial is not attached to this node
      */
      */
@@ -468,7 +469,7 @@ public class Node extends Spatial {
 
 
     /**
     /**
      * More efficient than e.g detaching and attaching as no updates are needed.
      * More efficient than e.g detaching and attaching as no updates are needed.
-     * 
+     *
      * @param index1 The index of the first child to swap
      * @param index1 The index of the first child to swap
      * @param index2 The index of the second child to swap
      * @param index2 The index of the second child to swap
      */
      */
@@ -481,9 +482,9 @@ public class Node extends Spatial {
     }
     }
 
 
     /**
     /**
-     * 
+     *
      * <code>getChild</code> returns a child at a given index.
      * <code>getChild</code> returns a child at a given index.
-     * 
+     *
      * @param i
      * @param i
      *            the index to retrieve the child from.
      *            the index to retrieve the child from.
      * @return the child at a specified index.
      * @return the child at a specified index.
@@ -497,13 +498,13 @@ public class Node extends Spatial {
      * given name (case sensitive.) This method does a depth first recursive
      * given name (case sensitive.) This method does a depth first recursive
      * search of all descendants of this node, it will return the first spatial
      * search of all descendants of this node, it will return the first spatial
      * found with a matching name.
      * found with a matching name.
-     * 
+     *
      * @param name
      * @param name
      *            the name of the child to retrieve. If null, we'll return null.
      *            the name of the child to retrieve. If null, we'll return null.
      * @return the child if found, or null.
      * @return the child if found, or null.
      */
      */
     public Spatial getChild(String name) {
     public Spatial getChild(String name) {
-        if (name == null) 
+        if (name == null)
             return null;
             return null;
 
 
         for (Spatial child : children.getArray()) {
         for (Spatial child : children.getArray()) {
@@ -518,11 +519,11 @@ public class Node extends Spatial {
         }
         }
         return null;
         return null;
     }
     }
-    
+
     /**
     /**
      * determines if the provided Spatial is contained in the children list of
      * determines if the provided Spatial is contained in the children list of
      * this node.
      * this node.
-     * 
+     *
      * @param spat
      * @param spat
      *            the child object to look for.
      *            the child object to look for.
      * @return true if the object is contained, false otherwise.
      * @return true if the object is contained, false otherwise.
@@ -566,39 +567,39 @@ public class Node extends Spatial {
 
 
     public int collideWith(Collidable other, CollisionResults results){
     public int collideWith(Collidable other, CollisionResults results){
         int total = 0;
         int total = 0;
-        
+
         // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
         // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
-        // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. 
+        // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
         // The idea is when there are few children, it can be too expensive to test boundingVolume first.
         // The idea is when there are few children, it can be too expensive to test boundingVolume first.
         /*
         /*
         I'm removing this change until some issues can be addressed and I really
         I'm removing this change until some issues can be addressed and I really
         think it needs to be implemented a better way anyway.
         think it needs to be implemented a better way anyway.
-        
+
         First, it causes issues for anyone doing collideWith() with BoundingVolumes
         First, it causes issues for anyone doing collideWith() with BoundingVolumes
         and expecting it to trickle down to the children.  For example, children
         and expecting it to trickle down to the children.  For example, children
         with BoundingSphere bounding volumes and collideWith(BoundingSphere).  Doing
         with BoundingSphere bounding volumes and collideWith(BoundingSphere).  Doing
         a collision check at the parent level then has to do a BoundingSphere to BoundingBox
         a collision check at the parent level then has to do a BoundingSphere to BoundingBox
         collision which isn't resolved.  (Having to come up with a collision point in that
         collision which isn't resolved.  (Having to come up with a collision point in that
         case is tricky and the first sign that this is the wrong approach.)
         case is tricky and the first sign that this is the wrong approach.)
-        
+
         Second, the rippling changes this caused to 'optimize' collideWith() for this
         Second, the rippling changes this caused to 'optimize' collideWith() for this
         special use-case are another sign that this approach was a bit dodgy.  The whole
         special use-case are another sign that this approach was a bit dodgy.  The whole
         idea of calculating a full collision just to see if the two shapes collide at all
         idea of calculating a full collision just to see if the two shapes collide at all
         is very wasteful.
         is very wasteful.
-        
+
         A proper implementation should support a simpler boolean check that doesn't do
         A proper implementation should support a simpler boolean check that doesn't do
         all of that calculation.  For example, if 'other' is also a BoundingVolume (ie: 99.9%
         all of that calculation.  For example, if 'other' is also a BoundingVolume (ie: 99.9%
         of all non-Ray cases) then a direct BV to BV intersects() test can be done.  So much
         of all non-Ray cases) then a direct BV to BV intersects() test can be done.  So much
         faster.  And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
         faster.  And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
-        
+
         I don't have time to do it right now but I'll at least un-break a bunch of peoples'
         I don't have time to do it right now but I'll at least un-break a bunch of peoples'
         code until it can be 'optimized' properly.  Hopefully it's not too late to back out
         code until it can be 'optimized' properly.  Hopefully it's not too late to back out
-        the other dodgy ripples this caused.  -pspeed (hindsight-expert ;)) 
-        
+        the other dodgy ripples this caused.  -pspeed (hindsight-expert ;))
+
         Note: the code itself is relatively simple to implement but I don't have time to
         Note: the code itself is relatively simple to implement but I don't have time to
         a) test it, and b) see if '> 4' is still a decent check for it.  Could be it's fast
         a) test it, and b) see if '> 4' is still a decent check for it.  Could be it's fast
         enough to do all the time for > 1.
         enough to do all the time for > 1.
-        
+
         if (children.size() > 4)
         if (children.size() > 4)
         {
         {
           BoundingVolume bv = this.getWorldBound();
           BoundingVolume bv = this.getWorldBound();
@@ -642,7 +643,7 @@ public class Node extends Spatial {
      * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
      * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
      *
      *
      * @see java.util.regex.Pattern
      * @see java.util.regex.Pattern
-     * @see Spatial#matches(java.lang.Class, java.lang.String) 
+     * @see Spatial#matches(java.lang.Class, java.lang.String)
      */
      */
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     public <T extends Spatial>List<T> descendantMatches(
     public <T extends Spatial>List<T> descendantMatches(
@@ -662,7 +663,7 @@ public class Node extends Spatial {
     /**
     /**
      * Convenience wrapper.
      * Convenience wrapper.
      *
      *
-     * @see #descendantMatches(java.lang.Class, java.lang.String) 
+     * @see #descendantMatches(java.lang.Class, java.lang.String)
      */
      */
     public <T extends Spatial>List<T> descendantMatches(
     public <T extends Spatial>List<T> descendantMatches(
             Class<T> spatialSubclass) {
             Class<T> spatialSubclass) {
@@ -672,7 +673,7 @@ public class Node extends Spatial {
     /**
     /**
      * Convenience wrapper.
      * Convenience wrapper.
      *
      *
-     * @see #descendantMatches(java.lang.Class, java.lang.String) 
+     * @see #descendantMatches(java.lang.Class, java.lang.String)
      */
      */
     public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
     public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
         return descendantMatches(null, nameRegex);
         return descendantMatches(null, nameRegex);
@@ -691,7 +692,7 @@ public class Node extends Spatial {
         // Reset the fields of the clone that should be in a 'new' state.
         // Reset the fields of the clone that should be in a 'new' state.
         nodeClone.updateList = null;
         nodeClone.updateList = null;
         nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
         nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
-            
+
         return nodeClone;
         return nodeClone;
     }
     }
 
 
@@ -707,6 +708,19 @@ public class Node extends Spatial {
         return nodeClone;
         return nodeClone;
     }
     }
 
 
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.children = cloner.clone(children);
+
+        // Only the outer cloning thing knows whether this should be nulled
+        // or not... after all, we might be cloning a root node in which case
+        // cloning this list is fine.
+        this.updateList = cloner.clone(updateList);
+    }
+
     @Override
     @Override
     public void write(JmeExporter e) throws IOException {
     public void write(JmeExporter e) throws IOException {
         super.write(e);
         super.write(e);
@@ -718,8 +732,8 @@ public class Node extends Spatial {
         // XXX: Load children before loading itself!!
         // XXX: Load children before loading itself!!
         // This prevents empty children list if controls query
         // This prevents empty children list if controls query
         // it in Control.setSpatial().
         // it in Control.setSpatial().
-        
-        children = new SafeArrayList( Spatial.class, 
+
+        children = new SafeArrayList( Spatial.class,
                                       e.getCapsule(this).readSavableArrayList("children", null) );
                                       e.getCapsule(this).readSavableArrayList("children", null) );
 
 
         // go through children and set parent to this node
         // go through children and set parent to this node
@@ -728,7 +742,7 @@ public class Node extends Spatial {
                 child.parent = this;
                 child.parent = this;
             }
             }
         }
         }
-        
+
         super.read(e);
         super.read(e);
     }
     }
 
 
@@ -749,7 +763,7 @@ public class Node extends Spatial {
             }
             }
         }
         }
     }
     }
-    
+
     @Override
     @Override
     public void depthFirstTraversal(SceneGraphVisitor visitor) {
     public void depthFirstTraversal(SceneGraphVisitor visitor) {
         for (Spatial child : children.getArray()) {
         for (Spatial child : children.getArray()) {
@@ -757,7 +771,7 @@ public class Node extends Spatial {
         }
         }
         visitor.visit(this);
         visitor.visit(this);
     }
     }
-    
+
     @Override
     @Override
     protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
     protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
         queue.addAll(children);
         queue.addAll(children);

+ 114 - 66
jme3-core/src/main/java/com/jme3/scene/Spatial.java

@@ -47,6 +47,8 @@ import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
@@ -63,17 +65,17 @@ import java.util.logging.Logger;
  * @author Joshua Slack
  * @author Joshua Slack
  * @version $Revision: 4075 $, $Data$
  * @version $Revision: 4075 $, $Data$
  */
  */
-public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset {
+public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable {
 
 
     private static final Logger logger = Logger.getLogger(Spatial.class.getName());
     private static final Logger logger = Logger.getLogger(Spatial.class.getName());
 
 
     /**
     /**
-     * Specifies how frustum culling should be handled by 
+     * Specifies how frustum culling should be handled by
      * this spatial.
      * this spatial.
      */
      */
     public enum CullHint {
     public enum CullHint {
 
 
-        /** 
+        /**
          * Do whatever our parent does. If no parent, default to {@link #Dynamic}.
          * Do whatever our parent does. If no parent, default to {@link #Dynamic}.
          */
          */
         Inherit,
         Inherit,
@@ -83,13 +85,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
          * Camera planes whether or not this Spatial should be culled.
          * Camera planes whether or not this Spatial should be culled.
          */
          */
         Dynamic,
         Dynamic,
-        /** 
+        /**
          * Always cull this from the view, throwing away this object
          * Always cull this from the view, throwing away this object
          * and any children from rendering commands.
          * and any children from rendering commands.
          */
          */
         Always,
         Always,
         /**
         /**
-         * Never cull this from view, always draw it. 
+         * Never cull this from view, always draw it.
          * Note that we will still get culled if our parent is culled.
          * Note that we will still get culled if our parent is culled.
          */
          */
         Never;
         Never;
@@ -100,15 +102,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
      */
     public enum BatchHint {
     public enum BatchHint {
 
 
-        /** 
+        /**
          * Do whatever our parent does. If no parent, default to {@link #Always}.
          * Do whatever our parent does. If no parent, default to {@link #Always}.
          */
          */
         Inherit,
         Inherit,
-        /** 
+        /**
          * This spatial will always be batched when attached to a BatchNode.
          * This spatial will always be batched when attached to a BatchNode.
          */
          */
         Always,
         Always,
-        /** 
+        /**
          * This spatial will never be batched when attached to a BatchNode.
          * This spatial will never be batched when attached to a BatchNode.
          */
          */
         Never;
         Never;
@@ -118,12 +120,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
      */
     protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
     protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
                                RF_BOUND = 0x02,
                                RF_BOUND = 0x02,
-                               RF_LIGHTLIST = 0x04, // changes in light lists 
+                               RF_LIGHTLIST = 0x04, // changes in light lists
                                RF_CHILD_LIGHTLIST = 0x08; // some child need geometry update
                                RF_CHILD_LIGHTLIST = 0x08; // some child need geometry update
-    
+
     protected CullHint cullHint = CullHint.Inherit;
     protected CullHint cullHint = CullHint.Inherit;
     protected BatchHint batchHint = BatchHint.Inherit;
     protected BatchHint batchHint = BatchHint.Inherit;
-    /** 
+    /**
      * Spatial's bounding volume relative to the world.
      * Spatial's bounding volume relative to the world.
      */
      */
     protected BoundingVolume worldBound;
     protected BoundingVolume worldBound;
@@ -132,7 +134,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
      */
     protected LightList localLights;
     protected LightList localLights;
     protected transient LightList worldLights;
     protected transient LightList worldLights;
-    /** 
+    /**
      * This spatial's name.
      * This spatial's name.
      */
      */
     protected String name;
     protected String name;
@@ -147,11 +149,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     protected HashMap<String, Savable> userData = null;
     protected HashMap<String, Savable> userData = null;
     /**
     /**
      * Used for smart asset caching
      * Used for smart asset caching
-     * 
-     * @see AssetKey#useSmartCache() 
+     *
+     * @see AssetKey#useSmartCache()
      */
      */
     protected AssetKey key;
     protected AssetKey key;
-    /** 
+    /**
      * Spatial's parent, or null if it has none.
      * Spatial's parent, or null if it has none.
      */
      */
     protected transient Node parent;
     protected transient Node parent;
@@ -174,7 +176,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     /**
     /**
      * Serialization only. Do not use.
      * Serialization only. Do not use.
      * Not really. This class is never instantiated directly but the
      * Not really. This class is never instantiated directly but the
-     * subclasses like to use the no-arg constructor for their own 
+     * subclasses like to use the no-arg constructor for their own
      * no-arg constructor... which is technically weaker than
      * no-arg constructor... which is technically weaker than
      * forward supplying defaults.
      * forward supplying defaults.
      */
      */
@@ -192,7 +194,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
      */
     protected Spatial(String name) {
     protected Spatial(String name) {
         this.name = name;
         this.name = name;
-        
+
         localTransform = new Transform();
         localTransform = new Transform();
         worldTransform = new Transform();
         worldTransform = new Transform();
 
 
@@ -219,13 +221,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     boolean requiresUpdates() {
     boolean requiresUpdates() {
         return requiresUpdates | !controls.isEmpty();
         return requiresUpdates | !controls.isEmpty();
     }
     }
-    
+
     /**
     /**
-     * Subclasses can call this with true to denote that they require 
+     * Subclasses can call this with true to denote that they require
      * updateLogicalState() to be called even if they contain no controls.
      * updateLogicalState() to be called even if they contain no controls.
      * Setting this to false reverts to the default behavior of only
      * Setting this to false reverts to the default behavior of only
      * updating if the spatial has controls.  This is not meant to
      * updating if the spatial has controls.  This is not meant to
-     * indicate dynamic state in any way and must be called while 
+     * indicate dynamic state in any way and must be called while
      * unattached or an IllegalStateException is thrown.  It is designed
      * unattached or an IllegalStateException is thrown.  It is designed
      * to be called during object construction and then never changed, ie:
      * to be called during object construction and then never changed, ie:
      * it's meant to be subclass specific state and not runtime state.
      * it's meant to be subclass specific state and not runtime state.
@@ -251,12 +253,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         // override it for more optimal behavior.  Node and Geometry will override
         // override it for more optimal behavior.  Node and Geometry will override
         // it to false if the class is Node.class or Geometry.class.
         // it to false if the class is Node.class or Geometry.class.
         // This means that all subclasses will default to the old behavior
         // This means that all subclasses will default to the old behavior
-        // unless they opt in. 
+        // unless they opt in.
         if( parent != null ) {
         if( parent != null ) {
-            throw new IllegalStateException("setRequiresUpdates() cannot be called once attached."); 
+            throw new IllegalStateException("setRequiresUpdates() cannot be called once attached.");
         }
         }
         this.requiresUpdates = f;
         this.requiresUpdates = f;
-    } 
+    }
 
 
     /**
     /**
      * Indicate that the transform of this spatial has changed and that
      * Indicate that the transform of this spatial has changed and that
@@ -269,13 +271,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
 
 
     protected void setLightListRefresh() {
     protected void setLightListRefresh() {
         refreshFlags |= RF_LIGHTLIST;
         refreshFlags |= RF_LIGHTLIST;
-        
+
         // Make sure next updateGeometricState() visits this branch
         // Make sure next updateGeometricState() visits this branch
         // to update lights.
         // to update lights.
         Spatial p = parent;
         Spatial p = parent;
         while (p != null) {
         while (p != null) {
             //if (p.refreshFlags != 0) {
             //if (p.refreshFlags != 0) {
-                // any refresh flag is sufficient, 
+                // any refresh flag is sufficient,
                 // as each propagates to the root Node
                 // as each propagates to the root Node
 
 
                 // 2015/2/8:
                 // 2015/2/8:
@@ -283,16 +285,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
                 // or getWorldTransform() activates a "partial refresh"
                 // or getWorldTransform() activates a "partial refresh"
                 // which does not update the lights but does clear
                 // which does not update the lights but does clear
                 // the refresh flags on the ancestors!
                 // the refresh flags on the ancestors!
-            
-            //    return; 
+
+            //    return;
             //}
             //}
-            
+
             if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
             if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
                 // The parent already has this flag,
                 // The parent already has this flag,
                 // so must all ancestors.
                 // so must all ancestors.
                 return;
                 return;
             }
             }
-            
+
             p.refreshFlags |= RF_CHILD_LIGHTLIST;
             p.refreshFlags |= RF_CHILD_LIGHTLIST;
             p = p.parent;
             p = p.parent;
         }
         }
@@ -315,10 +317,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             p = p.parent;
             p = p.parent;
         }
         }
     }
     }
-    
+
     /**
     /**
      * (Internal use only) Forces a refresh of the given types of data.
      * (Internal use only) Forces a refresh of the given types of data.
-     * 
+     *
      * @param transforms Refresh world transform based on parents'
      * @param transforms Refresh world transform based on parents'
      * @param bounds Refresh bounding volume data based on child nodes
      * @param bounds Refresh bounding volume data based on child nodes
      * @param lights Refresh light list based on parents'
      * @param lights Refresh light list based on parents'
@@ -401,9 +403,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     /**
     /**
      * Returns the local {@link LightList}, which are the lights
      * Returns the local {@link LightList}, which are the lights
      * that were directly attached to this <code>Spatial</code> through the
      * that were directly attached to this <code>Spatial</code> through the
-     * {@link #addLight(com.jme3.light.Light) } and 
+     * {@link #addLight(com.jme3.light.Light) } and
      * {@link #removeLight(com.jme3.light.Light) } methods.
      * {@link #removeLight(com.jme3.light.Light) } methods.
-     * 
+     *
      * @return The local light list
      * @return The local light list
      */
      */
     public LightList getLocalLightList() {
     public LightList getLocalLightList() {
@@ -414,7 +416,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * Returns the world {@link LightList}, containing the lights
      * Returns the world {@link LightList}, containing the lights
      * combined from all this <code>Spatial's</code> parents up to and including
      * combined from all this <code>Spatial's</code> parents up to and including
      * this <code>Spatial</code>'s lights.
      * this <code>Spatial</code>'s lights.
-     * 
+     *
      * @return The combined world light list
      * @return The combined world light list
      */
      */
     public LightList getWorldLightList() {
     public LightList getWorldLightList() {
@@ -502,14 +504,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * <code>lookAt</code> is a convenience method for auto-setting the local
      * <code>lookAt</code> is a convenience method for auto-setting the local
      * rotation based on a position in world space and an up vector. It computes the rotation
      * rotation based on a position in world space and an up vector. It computes the rotation
      * to transform the z-axis to point onto 'position' and the y-axis to 'up'.
      * to transform the z-axis to point onto 'position' and the y-axis to 'up'.
-     * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } 
+     * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) }
      * this method takes a world position to look at and not a relative direction.
      * this method takes a world position to look at and not a relative direction.
      *
      *
      * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation.
      * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation.
      * This was resulting in improper rotation when the spatial had rotated parent nodes.
      * This was resulting in improper rotation when the spatial had rotated parent nodes.
-     * This method is intended to work in world space, so no matter what parent graph the 
+     * This method is intended to work in world space, so no matter what parent graph the
      * spatial has, it will look at the given position in world space.
      * spatial has, it will look at the given position in world space.
-     * 
+     *
      * @param position
      * @param position
      *            where to look at in terms of world coordinates
      *            where to look at in terms of world coordinates
      * @param upVector
      * @param upVector
@@ -522,10 +524,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         TempVars vars = TempVars.get();
         TempVars vars = TempVars.get();
 
 
         Vector3f compVecA = vars.vect4;
         Vector3f compVecA = vars.vect4;
-      
+
         compVecA.set(position).subtractLocal(worldTranslation);
         compVecA.set(position).subtractLocal(worldTranslation);
-        getLocalRotation().lookAt(compVecA, upVector);        
-        
+        getLocalRotation().lookAt(compVecA, upVector);
+
         if ( getParent() != null ) {
         if ( getParent() != null ) {
             Quaternion rot=vars.quat1;
             Quaternion rot=vars.quat1;
             rot =  rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
             rot =  rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
@@ -579,7 +581,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     }
     }
 
 
     /**
     /**
-     * Computes the world transform of this Spatial in the most 
+     * Computes the world transform of this Spatial in the most
      * efficient manner possible.
      * efficient manner possible.
      */
      */
     void checkDoTransformUpdate() {
     void checkDoTransformUpdate() {
@@ -670,7 +672,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * @param vp The ViewPort to which the Spatial is being rendered to.
      * @param vp The ViewPort to which the Spatial is being rendered to.
      *
      *
      * @see Spatial#addControl(com.jme3.scene.control.Control)
      * @see Spatial#addControl(com.jme3.scene.control.Control)
-     * @see Spatial#getControl(java.lang.Class) 
+     * @see Spatial#getControl(java.lang.Class)
      */
      */
     public void runControlRender(RenderManager rm, ViewPort vp) {
     public void runControlRender(RenderManager rm, ViewPort vp) {
         if (controls.isEmpty()) {
         if (controls.isEmpty()) {
@@ -686,26 +688,26 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * Add a control to the list of controls.
      * Add a control to the list of controls.
      * @param control The control to add.
      * @param control The control to add.
      *
      *
-     * @see Spatial#removeControl(java.lang.Class) 
+     * @see Spatial#removeControl(java.lang.Class)
      */
      */
     public void addControl(Control control) {
     public void addControl(Control control) {
         boolean before = requiresUpdates();
         boolean before = requiresUpdates();
         controls.add(control);
         controls.add(control);
         control.setSpatial(this);
         control.setSpatial(this);
         boolean after = requiresUpdates();
         boolean after = requiresUpdates();
-        
+
         // If the requirement to be updated has changed
         // If the requirement to be updated has changed
         // then we need to let the parent node know so it
         // then we need to let the parent node know so it
         // can rebuild its update list.
         // can rebuild its update list.
         if( parent != null && before != after ) {
         if( parent != null && before != after ) {
-            parent.invalidateUpdateList();   
+            parent.invalidateUpdateList();
         }
         }
     }
     }
 
 
     /**
     /**
      * Removes the first control that is an instance of the given class.
      * Removes the first control that is an instance of the given class.
      *
      *
-     * @see Spatial#addControl(com.jme3.scene.control.Control) 
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
      */
      */
     public void removeControl(Class<? extends Control> controlType) {
     public void removeControl(Class<? extends Control> controlType) {
         boolean before = requiresUpdates();
         boolean before = requiresUpdates();
@@ -717,23 +719,23 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             }
             }
         }
         }
         boolean after = requiresUpdates();
         boolean after = requiresUpdates();
-        
+
         // If the requirement to be updated has changed
         // If the requirement to be updated has changed
         // then we need to let the parent node know so it
         // then we need to let the parent node know so it
         // can rebuild its update list.
         // can rebuild its update list.
         if( parent != null && before != after ) {
         if( parent != null && before != after ) {
-            parent.invalidateUpdateList();   
+            parent.invalidateUpdateList();
         }
         }
     }
     }
 
 
     /**
     /**
      * Removes the given control from this spatial's controls.
      * Removes the given control from this spatial's controls.
-     * 
+     *
      * @param control The control to remove
      * @param control The control to remove
      * @return True if the control was successfully removed. False if the
      * @return True if the control was successfully removed. False if the
      * control is not assigned to this spatial.
      * control is not assigned to this spatial.
      *
      *
-     * @see Spatial#addControl(com.jme3.scene.control.Control) 
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
      */
      */
     public boolean removeControl(Control control) {
     public boolean removeControl(Control control) {
         boolean before = requiresUpdates();
         boolean before = requiresUpdates();
@@ -743,14 +745,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         }
         }
 
 
         boolean after = requiresUpdates();
         boolean after = requiresUpdates();
-        
+
         // If the requirement to be updated has changed
         // If the requirement to be updated has changed
         // then we need to let the parent node know so it
         // then we need to let the parent node know so it
         // can rebuild its update list.
         // can rebuild its update list.
         if( parent != null && before != after ) {
         if( parent != null && before != after ) {
-            parent.invalidateUpdateList();   
+            parent.invalidateUpdateList();
         }
         }
-        
+
         return result;
         return result;
     }
     }
 
 
@@ -761,7 +763,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * @param controlType The superclass of the control to look for.
      * @param controlType The superclass of the control to look for.
      * @return The first instance in the list of the controlType class, or null.
      * @return The first instance in the list of the controlType class, or null.
      *
      *
-     * @see Spatial#addControl(com.jme3.scene.control.Control) 
+     * @see Spatial#addControl(com.jme3.scene.control.Control)
      */
      */
     public <T extends Control> T getControl(Class<T> controlType) {
     public <T extends Control> T getControl(Class<T> controlType) {
         for (Control c : controls.getArray()) {
         for (Control c : controls.getArray()) {
@@ -790,7 +792,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     /**
     /**
      * @return The number of controls attached to this Spatial.
      * @return The number of controls attached to this Spatial.
      * @see Spatial#addControl(com.jme3.scene.control.Control)
      * @see Spatial#addControl(com.jme3.scene.control.Control)
-     * @see Spatial#removeControl(java.lang.Class) 
+     * @see Spatial#removeControl(java.lang.Class)
      */
      */
     public int getNumControls() {
     public int getNumControls() {
         return controls.size();
         return controls.size();
@@ -815,7 +817,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * Calling this when the Spatial is attached to a node
      * Calling this when the Spatial is attached to a node
      * will cause undefined results. User code should only call this
      * will cause undefined results. User code should only call this
      * method on Spatials having no parent.
      * method on Spatials having no parent.
-     * 
+     *
      * @see Spatial#getWorldLightList()
      * @see Spatial#getWorldLightList()
      * @see Spatial#getWorldTransform()
      * @see Spatial#getWorldTransform()
      * @see Spatial#getWorldBound()
      * @see Spatial#getWorldBound()
@@ -835,7 +837,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         if ((refreshFlags & RF_BOUND) != 0) {
         if ((refreshFlags & RF_BOUND) != 0) {
             updateWorldBound();
             updateWorldBound();
         }
         }
-        
+
         assert refreshFlags == 0;
         assert refreshFlags == 0;
     }
     }
 
 
@@ -1067,9 +1069,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
 
 
     /**
     /**
      * <code>removeLight</code> removes the given light from the Spatial.
      * <code>removeLight</code> removes the given light from the Spatial.
-     * 
+     *
      * @param light The light to remove.
      * @param light The light to remove.
-     * @see Spatial#addLight(com.jme3.light.Light) 
+     * @see Spatial#addLight(com.jme3.light.Light)
      */
      */
     public void removeLight(Light light) {
     public void removeLight(Light light) {
         localLights.remove(light);
         localLights.remove(light);
@@ -1264,7 +1266,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * All controls will be cloned using the Control.cloneForSpatial method
      * All controls will be cloned using the Control.cloneForSpatial method
      * on the clone.
      * on the clone.
      *
      *
-     * @see Mesh#cloneForAnim() 
+     * @see Mesh#cloneForAnim()
      */
      */
     public Spatial clone(boolean cloneMaterial) {
     public Spatial clone(boolean cloneMaterial) {
         try {
         try {
@@ -1328,7 +1330,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      * All controls will be cloned using the Control.cloneForSpatial method
      * All controls will be cloned using the Control.cloneForSpatial method
      * on the clone.
      * on the clone.
      *
      *
-     * @see Mesh#cloneForAnim() 
+     * @see Mesh#cloneForAnim()
      */
      */
     @Override
     @Override
     public Spatial clone() {
     public Spatial clone() {
@@ -1344,13 +1346,59 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
      */
     public abstract Spatial deepClone();
     public abstract Spatial deepClone();
 
 
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public Spatial jmeClone() {
+        try {
+            Spatial clone = (Spatial)super.clone();
+            return clone;
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+
+        // Clone all of the fields that need fix-ups and/or potential
+        // sharing.
+        this.parent = cloner.clone(parent);
+        this.worldBound = cloner.clone(worldBound);
+        this.worldLights = cloner.clone(worldLights);
+        this.localLights = cloner.clone(localLights);
+        this.worldTransform = cloner.clone(worldTransform);
+        this.localTransform = cloner.clone(localTransform);
+        this.controls = cloner.clone(controls);
+
+        // Cloner doesn't handle maps on its own just yet.
+        // Note: this is more advanced cloning than the old clone() method
+        // did because it just shallow cloned the map.  In this case, we want
+        // to avoid all of the nasty cloneForSpatial() fixup style code that
+        // used to inject stuff into the clone's user data.  By using cloner
+        // to clone the user data we get this automatically.
+        userData = (HashMap<String, Savable>)userData.clone();
+        for( Map.Entry<String, Savable> e : userData.entrySet() ) {
+            Savable value = e.getValue();
+            if( value instanceof Cloneable ) {
+                // Note: all JmeCloneable objects are also Cloneable so this
+                // catches both cases.
+                e.setValue(cloner.clone(value));
+            }
+        }
+    }
+
     public void setUserData(String key, Object data) {
     public void setUserData(String key, Object data) {
         if (userData == null) {
         if (userData == null) {
             userData = new HashMap<String, Savable>();
             userData = new HashMap<String, Savable>();
         }
         }
 
 
         if(data == null){
         if(data == null){
-            userData.remove(key);            
+            userData.remove(key);
         }else if (data instanceof Savable) {
         }else if (data instanceof Savable) {
             userData.put(key, (Savable) data);
             userData.put(key, (Savable) data);
         } else {
         } else {
@@ -1445,7 +1493,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
         //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
         //the AnimControl creates the SkeletonControl for old files and add it to the spatial.
         //the AnimControl creates the SkeletonControl for old files and add it to the spatial.
         //The SkeletonControl must be the last in the stack so we add the list of all other control before it.
         //The SkeletonControl must be the last in the stack so we add the list of all other control before it.
-        //When backward compatibility won't be needed anymore this can be replaced by : 
+        //When backward compatibility won't be needed anymore this can be replaced by :
         //controls = ic.readSavableArrayList("controlsList", null));
         //controls = ic.readSavableArrayList("controlsList", null));
         controls.addAll(0, ic.readSavableArrayList("controlsList", null));
         controls.addAll(0, ic.readSavableArrayList("controlsList", null));
 
 
@@ -1508,9 +1556,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     /**
     /**
      * <code>setQueueBucket</code> determines at what phase of the
      * <code>setQueueBucket</code> determines at what phase of the
      * rendering process this Spatial will rendered. See the
      * rendering process this Spatial will rendered. See the
-     * {@link Bucket} enum for an explanation of the various 
+     * {@link Bucket} enum for an explanation of the various
      * render queue buckets.
      * render queue buckets.
-     * 
+     *
      * @param queueBucket
      * @param queueBucket
      *            The bucket to use for this Spatial.
      *            The bucket to use for this Spatial.
      */
      */
@@ -1595,7 +1643,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      *
      *
      * @return store if not null, otherwise, a new matrix containing the result.
      * @return store if not null, otherwise, a new matrix containing the result.
      *
      *
-     * @see Spatial#getWorldTransform() 
+     * @see Spatial#getWorldTransform()
      */
      */
     public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
     public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
         if (store == null) {
         if (store == null) {

+ 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.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -45,7 +47,7 @@ import java.io.IOException;
  *
  *
  * @author Kirill Vainer
  * @author Kirill Vainer
  */
  */
-public abstract class AbstractControl implements Control {
+public abstract class AbstractControl implements Control, JmeCloneable {
 
 
     protected boolean enabled = true;
     protected boolean enabled = true;
     protected Spatial spatial;
     protected Spatial spatial;
@@ -105,6 +107,20 @@ public abstract class AbstractControl implements Control {
         } 
         } 
     }
     }
 
 
+    @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) {
     public void update(float tpf) {
         if (!enabled)
         if (!enabled)
             return;
             return;

+ 7 - 6
jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java

@@ -86,12 +86,13 @@ public class BillboardControl extends AbstractControl {
         alignment = Alignment.Screen;
         alignment = Alignment.Screen;
     }
     }
 
 
-    public Control cloneForSpatial(Spatial spatial) {
-        BillboardControl control = new BillboardControl();
-        control.alignment = this.alignment;
-        control.setSpatial(spatial);
-        return control;
-    }
+    // default implementation from AbstractControl is equivalent
+    //public Control cloneForSpatial(Spatial spatial) {
+    //    BillboardControl control = new BillboardControl();
+    //    control.alignment = this.alignment;
+    //    control.setSpatial(spatial);
+    //    return control;
+    //}
 
 
     @Override
     @Override
     protected void controlUpdate(float tpf) {
     protected void controlUpdate(float tpf) {

+ 8 - 7
jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java

@@ -136,13 +136,14 @@ public class CameraControl extends AbstractControl {
         // nothing to do
         // nothing to do
     }
     }
 
 
-    @Override
-    public Control cloneForSpatial(Spatial newSpatial) {
-        CameraControl control = new CameraControl(camera, controlDir);
-        control.setSpatial(newSpatial);
-        control.setEnabled(isEnabled());
-        return control;
-    }
+    // default implementation from AbstractControl is equivalent
+    //@Override
+    //public Control cloneForSpatial(Spatial newSpatial) {
+    //    CameraControl control = new CameraControl(camera, controlDir);
+    //    control.setSpatial(newSpatial);
+    //    control.setEnabled(isEnabled());
+    //    return control;
+    //}
     private static final String CONTROL_DIR_NAME = "controlDir";
     private static final String CONTROL_DIR_NAME = "controlDir";
     private static final String CAMERA_NAME = "camera";
     private static final String CAMERA_NAME = "camera";
     
     

+ 8 - 7
jme3-core/src/main/java/com/jme3/scene/control/LightControl.java

@@ -167,13 +167,14 @@ public class LightControl extends AbstractControl {
         // nothing to do
         // nothing to do
     }
     }
 
 
-    @Override
-    public Control cloneForSpatial(Spatial newSpatial) {
-        LightControl control = new LightControl(light, controlDir);
-        control.setSpatial(newSpatial);
-        control.setEnabled(isEnabled());
-        return control;
-    }
+    // default implementation from AbstractControl is equivalent
+    //@Override
+    //public Control cloneForSpatial(Spatial newSpatial) {
+    //    LightControl control = new LightControl(light, controlDir);
+    //    control.setSpatial(newSpatial);
+    //    control.setEnabled(isEnabled());
+    //    return control;
+    //}
     private static final String CONTROL_DIR_NAME = "controlDir";
     private static final String CONTROL_DIR_NAME = "controlDir";
     private static final String LIGHT_NAME = "light";
     private static final String LIGHT_NAME = "light";
     
     

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

+ 14 - 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.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 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.Callable;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Future;
 import java.util.concurrent.Future;
@@ -85,6 +87,7 @@ public class UpdateControl extends AbstractControl {
         
         
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial newSpatial) {
     public Control cloneForSpatial(Spatial newSpatial) {
         UpdateControl control = new UpdateControl(); 
         UpdateControl control = new UpdateControl(); 
         control.setSpatial(newSpatial);
         control.setSpatial(newSpatial);
@@ -93,4 +96,15 @@ public class UpdateControl extends AbstractControl {
         return control;
         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;
+    }     
 }
 }

+ 68 - 57
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java

@@ -47,19 +47,20 @@ import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.VertexBuffer.Usage;
 import com.jme3.scene.VertexBuffer.Usage;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
+import com.jme3.util.clone.Cloner;
 import java.io.IOException;
 import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 
 
 public class InstancedGeometry extends Geometry {
 public class InstancedGeometry extends Geometry {
-    
+
     private static final int INSTANCE_SIZE = 16;
     private static final int INSTANCE_SIZE = 16;
-    
+
     private VertexBuffer[] globalInstanceData;
     private VertexBuffer[] globalInstanceData;
     private VertexBuffer transformInstanceData;
     private VertexBuffer transformInstanceData;
     private Geometry[] geometries = new Geometry[1];
     private Geometry[] geometries = new Geometry[1];
-    
+
     private int firstUnusedIndex = 0;
     private int firstUnusedIndex = 0;
 
 
     /**
     /**
@@ -71,12 +72,12 @@ public class InstancedGeometry extends Geometry {
         setBatchHint(BatchHint.Never);
         setBatchHint(BatchHint.Never);
         setMaxNumInstances(1);
         setMaxNumInstances(1);
     }
     }
-    
+
     /**
     /**
      * Creates instanced geometry with the specified mode and name.
      * Creates instanced geometry with the specified mode and name.
-     * 
-     * @param name The name of the spatial. 
-     * 
+     *
+     * @param name The name of the spatial.
+     *
      * @see Spatial#Spatial(java.lang.String)
      * @see Spatial#Spatial(java.lang.String)
      */
      */
     public InstancedGeometry(String name) {
     public InstancedGeometry(String name) {
@@ -85,57 +86,57 @@ public class InstancedGeometry extends Geometry {
         setBatchHint(BatchHint.Never);
         setBatchHint(BatchHint.Never);
         setMaxNumInstances(1);
         setMaxNumInstances(1);
     }
     }
-    
+
     /**
     /**
-     * Global user specified per-instance data. 
-     * 
+     * Global user specified per-instance data.
+     *
      * By default set to <code>null</code>, specify an array of VertexBuffers
      * By default set to <code>null</code>, specify an array of VertexBuffers
      * via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
      * via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
-     * 
-     * @return global user specified per-instance data. 
-     * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) 
+     *
+     * @return global user specified per-instance data.
+     * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
      */
      */
     public VertexBuffer[] getGlobalUserInstanceData() {
     public VertexBuffer[] getGlobalUserInstanceData() {
         return globalInstanceData;
         return globalInstanceData;
     }
     }
-    
+
     /**
     /**
      * Specify global user per-instance data.
      * Specify global user per-instance data.
-     * 
+     *
      * By default set to <code>null</code>, specify an array of VertexBuffers
      * By default set to <code>null</code>, specify an array of VertexBuffers
      * that contain per-instance vertex attributes.
      * that contain per-instance vertex attributes.
-     * 
+     *
      * @param globalInstanceData global user per-instance data.
      * @param globalInstanceData global user per-instance data.
-     * 
-     * @throws IllegalArgumentException If one of the VertexBuffers is not 
+     *
+     * @throws IllegalArgumentException If one of the VertexBuffers is not
      * {@link VertexBuffer#setInstanced(boolean) instanced}.
      * {@link VertexBuffer#setInstanced(boolean) instanced}.
      */
      */
     public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
     public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
         this.globalInstanceData = globalInstanceData;
         this.globalInstanceData = globalInstanceData;
     }
     }
-    
+
     /**
     /**
      * Specify camera specific user per-instance data.
      * Specify camera specific user per-instance data.
-     * 
+     *
      * @param transformInstanceData The transforms for each instance.
      * @param transformInstanceData The transforms for each instance.
      */
      */
     public void setTransformUserInstanceData(VertexBuffer transformInstanceData) {
     public void setTransformUserInstanceData(VertexBuffer transformInstanceData) {
         this.transformInstanceData = transformInstanceData;
         this.transformInstanceData = transformInstanceData;
     }
     }
-    
+
     /**
     /**
      * Return user per-instance transform data.
      * Return user per-instance transform data.
-     * 
+     *
      * @return The per-instance transform data.
      * @return The per-instance transform data.
      *
      *
-     * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) 
+     * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer)
      */
      */
     public VertexBuffer getTransformUserInstanceData() {
     public VertexBuffer getTransformUserInstanceData() {
         return transformInstanceData;
         return transformInstanceData;
     }
     }
-    
-    private void updateInstance(Matrix4f worldMatrix, float[] store, 
-                                int offset, Matrix3f tempMat3, 
+
+    private void updateInstance(Matrix4f worldMatrix, float[] store,
+                                int offset, Matrix3f tempMat3,
                                 Quaternion tempQuat) {
                                 Quaternion tempQuat) {
         worldMatrix.toRotationMatrix(tempMat3);
         worldMatrix.toRotationMatrix(tempMat3);
         tempMat3.invertLocal();
         tempMat3.invertLocal();
@@ -164,17 +165,17 @@ public class InstancedGeometry extends Geometry {
         store[offset + 14] = worldMatrix.m23;
         store[offset + 14] = worldMatrix.m23;
         store[offset + 15] = tempQuat.getW();
         store[offset + 15] = tempQuat.getW();
     }
     }
-    
+
     /**
     /**
      * Set the maximum amount of instances that can be rendered by this
      * Set the maximum amount of instances that can be rendered by this
      * instanced geometry when mode is set to auto.
      * instanced geometry when mode is set to auto.
-     * 
+     *
      * This re-allocates internal structures and therefore should be called
      * This re-allocates internal structures and therefore should be called
-     * only when necessary. 
-     * 
+     * only when necessary.
+     *
      * @param maxNumInstances The maximum number of instances that can be
      * @param maxNumInstances The maximum number of instances that can be
      * rendered.
      * rendered.
-     * 
+     *
      * @throws IllegalStateException If mode is set to manual.
      * @throws IllegalStateException If mode is set to manual.
      * @throws IllegalArgumentException If maxNumInstances is zero or negative
      * @throws IllegalArgumentException If maxNumInstances is zero or negative
      */
      */
@@ -182,14 +183,14 @@ public class InstancedGeometry extends Geometry {
         if (maxNumInstances < 1) {
         if (maxNumInstances < 1) {
             throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
             throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
         }
         }
-        
+
         Geometry[] originalGeometries = geometries;
         Geometry[] originalGeometries = geometries;
         this.geometries = new Geometry[maxNumInstances];
         this.geometries = new Geometry[maxNumInstances];
-        
+
         if (originalGeometries != null) {
         if (originalGeometries != null) {
             System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length);
             System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length);
         }
         }
-        
+
         // Resize instance data.
         // Resize instance data.
         if (transformInstanceData != null) {
         if (transformInstanceData != null) {
             BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
             BufferUtils.destroyDirectBuffer(transformInstanceData.getData());
@@ -203,7 +204,7 @@ public class InstancedGeometry extends Geometry {
                     BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
                     BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE));
         }
         }
     }
     }
-    
+
     public int getMaxNumInstances() {
     public int getMaxNumInstances() {
         return geometries.length;
         return geometries.length;
     }
     }
@@ -211,12 +212,12 @@ public class InstancedGeometry extends Geometry {
     public int getActualNumInstances() {
     public int getActualNumInstances() {
         return firstUnusedIndex;
         return firstUnusedIndex;
     }
     }
-    
+
     private void swap(int idx1, int idx2) {
     private void swap(int idx1, int idx2) {
         Geometry g = geometries[idx1];
         Geometry g = geometries[idx1];
         geometries[idx1] = geometries[idx2];
         geometries[idx1] = geometries[idx2];
         geometries[idx2] = g;
         geometries[idx2] = g;
-        
+
         if (geometries[idx1] != null) {
         if (geometries[idx1] != null) {
             InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1);
             InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1);
         }
         }
@@ -224,7 +225,7 @@ public class InstancedGeometry extends Geometry {
             InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2);
             InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2);
         }
         }
     }
     }
-    
+
     private void sanitize(boolean insideEntriesNonNull) {
     private void sanitize(boolean insideEntriesNonNull) {
         if (firstUnusedIndex >= geometries.length) {
         if (firstUnusedIndex >= geometries.length) {
             throw new AssertionError();
             throw new AssertionError();
@@ -234,7 +235,7 @@ public class InstancedGeometry extends Geometry {
                 if (geometries[i] == null) {
                 if (geometries[i] == null) {
                     if (insideEntriesNonNull) {
                     if (insideEntriesNonNull) {
                         throw new AssertionError();
                         throw new AssertionError();
-                    }  
+                    }
                 } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) {
                 } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) {
                     throw new AssertionError();
                     throw new AssertionError();
                 }
                 }
@@ -245,55 +246,55 @@ public class InstancedGeometry extends Geometry {
             }
             }
         }
         }
     }
     }
-    
+
     public void updateInstances() {
     public void updateInstances() {
         FloatBuffer fb = (FloatBuffer) transformInstanceData.getData();
         FloatBuffer fb = (FloatBuffer) transformInstanceData.getData();
         fb.limit(fb.capacity());
         fb.limit(fb.capacity());
         fb.position(0);
         fb.position(0);
-        
+
         TempVars vars = TempVars.get();
         TempVars vars = TempVars.get();
         {
         {
             float[] temp = vars.matrixWrite;
             float[] temp = vars.matrixWrite;
-            
+
             for (int i = 0; i < firstUnusedIndex; i++) {
             for (int i = 0; i < firstUnusedIndex; i++) {
                 Geometry geom = geometries[i];
                 Geometry geom = geometries[i];
 
 
                 if (geom == null) {
                 if (geom == null) {
                     geom = geometries[firstUnusedIndex - 1];
                     geom = geometries[firstUnusedIndex - 1];
-                    
+
                     if (geom == null) {
                     if (geom == null) {
                         throw new AssertionError();
                         throw new AssertionError();
                     }
                     }
-                    
+
                     swap(i, firstUnusedIndex - 1);
                     swap(i, firstUnusedIndex - 1);
-                    
+
                     while (geometries[firstUnusedIndex -1] == null) {
                     while (geometries[firstUnusedIndex -1] == null) {
                         firstUnusedIndex--;
                         firstUnusedIndex--;
                     }
                     }
                 }
                 }
-                
+
                 Matrix4f worldMatrix = geom.getWorldMatrix();
                 Matrix4f worldMatrix = geom.getWorldMatrix();
                 updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1);
                 updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1);
                 fb.put(temp);
                 fb.put(temp);
             }
             }
         }
         }
         vars.release();
         vars.release();
-        
+
         fb.flip();
         fb.flip();
-        
+
         if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) {
         if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) {
             throw new AssertionError();
             throw new AssertionError();
         }
         }
 
 
         transformInstanceData.updateData(fb);
         transformInstanceData.updateData(fb);
     }
     }
-    
+
     public void deleteInstance(Geometry geom) {
     public void deleteInstance(Geometry geom) {
         int idx = InstancedNode.getGeometryStartIndex2(geom);
         int idx = InstancedNode.getGeometryStartIndex2(geom);
         InstancedNode.setGeometryStartIndex2(geom, -1);
         InstancedNode.setGeometryStartIndex2(geom, -1);
-        
+
         geometries[idx] = null;
         geometries[idx] = null;
-        
+
         if (idx == firstUnusedIndex - 1) {
         if (idx == firstUnusedIndex - 1) {
             // Deleting the last element.
             // Deleting the last element.
             // Move index back.
             // Move index back.
@@ -309,12 +310,12 @@ public class InstancedGeometry extends Geometry {
             // Deleting element in the middle
             // Deleting element in the middle
         }
         }
     }
     }
-    
+
     public void addInstance(Geometry geometry) {
     public void addInstance(Geometry geometry) {
         if (geometry == null) {
         if (geometry == null) {
             throw new IllegalArgumentException("geometry cannot be null");
             throw new IllegalArgumentException("geometry cannot be null");
         }
         }
-       
+
         // Take an index from the end.
         // Take an index from the end.
         if (firstUnusedIndex + 1 >= geometries.length) {
         if (firstUnusedIndex + 1 >= geometries.length) {
             // No more room.
             // No more room.
@@ -323,15 +324,15 @@ public class InstancedGeometry extends Geometry {
 
 
         int freeIndex = firstUnusedIndex;
         int freeIndex = firstUnusedIndex;
         firstUnusedIndex++;
         firstUnusedIndex++;
-        
+
         geometries[freeIndex] = geometry;
         geometries[freeIndex] = geometry;
         InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
         InstancedNode.setGeometryStartIndex2(geometry, freeIndex);
     }
     }
-    
+
     public Geometry[] getGeometries() {
     public Geometry[] getGeometries() {
         return geometries;
         return geometries;
     }
     }
-    
+
     public VertexBuffer[] getAllInstanceData() {
     public VertexBuffer[] getAllInstanceData() {
         ArrayList<VertexBuffer> allData = new ArrayList();
         ArrayList<VertexBuffer> allData = new ArrayList();
         if (transformInstanceData != null) {
         if (transformInstanceData != null) {
@@ -343,6 +344,16 @@ public class InstancedGeometry extends Geometry {
         return allData.toArray(new VertexBuffer[allData.size()]);
         return allData.toArray(new VertexBuffer[allData.size()]);
     }
     }
 
 
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.globalInstanceData = cloner.clone(globalInstanceData);
+        this.transformInstanceData = cloner.clone(transformInstanceData);
+        this.geometries = cloner.clone(geometries);
+    }
+
     @Override
     @Override
     public void write(JmeExporter exporter) throws IOException {
     public void write(JmeExporter exporter) throws IOException {
         super.write(exporter);
         super.write(exporter);
@@ -350,7 +361,7 @@ public class InstancedGeometry extends Geometry {
         //capsule.write(currentNumInstances, "cur_num_instances", 1);
         //capsule.write(currentNumInstances, "cur_num_instances", 1);
         capsule.write(geometries, "geometries", null);
         capsule.write(geometries, "geometries", null);
     }
     }
-    
+
     @Override
     @Override
     public void read(JmeImporter importer) throws IOException {
     public void read(JmeImporter importer) throws IOException {
         super.read(importer);
         super.read(importer);

+ 94 - 41
jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java

@@ -44,20 +44,23 @@ import com.jme3.scene.control.Control;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.material.MatParam;
 import com.jme3.material.MatParam;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.Map;
 
 
 public class InstancedNode extends GeometryGroupNode {
 public class InstancedNode extends GeometryGroupNode {
-    
+
     static int getGeometryStartIndex2(Geometry geom) {
     static int getGeometryStartIndex2(Geometry geom) {
         return getGeometryStartIndex(geom);
         return getGeometryStartIndex(geom);
     }
     }
-    
+
     static void setGeometryStartIndex2(Geometry geom, int startIndex) {
     static void setGeometryStartIndex2(Geometry geom, int startIndex) {
         setGeometryStartIndex(geom, startIndex);
         setGeometryStartIndex(geom, startIndex);
     }
     }
-    
-    private static final class InstanceTypeKey implements Cloneable {
+
+    private static final class InstanceTypeKey implements Cloneable, JmeCloneable {
 
 
         Mesh mesh;
         Mesh mesh;
         Material material;
         Material material;
@@ -68,7 +71,7 @@ public class InstancedNode extends GeometryGroupNode {
             this.material = material;
             this.material = material;
             this.lodLevel = lodLevel;
             this.lodLevel = lodLevel;
         }
         }
-        
+
         public InstanceTypeKey(){
         public InstanceTypeKey(){
         }
         }
 
 
@@ -95,7 +98,7 @@ public class InstancedNode extends GeometryGroupNode {
             }
             }
             return true;
             return true;
         }
         }
-        
+
         @Override
         @Override
         public InstanceTypeKey clone() {
         public InstanceTypeKey clone() {
             try {
             try {
@@ -104,65 +107,94 @@ public class InstancedNode extends GeometryGroupNode {
                 throw new AssertionError();
                 throw new AssertionError();
             }
             }
         }
         }
+
+        @Override
+        public Object jmeClone() {
+            try {
+                return super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new AssertionError();
+            }
+        }
+
+        @Override
+        public void cloneFields( Cloner cloner, Object original ) {
+            this.mesh = cloner.clone(mesh);
+            this.material = cloner.clone(material);
+        }
     }
     }
-    
-    private static class InstancedNodeControl implements Control {
+
+    private static class InstancedNodeControl implements Control, JmeCloneable {
 
 
         private InstancedNode node;
         private InstancedNode node;
-        
+
         public InstancedNodeControl() {
         public InstancedNodeControl() {
         }
         }
-        
+
         public InstancedNodeControl(InstancedNode node) {
         public InstancedNodeControl(InstancedNode node) {
             this.node = node;
             this.node = node;
         }
         }
-        
+
         @Override
         @Override
         public Control cloneForSpatial(Spatial spatial) {
         public Control cloneForSpatial(Spatial spatial) {
-            return this; 
+            return this;
             // WARNING: Sets wrong control on spatial. Will be
             // WARNING: Sets wrong control on spatial. Will be
             // fixed automatically by InstancedNode.clone() method.
             // 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){
         public void setSpatial(Spatial spatial){
         }
         }
-        
+
         public void update(float tpf){
         public void update(float tpf){
         }
         }
-        
+
         public void render(RenderManager rm, ViewPort vp) {
         public void render(RenderManager rm, ViewPort vp) {
             node.renderFromControl();
             node.renderFromControl();
         }
         }
-        
+
         public void write(JmeExporter ex) throws IOException {
         public void write(JmeExporter ex) throws IOException {
         }
         }
 
 
         public void read(JmeImporter im) throws IOException {
         public void read(JmeImporter im) throws IOException {
         }
         }
     }
     }
-    
+
     protected InstancedNodeControl control;
     protected InstancedNodeControl control;
-    
-    protected HashMap<Geometry, InstancedGeometry> igByGeom 
+
+    protected HashMap<Geometry, InstancedGeometry> igByGeom
             = new HashMap<Geometry, InstancedGeometry>();
             = new HashMap<Geometry, InstancedGeometry>();
-    
+
     private InstanceTypeKey lookUp = new InstanceTypeKey();
     private InstanceTypeKey lookUp = new InstanceTypeKey();
-    
-    private HashMap<InstanceTypeKey, InstancedGeometry> instancesMap = 
+
+    private HashMap<InstanceTypeKey, InstancedGeometry> instancesMap =
             new HashMap<InstanceTypeKey, InstancedGeometry>();
             new HashMap<InstanceTypeKey, InstancedGeometry>();
-    
+
     public InstancedNode() {
     public InstancedNode() {
         super();
         super();
         // NOTE: since we are deserializing,
         // NOTE: since we are deserializing,
         // the control is going to be added automatically here.
         // the control is going to be added automatically here.
     }
     }
-    
+
     public InstancedNode(String name) {
     public InstancedNode(String name) {
         super(name);
         super(name);
         control = new InstancedNodeControl(this);
         control = new InstancedNodeControl(this);
         addControl(control);
         addControl(control);
     }
     }
-    
+
     private void renderFromControl() {
     private void renderFromControl() {
         for (InstancedGeometry ig : instancesMap.values()) {
         for (InstancedGeometry ig : instancesMap.values()) {
             ig.updateInstances();
             ig.updateInstances();
@@ -191,7 +223,7 @@ public class InstancedNode extends GeometryGroupNode {
 
 
         return ig;
         return ig;
     }
     }
-    
+
     private void addToInstancedGeometry(Geometry geom) {
     private void addToInstancedGeometry(Geometry geom) {
         Material material = geom.getMaterial();
         Material material = geom.getMaterial();
         MatParam param = material.getParam("UseInstancing");
         MatParam param = material.getParam("UseInstancing");
@@ -200,20 +232,20 @@ public class InstancedNode extends GeometryGroupNode {
                     + "parameter to true on the material prior "
                     + "parameter to true on the material prior "
                     + "to adding it to InstancedNode");
                     + "to adding it to InstancedNode");
         }
         }
-        
+
         InstancedGeometry ig = lookUpByGeometry(geom);
         InstancedGeometry ig = lookUpByGeometry(geom);
         igByGeom.put(geom, ig);
         igByGeom.put(geom, ig);
         geom.associateWithGroupNode(this, 0);
         geom.associateWithGroupNode(this, 0);
         ig.addInstance(geom);
         ig.addInstance(geom);
     }
     }
-    
+
     private void removeFromInstancedGeometry(Geometry geom) {
     private void removeFromInstancedGeometry(Geometry geom) {
         InstancedGeometry ig = igByGeom.remove(geom);
         InstancedGeometry ig = igByGeom.remove(geom);
         if (ig != null) {
         if (ig != null) {
             ig.deleteInstance(geom);
             ig.deleteInstance(geom);
         }
         }
     }
     }
-    
+
     private void relocateInInstancedGeometry(Geometry geom) {
     private void relocateInInstancedGeometry(Geometry geom) {
         InstancedGeometry oldIG = igByGeom.get(geom);
         InstancedGeometry oldIG = igByGeom.get(geom);
         InstancedGeometry newIG = lookUpByGeometry(geom);
         InstancedGeometry newIG = lookUpByGeometry(geom);
@@ -226,7 +258,7 @@ public class InstancedNode extends GeometryGroupNode {
             igByGeom.put(geom, newIG);
             igByGeom.put(geom, newIG);
         }
         }
     }
     }
-    
+
     private void ungroupSceneGraph(Spatial s) {
     private void ungroupSceneGraph(Spatial s) {
         if (s instanceof Node) {
         if (s instanceof Node) {
             for (Spatial sp : ((Node) s).getChildren()) {
             for (Spatial sp : ((Node) s).getChildren()) {
@@ -237,14 +269,14 @@ public class InstancedNode extends GeometryGroupNode {
             if (g.isGrouped()) {
             if (g.isGrouped()) {
                 // Will invoke onGeometryUnassociated automatically.
                 // Will invoke onGeometryUnassociated automatically.
                 g.unassociateFromGroupNode();
                 g.unassociateFromGroupNode();
-                
+
                 if (InstancedNode.getGeometryStartIndex(g) != -1) {
                 if (InstancedNode.getGeometryStartIndex(g) != -1) {
                     throw new AssertionError();
                     throw new AssertionError();
                 }
                 }
             }
             }
         }
         }
     }
     }
-    
+
     @Override
     @Override
     public Spatial detachChildAt(int index) {
     public Spatial detachChildAt(int index) {
         Spatial s = super.detachChildAt(index);
         Spatial s = super.detachChildAt(index);
@@ -253,7 +285,7 @@ public class InstancedNode extends GeometryGroupNode {
         }
         }
         return s;
         return s;
     }
     }
-    
+
     private void instance(Spatial n) {
     private void instance(Spatial n) {
         if (n instanceof Geometry) {
         if (n instanceof Geometry) {
             Geometry g = (Geometry) n;
             Geometry g = (Geometry) n;
@@ -269,20 +301,20 @@ public class InstancedNode extends GeometryGroupNode {
             }
             }
         }
         }
     }
     }
-    
+
     public void instance() {
     public void instance() {
         instance(this);
         instance(this);
     }
     }
-    
+
     @Override
     @Override
     public Node clone() {
     public Node clone() {
         return clone(true);
         return clone(true);
     }
     }
-    
+
     @Override
     @Override
     public Node clone(boolean cloneMaterials) {
     public Node clone(boolean cloneMaterials) {
         InstancedNode clone = (InstancedNode)super.clone(cloneMaterials);
         InstancedNode clone = (InstancedNode)super.clone(cloneMaterials);
-        
+
         if (instancesMap.size() > 0) {
         if (instancesMap.size() > 0) {
             // Remove all instanced geometries from the clone
             // Remove all instanced geometries from the clone
             for (int i = 0; i < clone.children.size(); i++) {
             for (int i = 0; i < clone.children.size(); i++) {
@@ -296,7 +328,7 @@ public class InstancedNode extends GeometryGroupNode {
                 }
                 }
             }
             }
         }
         }
-        
+
         // remove original control from the clone
         // remove original control from the clone
         clone.controls.remove(this.control);
         clone.controls.remove(this.control);
 
 
@@ -307,12 +339,33 @@ public class InstancedNode extends GeometryGroupNode {
         clone.lookUp = new InstanceTypeKey();
         clone.lookUp = new InstanceTypeKey();
         clone.igByGeom = new HashMap<Geometry, InstancedGeometry>();
         clone.igByGeom = new HashMap<Geometry, InstancedGeometry>();
         clone.instancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
         clone.instancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
-        
+
         clone.instance();
         clone.instance();
-        
+
         return clone;
         return clone;
     }
     }
-    
+
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.control = cloner.clone(control);
+        this.lookUp = cloner.clone(lookUp);
+
+        HashMap<Geometry, InstancedGeometry> newIgByGeom = new HashMap<Geometry, InstancedGeometry>();
+        for( Map.Entry<Geometry, InstancedGeometry> e : igByGeom.entrySet() ) {
+            newIgByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
+        }
+        this.igByGeom = newIgByGeom;
+
+        HashMap<InstanceTypeKey, InstancedGeometry> newInstancesMap = new HashMap<InstanceTypeKey, InstancedGeometry>();
+        for( Map.Entry<InstanceTypeKey, InstancedGeometry> e : instancesMap.entrySet() ) {
+            newInstancesMap.put(cloner.clone(e.getKey()), cloner.clone(e.getValue()));
+        }
+        this.instancesMap = newInstancesMap;
+    }
+
     @Override
     @Override
     public void onTransformChange(Geometry geom) {
     public void onTransformChange(Geometry geom) {
         // Handled automatically
         // Handled automatically

+ 46 - 1
jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java

@@ -37,6 +37,7 @@ import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.OutputCapsule;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
+import com.jme3.material.RenderState;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Vector4f;
 import com.jme3.math.Vector4f;
 import com.jme3.post.Filter;
 import com.jme3.post.Filter;
@@ -44,6 +45,7 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.ViewPort;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.FrameBuffer;
+
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
@@ -74,6 +76,9 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
         material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");       
         material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md");       
         this.shadowRenderer = shadowRenderer;
         this.shadowRenderer = shadowRenderer;
         this.shadowRenderer.setPostShadowMaterial(material);
         this.shadowRenderer.setPostShadowMaterial(material);
+
+        //this is legacy setting for shadows with backface shadows
+        this.shadowRenderer.setRenderBackFacesShadows(true);
     }
     }
 
 
     @Override
     @Override
@@ -126,7 +131,7 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
       /**
       /**
      * How far the shadows are rendered in the view
      * How far the shadows are rendered in the view
      *
      *
-     * @see setShadowZExtend(float zFar)
+     * @see #setShadowZExtend(float zFar)
      * @return shadowZExtend
      * @return shadowZExtend
      */
      */
     public float getShadowZExtend() {
     public float getShadowZExtend() {
@@ -248,6 +253,46 @@ public abstract class AbstractShadowFilter<T extends AbstractShadowRenderer> ext
         shadowRenderer.setEdgeFilteringMode(filterMode);
         shadowRenderer.setEdgeFilteringMode(filterMode);
     }
     }
 
 
+    /**
+     *
+     * !! WARNING !! this parameter is defaulted to true for the ShadowFilter.
+     * Setting it to true, may produce edges artifacts on shadows.     *
+     *
+     * Set to true if you want back faces shadows on geometries.
+     * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
+     *
+     * Setting this parameter will override this parameter for ALL materials in the scene.
+     * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
+     * You can modify them by using {@link #getPreShadowForcedRenderState()}
+     *
+     * If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead
+     * of the shadow filter.
+     *
+     * @param renderBackFacesShadows true or false.
+     */
+    public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
+        shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows);
+    }
+
+    /**
+     * if this filter renders back faces shadows
+     * @return true if this filter renders back faces shadows
+     */
+    public boolean isRenderBackFacesShadows() {
+        return shadowRenderer.isRenderBackFacesShadows();
+    }
+
+    /**
+     * returns the pre shadows pass render state.
+     * use it to adjust the RenderState parameters of the pre shadow pass.
+     * Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState
+     * @return the pre shadow render state.
+     */
+    public RenderState getPreShadowForcedRenderState() {
+        return shadowRenderer.getPreShadowForcedRenderState();
+    }
+
+
     /**
     /**
      * returns the the edge filtering mode
      * returns the the edge filtering mode
      *
      *

+ 85 - 20
jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java

@@ -31,10 +31,6 @@
  */
  */
 package com.jme3.shadow;
 package com.jme3.shadow;
 
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeExporter;
@@ -42,6 +38,7 @@ import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.Savable;
 import com.jme3.export.Savable;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
+import com.jme3.material.RenderState;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector2f;
@@ -67,6 +64,10 @@ import com.jme3.texture.Texture.ShadowCompareMode;
 import com.jme3.texture.Texture2D;
 import com.jme3.texture.Texture2D;
 import com.jme3.ui.Picture;
 import com.jme3.ui.Picture;
 
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
 /**
  * abstract shadow renderer that holds commons feature to have for a shadow
  * abstract shadow renderer that holds commons feature to have for a shadow
  * renderer
  * renderer
@@ -92,6 +93,9 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
     protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear;
     protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear;
     protected CompareMode shadowCompareMode = CompareMode.Hardware;
     protected CompareMode shadowCompareMode = CompareMode.Hardware;
     protected Picture[] dispPic;
     protected Picture[] dispPic;
+    protected RenderState forcedRenderState = new RenderState();
+    protected Boolean renderBackFacesShadows;
+
     /**
     /**
      * true if the fallback material should be used, otherwise false
      * true if the fallback material should be used, otherwise false
      */
      */
@@ -181,6 +185,14 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
         setShadowCompareMode(shadowCompareMode);
         setShadowCompareMode(shadowCompareMode);
         setEdgeFilteringMode(edgeFilteringMode);
         setEdgeFilteringMode(edgeFilteringMode);
         setShadowIntensity(shadowIntensity);
         setShadowIntensity(shadowIntensity);
+        initForcedRenderState();
+    }
+
+    protected void initForcedRenderState() {
+        forcedRenderState.setFaceCullMode(RenderState.FaceCullMode.Front);
+        forcedRenderState.setColorWrite(false);
+        forcedRenderState.setDepthWrite(true);
+        forcedRenderState.setDepthTest(true);
     }
     }
 
 
     /**
     /**
@@ -356,9 +368,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
      * rendered in the shadow map
      * rendered in the shadow map
      *
      *
      * @param shadowMapIndex the index of the shadow map being rendered
      * @param shadowMapIndex the index of the shadow map being rendered
-     * @param sceneOccluders the occluders of the whole scene
-     * @param sceneReceivers the receivers of the whole scene
-     * @param shadowMapOcculders
+     * @param shadowMapOccluders the list of occluders
      * @return
      * @return
      */
      */
     protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders);
     protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders);
@@ -425,9 +435,11 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
 
 
         renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]);
         renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]);
         renderManager.getRenderer().clearBuffers(true, true, true);
         renderManager.getRenderer().clearBuffers(true, true, true);
+        renderManager.setForcedRenderState(forcedRenderState);
 
 
         // render shadow casters to shadow map
         // render shadow casters to shadow map
         viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true);
         viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true);
+        renderManager.setForcedRenderState(null);
     }
     }
     boolean debugfrustums = false;
     boolean debugfrustums = false;
 
 
@@ -535,18 +547,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
     private void setMatParams(GeometryList l) {
     private void setMatParams(GeometryList l) {
         //iteration throught all the geometries of the list to gather the materials
         //iteration throught all the geometries of the list to gather the materials
 
 
-        matCache.clear();
-        for (int i = 0; i < l.size(); i++) {
-            Material mat = l.get(i).getMaterial();
-            //checking if the material has the post technique and adding it to the material cache
-            if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
-                if (!matCache.contains(mat)) {
-                    matCache.add(mat);
-                }
-            } else {
-                needsfallBackMaterial = true;
-            }
-        }
+        buildMatCache(l);
 
 
         //iterating through the mat cache and setting the parameters
         //iterating through the mat cache and setting the parameters
         for (Material mat : matCache) {
         for (Material mat : matCache) {
@@ -566,6 +567,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
             if (fadeInfo != null) {
             if (fadeInfo != null) {
                mat.setVector2("FadeInfo", fadeInfo);
                mat.setVector2("FadeInfo", fadeInfo);
             }
             }
+            if(renderBackFacesShadows != null){
+                mat.setBoolean("BackfaceShadows", renderBackFacesShadows);
+            }
+
             setMaterialParameters(mat);
             setMaterialParameters(mat);
         }
         }
 
 
@@ -577,6 +582,21 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
 
 
     }
     }
 
 
+    private void buildMatCache(GeometryList l) {
+        matCache.clear();
+        for (int i = 0; i < l.size(); i++) {
+            Material mat = l.get(i).getMaterial();
+            //checking if the material has the post technique and adding it to the material cache
+            if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
+                if (!matCache.contains(mat)) {
+                    matCache.add(mat);
+                }
+            } else {
+                needsfallBackMaterial = true;
+            }
+        }
+    }
+
     /**
     /**
      * for internal use only
      * for internal use only
      */
      */
@@ -587,7 +607,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
             postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]);
             postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]);
         }
         }
         if (fadeInfo != null) {
         if (fadeInfo != null) {
-              postshadowMat.setVector2("FadeInfo", fadeInfo);
+            postshadowMat.setVector2("FadeInfo", fadeInfo);
+        }
+        if(renderBackFacesShadows != null){
+            postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows);
         }
         }
     }
     }
     
     
@@ -730,6 +753,48 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
     @Deprecated
     @Deprecated
     public void setFlushQueues(boolean flushQueues) {}
     public void setFlushQueues(boolean flushQueues) {}
 
 
+
+    /**
+     * returns the pre shadows pass render state.
+     * use it to adjust the RenderState parameters of the pre shadow pass.
+     * Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState
+     * @return the pre shadow render state.
+     */
+    public RenderState getPreShadowForcedRenderState() {
+        return forcedRenderState;
+    }
+
+    /**
+     * Set to true if you want back faces shadows on geometries.
+     * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
+     *
+     * Also note that setting this parameter will override this parameter for ALL materials in the scene.
+     * You can alternatively change this parameter on a single material using {@link Material#setBoolean(String, boolean)}
+     *
+     * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass.
+     * You can modify them by using {@link #getPreShadowForcedRenderState()}
+     *
+     * @param renderBackFacesShadows true or false.
+     */
+    public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) {
+        this.renderBackFacesShadows = renderBackFacesShadows;
+        if(renderBackFacesShadows) {
+            getPreShadowForcedRenderState().setPolyOffset(5, 3);
+            getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back);
+        }else{
+            getPreShadowForcedRenderState().setPolyOffset(0, 0);
+            getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front);
+        }
+    }
+
+    /**
+     * if this processor renders back faces shadows
+     * @return true if this processor renders back faces shadows
+     */
+    public boolean isRenderBackFacesShadows() {
+        return renderBackFacesShadows != null?renderBackFacesShadows:false;
+    }
+
     /**
     /**
      * De-serialize this instance, for example when loading from a J3O file.
      * De-serialize this instance, for example when loading from a J3O file.
      *
      *

+ 2 - 0
jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java

@@ -215,6 +215,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
     @Override
     @Override
     protected void setMaterialParameters(Material material) {
     protected void setMaterialParameters(Material material) {
         material.setColor("Splits", splits);
         material.setColor("Splits", splits);
+        material.setVector3("LightDir", light.getDirection());
         if (fadeInfo != null) {
         if (fadeInfo != null) {
             material.setVector2("FadeInfo", fadeInfo);
             material.setVector2("FadeInfo", fadeInfo);
         }
         }
@@ -224,6 +225,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer {
     protected void clearMaterialParameters(Material material) {
     protected void clearMaterialParameters(Material material) {
         material.clearParam("Splits");
         material.clearParam("Splits");
         material.clearParam("FadeInfo");
         material.clearParam("FadeInfo");
+        material.clearParam("LightDir");
     }
     }
 
 
     /**
     /**

+ 2 - 0
jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java

@@ -157,6 +157,8 @@ public abstract class JmeSystemDelegate {
             return false;
             return false;
         } else if (arch.equals("aarch64")) {
         } else if (arch.equals("aarch64")) {
             return true;
             return true;
+        } else if (arch.equals("armv7") || arch.equals("armv7l")) {
+            return false;
         } else if (arch.equals("arm")) {
         } else if (arch.equals("arm")) {
             return false;
             return false;
         } else {
         } else {

+ 48 - 11
jme3-core/src/main/java/com/jme3/util/IntMap.java

@@ -32,19 +32,21 @@
 package com.jme3.util;
 package com.jme3.util;
 
 
 import com.jme3.util.IntMap.Entry;
 import com.jme3.util.IntMap.Entry;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.util.Iterator;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.NoSuchElementException;
 
 
 /**
 /**
  * Similar to a {@link Map} except that ints are used as keys.
  * Similar to a {@link Map} except that ints are used as keys.
- * 
+ *
  * Taken from <a href="http://code.google.com/p/skorpios/">http://code.google.com/p/skorpios/</a>
  * Taken from <a href="http://code.google.com/p/skorpios/">http://code.google.com/p/skorpios/</a>
- * 
- * @author Nate 
+ *
+ * @author Nate
  */
  */
-public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
-            
+public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable, JmeCloneable {
+
     private Entry[] table;
     private Entry[] table;
     private final float loadFactor;
     private final float loadFactor;
     private int size, mask, capacity, threshold;
     private int size, mask, capacity, threshold;
@@ -93,6 +95,26 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
         return null;
         return null;
     }
     }
 
 
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public Object jmeClone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException ex) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     *  Called internally by com.jme3.util.clone.Cloner.  Do not call directly.
+     */
+    @Override
+    public void cloneFields( Cloner cloner, Object original ) {
+        this.table = cloner.clone(table);
+    }
+
     public boolean containsValue(Object value) {
     public boolean containsValue(Object value) {
         Entry[] table = this.table;
         Entry[] table = this.table;
         for (int i = table.length; i-- > 0;){
         for (int i = table.length; i-- > 0;){
@@ -228,7 +250,7 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
             idx = 0;
             idx = 0;
             el = 0;
             el = 0;
         }
         }
-        
+
         public boolean hasNext() {
         public boolean hasNext() {
             return el < size;
             return el < size;
         }
         }
@@ -255,20 +277,20 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
                 // the entry was null. find another non-null entry.
                 // the entry was null. find another non-null entry.
                 cur = table[++idx];
                 cur = table[++idx];
             } while (cur == null);
             } while (cur == null);
-            
+
             Entry e = cur;
             Entry e = cur;
             cur = cur.next;
             cur = cur.next;
             el ++;
             el ++;
-            
+
             return e;
             return e;
         }
         }
 
 
         public void remove() {
         public void remove() {
         }
         }
-        
+
     }
     }
-    
-    public static final class Entry<T> implements Cloneable {
+
+    public static final class Entry<T> implements Cloneable, JmeCloneable {
 
 
         final int key;
         final int key;
         T value;
         T value;
@@ -303,5 +325,20 @@ public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
             }
             }
             return null;
             return null;
         }
         }
+
+        @Override
+        public Object jmeClone() {
+            try {
+                return super.clone();
+            } catch (CloneNotSupportedException ex) {
+                throw new AssertionError();
+            }
+        }
+
+        @Override
+        public void cloneFields( Cloner cloner, Object original ) {
+            this.value = cloner.clone(value);
+            this.next = cloner.clone(next);
+        }
     }
     }
 }
 }

+ 81 - 0
jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ *  Provides custom cloning for a particular object type.  Once
+ *  registered with the Cloner, this function object will be called twice
+ *  for any cloned object that matches the class for which it was registered.
+ *  It will first call cloneObject() to shallow clone the object and then call
+ *  cloneFields()  to deep clone the object's values.
+ *
+ *  <p>This two step process is important because this is what allows
+ *  circular references in the cloned object graph.</p>
+ *
+ *  @author    Paul Speed
+ */
+public interface CloneFunction<T> {
+
+    /**
+     *  Performs a shallow clone of the specified object.  This is similar
+     *  to the JmeCloneable.clone() method in semantics and is the first part
+     *  of a two part cloning process.  Once the shallow clone is created, it
+     *  is cached and CloneFunction.cloneFields() is called.  In this way, 
+     *  the CloneFunction interface can completely take over the JmeCloneable
+     *  style cloning for an object that doesn't otherwise implement that interface.
+     *
+     *  @param cloner The cloner performing the cloning operation.
+     *  @param original The original object that needs to be cloned.
+     */
+    public T cloneObject( Cloner cloner, T original );
+ 
+ 
+    /**
+     *  Performs a deep clone of the specified clone's fields.  This is similar
+     *  to the JmeCloneable.cloneFields() method in semantics and is the second part
+     *  of a two part cloning process.  Once the shallow clone is created, it
+     *  is cached and CloneFunction.cloneFields() is called.  In this way, 
+     *  the CloneFunction interface can completely take over the JmeCloneable
+     *  style cloning for an object that doesn't otherwise implement that interface.
+     * 
+     *  @param cloner The cloner performing the cloning operation.
+     *  @param clone The clone previously returned from cloneObject().
+     *  @param original The original object that was cloned.  This is provided for
+     *              the very special case where field cloning needs to refer to
+     *              the original object.  Mostly the necessary fields should already
+     *              be on the clone.
+     */
+    public void cloneFields( Cloner cloner, T clone, T original );
+     
+}

+ 348 - 0
jme3-core/src/main/java/com/jme3/util/clone/Cloner.java

@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *  A deep clone utility that provides similar object-graph-preserving
+ *  qualities to typical serialization schemes.  An internal registry
+ *  of cloned objects is kept to be used by other objects in the deep
+ *  clone process that implement JmeCloneable.
+ *
+ *  <p>By default, objects that do not implement JmeCloneable will
+ *  be treated like normal Java Cloneable objects.  If the object does
+ *  not implement the JmeCloneable or the regular JDK Cloneable interfaces
+ *  AND has no special handling defined then an IllegalArgumentException 
+ *  will be thrown.</p>
+ *
+ *  <p>Enhanced object cloning is done in a two step process.  First,
+ *  the object is cloned using the normal Java clone() method and stored
+ *  in the clone registry.  After that, if it implements JmeCloneable then
+ *  its cloneFields() method is called to deep clone any of the fields.
+ *  This two step process has a few benefits.  First, it means that objects
+ *  can easily have a regular shallow clone implementation just like any
+ *  normal Java objects.  Second, the deep cloning of fields happens after
+ *  creation wich means that the clone is available to future field cloning
+ *  to resolve circular references.</p> 
+ *
+ *  <p>Similar to Java serialization, the handling of specific object
+ *  types can be customized.  This allows certain objects to be cloned gracefully
+ *  even if they aren't normally Cloneable.  This can also be used as a
+ *  sort of filter to keep certain types of objects from being cloned.
+ *  (For example, adding the IdentityCloneFunction for Mesh.class would cause
+ *  all mesh instances to be shared with the original object graph.)</p>
+ *
+ *  <p>By default, the Cloner registers serveral default clone functions
+ *  as follows:</p>
+ *  <ul>
+ *  <li>java.util.ArrayList: ListCloneFunction
+ *  <li>java.util.LinkedList: ListCloneFunction
+ *  <li>java.util.concurrent.CopyOnWriteArrayList: ListCloneFunction
+ *  <li>java.util.Vector: ListCloneFunction
+ *  <li>java.util.Stack: ListCloneFunction
+ *  <li>com.jme3.util.SafeArrayList: ListCloneFunction
+ *  </ul>
+ *
+ *  <p>Usage:</p>
+ *  <pre>
+ *  // Example 1: using an instantiated, reusable cloner.
+ *  Cloner cloner = new Cloner();
+ *  Foo fooClone = cloner.clone(foo);
+ *  cloner.clearIndex(); // prepare it for reuse
+ *  Foo fooClone2 = cloner.clone(foo);
+ * 
+ *  // Example 2: using the utility method that self-instantiates a temporary cloner.
+ *  Foo fooClone = Cloner.deepClone(foo);
+ *   
+ *  </pre>
+ *
+ *  @author    Paul Speed
+ */
+public class Cloner {
+ 
+    /**
+     *  Keeps track of the objects that have been cloned so far.
+     */   
+    private IdentityHashMap<Object, Object> index = new IdentityHashMap<Object, Object>();
+ 
+    /**
+     *  Custom functions for cloning objects.
+     */
+    private Map<Class, CloneFunction> functions = new HashMap<Class, CloneFunction>();
+ 
+    /**
+     *  Cache the clone methods once for all cloners.
+     */   
+    private static final Map<Class, Method> methodCache = new ConcurrentHashMap<>();
+ 
+    /**
+     *  Creates a new cloner with only default clone functions and an empty
+     *  object index.
+     */
+    public Cloner() {
+        // Register some standard types
+        ListCloneFunction listFunction = new ListCloneFunction();
+        functions.put(java.util.ArrayList.class, listFunction);
+        functions.put(java.util.LinkedList.class, listFunction);
+        functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction); 
+        functions.put(java.util.Vector.class, listFunction);
+        functions.put(java.util.Stack.class, listFunction);
+        functions.put(com.jme3.util.SafeArrayList.class, listFunction);
+    }
+ 
+    /**
+     *  Convenience utility function that creates a new Cloner, uses it to
+     *  deep clone the object, and then returns the result.
+     */
+    public static <T> T deepClone( T object ) {
+        return new Cloner().clone(object);
+    }     
+ 
+    /**
+     *  Deeps clones the specified object, reusing previous clones when possible.
+     * 
+     *  <p>Object cloning priority works as follows:</p>
+     *  <ul>
+     *  <li>If the object has already been cloned then its clone is returned.
+     *  <li>If there is a custom CloneFunction then it is called to clone the object.
+     *  <li>If the object implements Cloneable then its clone() method is called, arrays are 
+     *      deep cloned with entries passing through clone().
+     *  <li>If the object implements JmeCloneable then its cloneFields() method is called on the
+     *      clone.
+     *  <li>Else an IllegalArgumentException is thrown. 
+     *  </ul>
+     *
+     *  Note: objects returned by this method may not have yet had their cloneField()
+     *  method called.
+     */   
+    public <T> T clone( T object ) {
+        return clone(object, true);
+    }
+ 
+    /**
+     *  Internal method to work around a Java generics typing issue by
+     *  isolating the 'bad' case into a method with suppressed warnings.
+     */
+    @SuppressWarnings("unchecked")
+    private <T> Class<T> objectClass( T object ) {
+        // This should be 100% allowed without a cast but Java generics
+        // is not that smart sometimes.
+        // Wrapping it in a method at least isolates the warning suppression
+        return (Class<T>)object.getClass();
+    }
+ 
+    /**
+     *  Deeps clones the specified object, reusing previous clones when possible.
+     * 
+     *  <p>Object cloning priority works as follows:</p>
+     *  <ul>
+     *  <li>If the object has already been cloned then its clone is returned.
+     *  <li>If useFunctions is true and there is a custom CloneFunction then it is 
+     *      called to clone the object.
+     *  <li>If the object implements Cloneable then its clone() method is called, arrays are 
+     *      deep cloned with entries passing through clone().
+     *  <li>If the object implements JmeCloneable then its cloneFields() method is called on the
+     *      clone.
+     *  <li>Else an IllegalArgumentException is thrown. 
+     *  </ul>
+     *
+     *  <p>The abililty to selectively use clone functions is useful when
+     *  being called from a clone function.</p>
+     *
+     *  Note: objects returned by this method may not have yet had their cloneField()
+     *  method called.
+     */   
+    public <T> T clone( T object, boolean useFunctions ) {
+        if( object == null ) {
+            return null;
+        }
+        Class<T> type = objectClass(object); 
+        
+        // Check the index to see if we already have it
+        Object clone = index.get(object);
+        if( clone != null ) {
+            return type.cast(clone); 
+        }
+        
+        // See if there is a custom function... that trumps everything.
+        CloneFunction<T> f = getCloneFunction(type); 
+        if( f != null ) {
+            T result = f.cloneObject(this, object);
+            
+            // Store the object in the identity map so that any circular references
+            // are resolvable. 
+            index.put(object, result); 
+            
+            // Now call the function again to deep clone the fields
+            f.cloneFields(this, result, object);
+            
+            return result;           
+        }
+ 
+        if( object.getClass().isArray() ) {
+            // Perform an array clone        
+            clone = arrayClone(object);
+            
+            // Array clone already indexes the clone
+        } else if( object instanceof JmeCloneable ) {
+            // Use the two-step cloning semantics
+            clone = ((JmeCloneable)object).jmeClone();
+            
+            // Store the object in the identity map so that any circular references
+            // are resolvable
+            index.put(object, clone); 
+            
+            ((JmeCloneable)clone).cloneFields(this, object);
+        } else if( object instanceof Cloneable ) {
+            
+            // Perform a regular Java shallow clone
+            try {
+                clone = javaClone(object);
+            } catch( CloneNotSupportedException e ) {
+                throw new IllegalArgumentException("Object is not cloneable, type:" + type, e);
+            }
+            
+            // Store the object in the identity map so that any circular references
+            // are resolvable
+            index.put(object, clone); 
+        } else {
+            throw new IllegalArgumentException("Object is not cloneable, type:" + type);
+        }
+        
+        return type.cast(clone);
+    }
+ 
+    /**
+     *  Sets a custom CloneFunction for the exact Java type.  Note: no inheritence
+     *  checks are performed so a function must be registered for each specific type
+     *  that it handles.  By default ListCloneFunction is registered for 
+     *  ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList.
+     */
+    public <T> void setCloneFunction( Class<T> type, CloneFunction<T> function ) {
+        if( function == null ) {
+            functions.remove(type);
+        } else {
+            functions.put(type, function);
+        }
+    }
+ 
+    /**
+     *  Returns a previously registered clone function for the specified type or null
+     *  if there is no custom clone function for the type.
+     */ 
+    @SuppressWarnings("unchecked")
+    public <T> CloneFunction<T> getCloneFunction( Class<T> type ) {
+        return (CloneFunction<T>)functions.get(type); 
+    } 
+ 
+    /**
+     *  Clears the object index allowing the cloner to be reused for a brand new
+     *  cloning operation.
+     */
+    public void clearIndex() {
+        index.clear();
+    }     
+ 
+    /**
+     *  Performs a raw shallow Java clone using reflection.  This call does NOT
+     *  check against the clone index and so will return new objects every time
+     *  it is called.  That's because these are shallow clones and have not (and may
+     *  not ever, depending on the caller) get resolved.
+     *
+     *  <p>This method is provided as a convenient way for CloneFunctions to call
+     *  clone() and objects without necessarily knowing their real type.</p>  
+     */   
+    public <T> T javaClone( T object ) throws CloneNotSupportedException {
+        Method m = methodCache.get(object.getClass());
+        if( m == null ) {
+            try {
+                // Lookup the method and cache it
+                m = object.getClass().getMethod("clone");
+            } catch( NoSuchMethodException e ) {            
+                throw new CloneNotSupportedException("No public clone method found for:" + object.getClass());
+            }
+            methodCache.put(object.getClass(), m);
+            
+            // Note: yes we might cache the method twice... but so what?
+        }
+ 
+        try {
+            Class<? extends T> type = objectClass(object);       
+            return type.cast(m.invoke(object));
+        } catch( IllegalAccessException | InvocationTargetException e ) {
+            throw new RuntimeException("Error cloning object of type:" + object.getClass(), e);
+        }          
+    }
+    
+    /**
+     *  Clones a primitive array by coping it and clones an object
+     *  array by coping it and then running each of its values through
+     *  Cloner.clone().
+     */
+    protected <T> T arrayClone( T object ) {
+ 
+        // Java doesn't support the cloning of arrays through reflection unless
+        // you open access to Object's protected clone array... which requires
+        // elevated privileges.  So we will do a work-around that is slightly less
+        // elegant.
+        // This should be 100% allowed without a case but Java generics
+        // is not that smart
+        Class<T> type = objectClass(object); 
+        Class elementType = type.getComponentType();
+        int size = Array.getLength(object); 
+        Object clone = Array.newInstance(elementType, size);
+ 
+        // Store the clone for later lookups
+        index.put(object, clone); 
+        
+        if( elementType.isPrimitive() ) {
+            // Then our job is a bit easier
+            System.arraycopy(object, 0, clone, 0, size);
+        } else {
+            // Else it's an object array so we'll clone it and its children
+            for( int i = 0; i < size; i++ ) {
+                Object element = clone(Array.get(object, i));
+                Array.set(clone, i, element);
+            }
+        }           
+        
+        return type.cast(clone);
+    }
+}

+ 58 - 0
jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java

@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ *  A CloneFunction implementation that simply returns the 
+ *  the passed object without cloning it.  This is useful for
+ *  forcing some object types (like Meshes) to be shared between
+ *  the original and cloned object graph.
+ *
+ *  @author    Paul Speed
+ */
+public class IdentityCloneFunction<T> implements CloneFunction<T> {
+
+    /**
+     *  Returns the object directly.
+     */
+    public T cloneObject( Cloner cloner, T object ) {
+        return object;
+    }
+ 
+    /**
+     *  Does nothing.
+     */    
+    public void cloneFields( Cloner cloner, T clone, T object ) {
+    }
+}

+ 99 - 0
jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+
+/**
+ *  Indicates an object that wishes to more actively participate in the
+ *  two-part deep copying process provided by the Cloner.  Objects implementing
+ *  this interface can access the already cloned object graph to resolve
+ *  their local dependencies in a way that will be equivalent to the
+ *  original object graph.  In other words, if two objects in the graph
+ *  share the same target reference then the cloned version will share
+ *  the cloned reference. 
+ *  
+ *  <p>For example, if an object wishes to deep clone one of its fields
+ *  then it will call cloner.clone(object) instead of object.clone().
+ *  The cloner will keep track of any clones already created for 'object'
+ *  and return that instead of a new clone.</p>
+ *
+ *  <p>Cloning of a JmeCloneable object is done in two parts.  First,
+ *  the standard Java clone() method is called to create a shallow clone
+ *  of the object.  Second, the cloner wil lcall the cloneFields() method
+ *  to let the object deep clone any of its fields that should be cloned.</p>
+ *
+ *  <p>This two part process is necessary to facilitate circular references.
+ *  When an object calls cloner.clone() during its cloneFields() method, it
+ *  may get only a shallow copy that will be filled in later.</p>
+ *
+ *  @author    Paul Speed
+ */
+public interface JmeCloneable extends Cloneable {
+
+    /**
+     *  Performs a regular shallow clone of the object.  Some fields
+     *  may also be cloned but generally only if they will never be
+     *  shared with other objects.  (For example, local Vector3fs and so on.)
+     *
+     *  <p>This method is separate from the regular clone() method
+     *  so that objects might still maintain their own regular java clone()
+     *  semantics (perhaps even using Cloner for those methods).  However,
+     *  because Java's clone() has specific features in the sense of Object's
+     *  clone() implementation, it's usually best to have some path for
+     *  subclasses to bypass the public clone() method that might be cloning
+     *  fields and instead get at the superclass protected clone() methods.
+     *  For example, through super.jmeClone() or another protected clone
+     *  method that some base class eventually calls super.clone() in.</p>
+     */
+    public Object jmeClone();     
+
+    /**
+     *  Implemented to perform deep cloning for this object, resolving
+     *  local cloned references using the specified cloner.  The object
+     *  can call cloner.clone(fieldValue) to deep clone any of its fields.
+     * 
+     *  <p>Note: during normal clone operations the original object
+     *  will not be needed as the clone has already had all of the fields
+     *  shallow copied.</p>
+     *
+     *  @param cloner The cloner that is performing the cloning operation.  The 
+     *              cloneFields method can call back into the cloner to make
+     *              clones if its subordinate fields.     
+     *  @param original The original object from which this object was cloned.
+     *              This is provided for the very rare case that this object needs
+     *              to refer to its original for some reason.  In general, all of
+     *              the relevant values should have been transferred during the
+     *              shallow clone and this object need merely clone what it wants.
+     */
+    public void cloneFields( Cloner cloner, Object original ); 
+}

+ 70 - 0
jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.clone;
+
+import java.util.List;
+
+/**
+ *  A CloneFunction implementation that deep clones a list by
+ *  creating a new list and cloning its values using the cloner.
+ *
+ *  @author    Paul Speed
+ */
+public class ListCloneFunction<T extends List> implements CloneFunction<T> {
+
+    public T cloneObject( Cloner cloner, T object ) {         
+        try {
+            T clone = cloner.javaClone(object);         
+            return clone;
+        } catch( CloneNotSupportedException e ) {
+            throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e);
+        }
+    }
+     
+    /**
+     *  Clones the elements of the list.
+     */    
+    @SuppressWarnings("unchecked")
+    public void cloneFields( Cloner cloner, T clone, T object ) {
+        for( int i = 0; i < clone.size(); i++ ) {
+            // Need to clone the clones... because T might
+            // have done something special in its clone method that
+            // we will have to adhere to.  For example, clone may have nulled
+            // out some things or whatever that might be implementation specific.
+            // At any rate, if it's a proper clone then the clone will already
+            // have shallow versions of the elements that we can clone.
+            clone.set(i, cloner.clone(clone.get(i)));
+        }
+    }
+}
+

+ 7 - 10
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md

@@ -113,6 +113,8 @@ MaterialDef Phong Lighting {
                 
                 
         //For instancing
         //For instancing
         Boolean UseInstancing
         Boolean UseInstancing
+
+        Boolean BackfaceShadows: false
     }
     }
 
 
  Technique {
  Technique {
@@ -214,26 +216,19 @@ MaterialDef Phong Lighting {
             INSTANCING : UseInstancing
             INSTANCING : UseInstancing
         }
         }
 
 
-        ForcedRenderState {
-            FaceCull Off
-            DepthTest On
-            DepthWrite On
-            PolyOffset 5 3
-            ColorWrite Off
-        }
-
     }
     }
 
 
 
 
     Technique PostShadow15{
     Technique PostShadow15{
-        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow15.vert
-        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
+        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow.vert
+        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
 
 
         WorldParameters {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldViewProjectionMatrix
             WorldMatrix
             WorldMatrix
             ViewProjectionMatrix
             ViewProjectionMatrix
             ViewMatrix
             ViewMatrix
+            NormalMatrix
         }
         }
 
 
         Defines {
         Defines {
@@ -248,6 +243,7 @@ MaterialDef Phong Lighting {
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
             INSTANCING : UseInstancing
+            BACKFACE_SHADOWS: BackfaceShadows
         }
         }
 
 
         ForcedRenderState {
         ForcedRenderState {
@@ -266,6 +262,7 @@ MaterialDef Phong Lighting {
             WorldMatrix
             WorldMatrix
             ViewProjectionMatrix
             ViewProjectionMatrix
             ViewMatrix
             ViewMatrix
+            NormalMatrix
         }
         }
 
 
         Defines {
         Defines {

+ 2 - 3
jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag

@@ -41,8 +41,7 @@ varying vec3 SpecularSum;
   
   
 #ifdef NORMALMAP
 #ifdef NORMALMAP
   uniform sampler2D m_NormalMap;   
   uniform sampler2D m_NormalMap;   
-  varying vec3 vTangent;
-  varying vec3 vBinormal;
+  varying vec4 vTangent;
 #endif
 #endif
 varying vec3 vNormal;
 varying vec3 vNormal;
 
 
@@ -71,7 +70,7 @@ uniform float m_Shininess;
 void main(){
 void main(){
     #if !defined(VERTEX_LIGHTING)
     #if !defined(VERTEX_LIGHTING)
         #if defined(NORMALMAP)
         #if defined(NORMALMAP)
-            mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz));
+             mat3 tbnMat = mat3(vTangent.xyz, vTangent.w * cross( (vNormal), (vTangent.xyz)), vNormal.xyz);
 
 
             if (!gl_FrontFacing)
             if (!gl_FrontFacing)
             {
             {

+ 2 - 4
jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert

@@ -39,8 +39,7 @@ attribute vec3 inNormal;
     varying vec3 vPos;
     varying vec3 vPos;
     #ifdef NORMALMAP
     #ifdef NORMALMAP
         attribute vec4 inTangent;
         attribute vec4 inTangent;
-        varying vec3 vTangent;
-        varying vec3 vBinormal;
+        varying vec4 vTangent;
     #endif
     #endif
 #else
 #else
     #ifdef COLORRAMP
     #ifdef COLORRAMP
@@ -104,8 +103,7 @@ void main(){
   
   
        
        
     #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
     #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
-      vTangent = TransformNormal(modelSpaceTan);
-      vBinormal = cross(wvNormal, vTangent)* inTangent.w;      
+      vTangent = vec4(TransformNormal(modelSpaceTan).xyz,inTangent.w);
       vNormal = wvNormal;         
       vNormal = wvNormal;         
       vPos = wvPosition;
       vPos = wvPosition;
     #elif !defined(VERTEX_LIGHTING)
     #elif !defined(VERTEX_LIGHTING)

+ 6 - 2
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md

@@ -51,6 +51,8 @@ MaterialDef Unshaded {
         Float PCFEdge
         Float PCFEdge
 
 
         Float ShadowMapSize
         Float ShadowMapSize
+
+        Boolean BackfaceShadows: true
     }
     }
 
 
     Technique {
     Technique {
@@ -147,8 +149,8 @@ MaterialDef Unshaded {
 
 
 
 
     Technique PostShadow15{
     Technique PostShadow15{
-        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow15.vert
-        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
+        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow.vert
+        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
 
 
         WorldParameters {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldViewProjectionMatrix
@@ -169,6 +171,7 @@ MaterialDef Unshaded {
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
             NUM_BONES : NumberOfBones
 	    INSTANCING : UseInstancing
 	    INSTANCING : UseInstancing
+	        BACKFACE_SHADOWS: BackfaceShadows
         }
         }
 
 
         ForcedRenderState {
         ForcedRenderState {
@@ -201,6 +204,7 @@ MaterialDef Unshaded {
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
             NUM_BONES : NumberOfBones
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
             INSTANCING : UseInstancing
+            BACKFACE_SHADOWS: BackfaceShadows
         }
         }
 
 
         ForcedRenderState {
         ForcedRenderState {

+ 14 - 4
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag

@@ -1,4 +1,5 @@
 #import "Common/ShaderLib/Shadows.glsllib"
 #import "Common/ShaderLib/Shadows.glsllib"
+#import "Common/ShaderLib/GLSLCompat.glsllib"
 
 
 #if defined(PSSM) || defined(FADE)
 #if defined(PSSM) || defined(FADE)
 varying float shadowPosition;
 varying float shadowPosition;
@@ -8,6 +9,9 @@ varying vec4 projCoord0;
 varying vec4 projCoord1;
 varying vec4 projCoord1;
 varying vec4 projCoord2;
 varying vec4 projCoord2;
 varying vec4 projCoord3;
 varying vec4 projCoord3;
+#ifndef BACKFACE_SHADOWS
+    varying float nDotL;
+#endif
 
 
 #ifdef POINTLIGHT
 #ifdef POINTLIGHT
     varying vec4 projCoord4;
     varying vec4 projCoord4;
@@ -45,9 +49,15 @@ void main(){
         if(alpha<=m_AlphaDiscardThreshold){
         if(alpha<=m_AlphaDiscardThreshold){
             discard;
             discard;
         }
         }
+    #endif
 
 
+    #ifndef BACKFACE_SHADOWS
+        if(nDotL > 0.0){
+            discard;
+        }
     #endif
     #endif
-     
+
+
     float shadow = 1.0;
     float shadow = 1.0;
  
  
     #ifdef POINTLIGHT         
     #ifdef POINTLIGHT         
@@ -70,11 +80,11 @@ void main(){
     #endif   
     #endif   
 
 
     #ifdef FADE
     #ifdef FADE
-      shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));    
+        shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));
     #endif
     #endif
-    shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
 
 
-  gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
+    shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity);
+    gl_FragColor = vec4(shadow, shadow, shadow, 1.0);
 
 
 }
 }
 
 

+ 6 - 2
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md

@@ -29,12 +29,14 @@ MaterialDef Post Shadow {
         Float PCFEdge
         Float PCFEdge
 
 
         Float ShadowMapSize
         Float ShadowMapSize
+
+        Boolean BackfaceShadows: false
 		
 		
     }
     }
 
 
     Technique {
     Technique {
-        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow15.vert
-        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag
+        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow.vert
+        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
 
 
         WorldParameters {
         WorldParameters {
             WorldViewProjectionMatrix
             WorldViewProjectionMatrix
@@ -49,6 +51,7 @@ MaterialDef Post Shadow {
             FADE : FadeInfo
             FADE : FadeInfo
             PSSM : Splits
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
+            BACKFACE_SHADOWS: BackfaceShadows
         }
         }
 
 
         RenderState {
         RenderState {
@@ -75,6 +78,7 @@ MaterialDef Post Shadow {
             FADE : FadeInfo
             FADE : FadeInfo
             PSSM : Splits
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
+            BACKFACE_SHADOWS: BackfaceShadows
         }
         }
 
 
         RenderState {
         RenderState {

+ 27 - 7
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert

@@ -1,11 +1,12 @@
 #import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Instancing.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
 #import "Common/ShaderLib/Skinning.glsllib"
+#import "Common/ShaderLib/GLSLCompat.glsllib"
+
 uniform mat4 m_LightViewProjectionMatrix0;
 uniform mat4 m_LightViewProjectionMatrix0;
 uniform mat4 m_LightViewProjectionMatrix1;
 uniform mat4 m_LightViewProjectionMatrix1;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix3;
 uniform mat4 m_LightViewProjectionMatrix3;
 
 
-uniform vec3 m_LightPos; 
 
 
 varying vec4 projCoord0;
 varying vec4 projCoord0;
 varying vec4 projCoord1;
 varying vec4 projCoord1;
@@ -15,12 +16,14 @@ varying vec4 projCoord3;
 #ifdef POINTLIGHT
 #ifdef POINTLIGHT
     uniform mat4 m_LightViewProjectionMatrix4;
     uniform mat4 m_LightViewProjectionMatrix4;
     uniform mat4 m_LightViewProjectionMatrix5;
     uniform mat4 m_LightViewProjectionMatrix5;
+    uniform vec3 m_LightPos;
     varying vec4 projCoord4;
     varying vec4 projCoord4;
     varying vec4 projCoord5;
     varying vec4 projCoord5;
     varying vec4 worldPos;
     varying vec4 worldPos;
 #else
 #else
+    uniform vec3 m_LightDir;
     #ifndef PSSM
     #ifndef PSSM
-        uniform vec3 m_LightDir; 
+        uniform vec3 m_LightPos;
         varying float lightDot;
         varying float lightDot;
     #endif
     #endif
 #endif
 #endif
@@ -28,12 +31,15 @@ varying vec4 projCoord3;
 #if defined(PSSM) || defined(FADE)
 #if defined(PSSM) || defined(FADE)
 varying float shadowPosition;
 varying float shadowPosition;
 #endif
 #endif
-varying vec3 lightVec;
 
 
 varying vec2 texCoord;
 varying vec2 texCoord;
-
 attribute vec3 inPosition;
 attribute vec3 inPosition;
 
 
+#ifndef BACKFACE_SHADOWS
+    attribute vec3 inNormal;
+    varying float nDotL;
+#endif
+
 #ifdef DISCARD_ALPHA
 #ifdef DISCARD_ALPHA
     attribute vec2 inTexCoord;
     attribute vec2 inTexCoord;
 #endif
 #endif
@@ -51,16 +57,17 @@ void main(){
        Skinning_Compute(modelSpacePos);
        Skinning_Compute(modelSpacePos);
    #endif
    #endif
     gl_Position = TransformWorldViewProjection(modelSpacePos);
     gl_Position = TransformWorldViewProjection(modelSpacePos);
+    vec3 lightDir;
 
 
     #if defined(PSSM) || defined(FADE)
     #if defined(PSSM) || defined(FADE)
-         shadowPosition = gl_Position.z;
+        shadowPosition = gl_Position.z;
     #endif  
     #endif  
 
 
     #ifndef POINTLIGHT
     #ifndef POINTLIGHT
         vec4 worldPos=vec4(0.0);
         vec4 worldPos=vec4(0.0);
     #endif
     #endif
     // get the vertex in world space
     // get the vertex in world space
-    worldPos = g_WorldMatrix * modelSpacePos;
+    worldPos = TransformWorld(modelSpacePos);
 
 
     #ifdef DISCARD_ALPHA
     #ifdef DISCARD_ALPHA
        texCoord = inTexCoord;
        texCoord = inTexCoord;
@@ -75,8 +82,21 @@ void main(){
         projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
         projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
     #else
     #else
         #ifndef PSSM
         #ifndef PSSM
-            vec3 lightDir = worldPos.xyz - m_LightPos;
+            //Spot light
+            lightDir = worldPos.xyz - m_LightPos;
             lightDot = dot(m_LightDir,lightDir);
             lightDot = dot(m_LightDir,lightDir);
         #endif
         #endif
     #endif
     #endif
+
+    #ifndef BACKFACE_SHADOWS
+        vec3 normal = normalize(TransformWorld(vec4(inNormal,0.0))).xyz;
+        #ifdef POINTLIGHT
+            lightDir = worldPos.xyz - m_LightPos;
+        #else
+            #ifdef PSSM
+               lightDir = m_LightDir;
+            #endif
+        #endif
+        nDotL = dot(normal, lightDir);
+    #endif
 }
 }

+ 0 - 80
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag

@@ -1,80 +0,0 @@
-#import "Common/ShaderLib/Shadows15.glsllib"
-
-out vec4 outFragColor;
-
-#if defined(PSSM) || defined(FADE)
-in float shadowPosition;
-#endif
-
-in vec4 projCoord0;
-in vec4 projCoord1;
-in vec4 projCoord2;
-in vec4 projCoord3;
-
-#ifdef POINTLIGHT
-    in vec4 projCoord4;
-    in vec4 projCoord5;
-    in vec4 worldPos;
-    uniform vec3 m_LightPos; 
-#else
-    #ifndef PSSM        
-        in float lightDot;
-    #endif
-#endif
-
-#ifdef DISCARD_ALPHA
-    #ifdef COLOR_MAP
-        uniform sampler2D m_ColorMap;
-    #else    
-        uniform sampler2D m_DiffuseMap;
-    #endif
-    uniform float m_AlphaDiscardThreshold;
-    varying vec2 texCoord;
-#endif
-
-#ifdef FADE
-uniform vec2 m_FadeInfo;
-#endif
-
-void main(){
-
-    #ifdef DISCARD_ALPHA
-        #ifdef COLOR_MAP
-             float alpha = texture2D(m_ColorMap,texCoord).a;
-        #else    
-             float alpha = texture2D(m_DiffuseMap,texCoord).a;
-        #endif
-      
-        if(alpha < m_AlphaDiscardThreshold){
-            discard;
-        }
-    #endif
- 
-    float shadow = 1.0;
-    #ifdef POINTLIGHT         
-            shadow = getPointLightShadows(worldPos, m_LightPos,
-                           m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5,
-                           projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5);
-    #else
-       #ifdef PSSM
-            shadow = getDirectionalLightShadows(m_Splits, shadowPosition,
-                           m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,
-                           projCoord0, projCoord1, projCoord2, projCoord3);
-       #else
-            //spotlight
-            if(lightDot < 0){
-                outFragColor =  vec4(1.0);
-                return;
-            }
-            shadow = getSpotLightShadows(m_ShadowMap0,projCoord0);
-       #endif
-    #endif   
- 
-    #ifdef FADE
-            shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y));    
-    #endif
-      
-    shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); 
-    outFragColor =  vec4(shadow, shadow, shadow, 1.0);
-}
-

+ 0 - 82
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert

@@ -1,82 +0,0 @@
-#import "Common/ShaderLib/Instancing.glsllib"
-#import "Common/ShaderLib/Skinning.glsllib"
-uniform mat4 m_LightViewProjectionMatrix0;
-uniform mat4 m_LightViewProjectionMatrix1;
-uniform mat4 m_LightViewProjectionMatrix2;
-uniform mat4 m_LightViewProjectionMatrix3;
-
-
-out vec4 projCoord0;
-out vec4 projCoord1;
-out vec4 projCoord2;
-out vec4 projCoord3;
-
-#ifdef POINTLIGHT
-    uniform mat4 m_LightViewProjectionMatrix4;
-    uniform mat4 m_LightViewProjectionMatrix5;
-    out vec4 projCoord4;
-    out vec4 projCoord5;
-    out vec4 worldPos;
-#else
-    #ifndef PSSM
-        uniform vec3 m_LightPos; 
-        uniform vec3 m_LightDir; 
-        out float lightDot;
-    #endif
-#endif
-
-#if defined(PSSM) || defined(FADE)
-    out float shadowPosition;
-#endif
-out vec3 lightVec;
-
-out vec2 texCoord;
-
-in vec3 inPosition;
-
-#ifdef DISCARD_ALPHA
-    in vec2 inTexCoord;
-#endif
-
-const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
-                          0.0, 0.5, 0.0, 0.0,
-                          0.0, 0.0, 0.5, 0.0,
-                          0.5, 0.5, 0.5, 1.0);
-
-
-void main(){
-   vec4 modelSpacePos = vec4(inPosition, 1.0);
-  
-   #ifdef NUM_BONES
-       Skinning_Compute(modelSpacePos);
-   #endif
-    gl_Position =  TransformWorldViewProjection(modelSpacePos);
-    
-    #if defined(PSSM) || defined(FADE)
-        shadowPosition = gl_Position.z;
-    #endif
-
-    #ifndef POINTLIGHT        
-        vec4 worldPos=vec4(0.0);
-    #endif
-    // get the vertex in world space
-    worldPos = TransformWorld(modelSpacePos);
-
-    #ifdef DISCARD_ALPHA
-       texCoord = inTexCoord;
-    #endif
-    // populate the light view matrices array and convert vertex to light viewProj space
-    projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos;
-    projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos;
-    projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos;
-    projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos;
-    #ifdef POINTLIGHT
-        projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos;
-        projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos;
-    #else        
-        #ifndef PSSM
-            vec3 lightDir = worldPos.xyz - m_LightPos;
-            lightDot = dot(m_LightDir,lightDir);
-        #endif
-    #endif
-}

+ 31 - 2
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag

@@ -18,14 +18,16 @@ uniform mat4 m_LightViewProjectionMatrix1;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix3;
 uniform mat4 m_LightViewProjectionMatrix3;
 
 
+uniform vec2 g_ResolutionInverse;
+
 #ifdef POINTLIGHT
 #ifdef POINTLIGHT
     uniform vec3 m_LightPos;
     uniform vec3 m_LightPos;
     uniform mat4 m_LightViewProjectionMatrix4;
     uniform mat4 m_LightViewProjectionMatrix4;
     uniform mat4 m_LightViewProjectionMatrix5;
     uniform mat4 m_LightViewProjectionMatrix5;
 #else
 #else
+    uniform vec3 m_LightDir;
     #ifndef PSSM    
     #ifndef PSSM    
-        uniform vec3 m_LightPos;    
-        uniform vec3 m_LightDir;       
+        uniform vec3 m_LightPos;
     #endif
     #endif
 #endif
 #endif
 
 
@@ -39,6 +41,19 @@ vec3 getPosition(in float depth, in vec2 uv){
     return pos.xyz / pos.w;
     return pos.xyz / pos.w;
 }
 }
 
 
+vec3 approximateNormal(in vec4 worldPos,in vec2 texCoord){
+    float step = g_ResolutionInverse.x ;
+    float stepy = g_ResolutionInverse.y ;
+    float depth2 = texture2D(m_DepthTexture,texCoord + vec2(step,-stepy)).r;
+    float depth3 = texture2D(m_DepthTexture,texCoord + vec2(-step,-stepy)).r;
+    vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,-stepy)),1.0);
+    vec4 worldPos3 = vec4(getPosition(depth3,texCoord + vec2(-step,-stepy)),1.0);
+
+    vec3 v1 = (worldPos - worldPos2).xyz;
+    vec3 v2 = (worldPos3 - worldPos2).xyz;
+    return normalize(cross(v1, v2));
+}
+
 void main(){    
 void main(){    
     #if !defined( RENDER_SHADOWS )
     #if !defined( RENDER_SHADOWS )
           gl_FragColor = texture2D(m_Texture,texCoord);
           gl_FragColor = texture2D(m_Texture,texCoord);
@@ -48,6 +63,7 @@ void main(){
     float depth = texture2D(m_DepthTexture,texCoord).r;
     float depth = texture2D(m_DepthTexture,texCoord).r;
     vec4 color = texture2D(m_Texture,texCoord);
     vec4 color = texture2D(m_Texture,texCoord);
 
 
+
     //Discard shadow computation on the sky
     //Discard shadow computation on the sky
     if(depth == 1.0){
     if(depth == 1.0){
         gl_FragColor = color;
         gl_FragColor = color;
@@ -56,6 +72,19 @@ void main(){
 
 
     // get the vertex in world space
     // get the vertex in world space
     vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
     vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
+    vec3 normal = approximateNormal(worldPos, texCoord);
+
+    vec3 lightDir;
+    #ifdef PSSM
+        lightDir = m_LightDir;
+    #else
+        lightDir = worldPos.xyz - m_LightPos;
+    #endif
+    float ndotl = dot(normal, lightDir);
+    if(ndotl > -0.0){
+        gl_FragColor = color;
+        return;
+    }
    
    
      #if (!defined(POINTLIGHT) && !defined(PSSM))
      #if (!defined(POINTLIGHT) && !defined(PSSM))
           vec3 lightDir = worldPos.xyz - m_LightPos;
           vec3 lightDir = worldPos.xyz - m_LightPos;

+ 7 - 3
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md

@@ -38,13 +38,15 @@ MaterialDef Post Shadow {
         Texture2D Texture        
         Texture2D Texture        
         Texture2D DepthTexture
         Texture2D DepthTexture
 
 
+        Boolean BackfaceShadows: true
     }
     }
 
 
     Technique {
     Technique {
         VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadowFilter15.vert
         VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadowFilter15.vert
         FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag
         FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag
 
 
-        WorldParameters {           
+        WorldParameters {
+            ResolutionInverse
         }
         }
 
 
         Defines {
         Defines {
@@ -59,7 +61,7 @@ MaterialDef Post Shadow {
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
             //if no shadow map don't render shadows
             //if no shadow map don't render shadows
             RENDER_SHADOWS : ShadowMap0
             RENDER_SHADOWS : ShadowMap0
-
+            BACKFACE_SHADOWS : BackfaceShadows
         }
         }
       
       
     }
     }
@@ -68,7 +70,8 @@ MaterialDef Post Shadow {
         VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadowFilter.vert
         VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadowFilter.vert
         FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag
         FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag
 
 
-        WorldParameters {         
+        WorldParameters {
+            ResolutionInverse
         }
         }
 
 
         Defines {
         Defines {
@@ -79,6 +82,7 @@ MaterialDef Post Shadow {
             FADE : FadeInfo
             FADE : FadeInfo
             PSSM : Splits
             PSSM : Splits
             POINTLIGHT : LightViewProjectionMatrix5
             POINTLIGHT : LightViewProjectionMatrix5
+            BACKFACE_SHADOWS : BackfaceShadows
         }
         }
       
       
     }
     }

+ 42 - 8
jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag

@@ -1,5 +1,5 @@
 #import "Common/ShaderLib/MultiSample.glsllib"
 #import "Common/ShaderLib/MultiSample.glsllib"
-#import "Common/ShaderLib/Shadows15.glsllib"
+#import "Common/ShaderLib/Shadows.glsllib"
 
 
 
 
 uniform COLORTEXTURE m_Texture;
 uniform COLORTEXTURE m_Texture;
@@ -20,14 +20,16 @@ uniform mat4 m_LightViewProjectionMatrix1;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix2;
 uniform mat4 m_LightViewProjectionMatrix3;
 uniform mat4 m_LightViewProjectionMatrix3;
 
 
+uniform vec2 g_ResolutionInverse;
+
 #ifdef POINTLIGHT
 #ifdef POINTLIGHT
     uniform vec3 m_LightPos;
     uniform vec3 m_LightPos;
     uniform mat4 m_LightViewProjectionMatrix4;
     uniform mat4 m_LightViewProjectionMatrix4;
     uniform mat4 m_LightViewProjectionMatrix5;
     uniform mat4 m_LightViewProjectionMatrix5;
 #else
 #else
+    uniform vec3 m_LightDir;
     #ifndef PSSM    
     #ifndef PSSM    
-        uniform vec3 m_LightPos;    
-        uniform vec3 m_LightDir;       
+        uniform vec3 m_LightPos;
     #endif
     #endif
 #endif
 #endif
 
 
@@ -41,6 +43,23 @@ vec3 getPosition(in float depth, in vec2 uv){
     return pos.xyz / pos.w;
     return pos.xyz / pos.w;
 }
 }
 
 
+#ifndef BACKFACE_SHADOWS
+    vec3 approximateNormal(in float depth,in vec4 worldPos,in vec2 texCoord, in int numSample){
+        float step = g_ResolutionInverse.x ;
+        float stepy = g_ResolutionInverse.y ;
+        float depth1 = fetchTextureSample(m_DepthTexture,texCoord + vec2(-step,stepy),numSample).r;
+        float depth2 = fetchTextureSample(m_DepthTexture,texCoord + vec2(step,stepy),numSample).r;
+        vec3 v1, v2;
+        vec4 worldPos1 = vec4(getPosition(depth1,texCoord + vec2(-step,stepy)),1.0);
+        vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,stepy)),1.0);
+
+        v1 = normalize((worldPos1 - worldPos)).xyz;
+        v2 =  normalize((worldPos2 - worldPos)).xyz;
+        return normalize(cross(v2, v1));
+
+    }
+#endif
+
 vec4 main_multiSample(in int numSample){
 vec4 main_multiSample(in int numSample){
     float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r;
     float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r;
     vec4 color = fetchTextureSample(m_Texture,texCoord,numSample);
     vec4 color = fetchTextureSample(m_Texture,texCoord,numSample);
@@ -52,12 +71,27 @@ vec4 main_multiSample(in int numSample){
     
     
     // get the vertex in world space
     // get the vertex in world space
     vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
     vec4 worldPos = vec4(getPosition(depth,texCoord),1.0);
-  
+
+
+    vec3 lightDir;
+    #ifdef PSSM
+        lightDir = m_LightDir;
+    #else
+        lightDir = worldPos.xyz - m_LightPos;
+    #endif
+
+    #ifndef BACKFACE_SHADOWS
+        vec3 normal = approximateNormal(depth, worldPos, texCoord, numSample);
+        float ndotl = dot(normal, lightDir);
+        if(ndotl > 0.0){
+            return color;
+        }
+    #endif
+
     #if (!defined(POINTLIGHT) && !defined(PSSM))
     #if (!defined(POINTLIGHT) && !defined(PSSM))
-          vec3 lightDir = worldPos.xyz - m_LightPos;
-          if( dot(m_LightDir,lightDir)<0){
-             return color;
-          }         
+        if( dot(m_LightDir,lightDir)<0){
+         return color;
+        }
     #endif
     #endif
 
 
     // populate the light view matrices array and convert vertex to light viewProj space
     // populate the light view matrices array and convert vertex to light viewProj space

+ 153 - 102
jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib

@@ -1,22 +1,57 @@
-#ifdef HARDWARE_SHADOWS
-    #define SHADOWMAP sampler2DShadow
-    #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r 
-#else
-    #define SHADOWMAP sampler2D
-    #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
-#endif
+#if __VERSION__ >= 130
+    // Because gpu_shader5 is actually where those
+    // gather functions are declared to work on shadowmaps
+    #extension GL_ARB_gpu_shader5 : enable
+    #define IVEC2 ivec2
+    #ifdef HARDWARE_SHADOWS
+        #define SHADOWMAP sampler2DShadow
+        #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
+        #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
+        #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
+    #else
+        #define SHADOWMAP sampler2D
+        #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
+        #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r)
+        #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
+    #endif
 
 
-#if FILTER_MODE == 0
-    #define GETSHADOW Shadow_DoShadowCompare
-    #define KERNEL 1.0
-#elif FILTER_MODE == 1
+    #if FILTER_MODE == 0
+        #define GETSHADOW Shadow_Nearest
+        #define KERNEL 1.0
+    #elif FILTER_MODE == 1
+        #ifdef HARDWARE_SHADOWS
+            #define GETSHADOW Shadow_Nearest
+        #else
+            #define GETSHADOW Shadow_DoBilinear_2x2
+        #endif
+        #define KERNEL 1.0
+    #endif
+#else
+    #define IVEC2 vec2
     #ifdef HARDWARE_SHADOWS
     #ifdef HARDWARE_SHADOWS
-        #define GETSHADOW Shadow_DoShadowCompare
+        #define SHADOWMAP sampler2DShadow
+        #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r
     #else
     #else
-        #define GETSHADOW Shadow_DoBilinear_2x2
+        #define SHADOWMAP sampler2D
+        #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r)
     #endif
     #endif
-    #define KERNEL 1.0
-#elif FILTER_MODE == 2
+
+    #if FILTER_MODE == 0
+        #define GETSHADOW Shadow_DoShadowCompare
+        #define KERNEL 1.0
+    #elif FILTER_MODE == 1
+        #ifdef HARDWARE_SHADOWS
+            #define GETSHADOW Shadow_DoShadowCompare
+        #else
+            #define GETSHADOW Shadow_DoBilinear_2x2
+        #endif
+        #define KERNEL 1.0
+    #endif
+
+
+#endif
+
+#if FILTER_MODE == 2
     #define GETSHADOW Shadow_DoDither_2x2
     #define GETSHADOW Shadow_DoDither_2x2
     #define KERNEL 1.0
     #define KERNEL 1.0
 #elif FILTER_MODE == 3
 #elif FILTER_MODE == 3
@@ -30,14 +65,13 @@
     #define KERNEL 8.0
     #define KERNEL 8.0
 #endif
 #endif
 
 
-
 uniform SHADOWMAP m_ShadowMap0;
 uniform SHADOWMAP m_ShadowMap0;
 uniform SHADOWMAP m_ShadowMap1;
 uniform SHADOWMAP m_ShadowMap1;
 uniform SHADOWMAP m_ShadowMap2;
 uniform SHADOWMAP m_ShadowMap2;
 uniform SHADOWMAP m_ShadowMap3;
 uniform SHADOWMAP m_ShadowMap3;
 #ifdef POINTLIGHT
 #ifdef POINTLIGHT
-uniform SHADOWMAP m_ShadowMap4;
-uniform SHADOWMAP m_ShadowMap5;
+    uniform SHADOWMAP m_ShadowMap4;
+    uniform SHADOWMAP m_ShadowMap5;
 #endif
 #endif
 
 
 #ifdef PSSM
 #ifdef PSSM
@@ -49,73 +83,91 @@ uniform float m_ShadowIntensity;
 const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
 const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
 float shadowBorderScale = 1.0;
 float shadowBorderScale = 1.0;
 
 
-float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){
-    vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
-    return SHADOWCOMPARE(tex, coord);
-}
-
-float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){
+float Shadow_DoShadowCompare(in SHADOWMAP tex,in vec4 projCoord){
     return SHADOWCOMPARE(tex, projCoord);
     return SHADOWCOMPARE(tex, projCoord);
 }
 }
 
 
-float Shadow_BorderCheck(vec2 coord){
+float Shadow_BorderCheck(in vec2 coord){
     // Fastest, "hack" method (uses 4-5 instructions)
     // Fastest, "hack" method (uses 4-5 instructions)
     vec4 t = vec4(coord.xy, 0.0, 1.0);
     vec4 t = vec4(coord.xy, 0.0, 1.0);
     t = step(t.wwxy, t.xyzz);
     t = step(t.wwxy, t.xyzz);
     return dot(t,t);
     return dot(t,t);
 }
 }
 
 
-float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){
+float Shadow_Nearest(in SHADOWMAP tex,in vec4 projCoord){
     float border = Shadow_BorderCheck(projCoord.xy);
     float border = Shadow_BorderCheck(projCoord.xy);
     if (border > 0.0){
     if (border > 0.0){
         return 1.0;
         return 1.0;
     }
     }
-    return Shadow_DoShadowCompare(tex,projCoord);
+    return SHADOWCOMPARE(tex, projCoord);
+}
+
+float Shadow_DoShadowCompareOffset(in SHADOWMAP tex,in vec4 projCoord,in vec2 offset){
+    vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw);
+    return SHADOWCOMPARE(tex, coord);
 }
 }
 
 
-float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){
+
+float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
     float border = Shadow_BorderCheck(projCoord.xy);
     float border = Shadow_BorderCheck(projCoord.xy);
     if (border > 0.0)
     if (border > 0.0)
         return 1.0;
         return 1.0;
-  
 
 
     float shadow = 0.0;
     float shadow = 0.0;
-    vec2 o = mod(floor(gl_FragCoord.xy), 2.0);
-    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5,  1.5) + o);
-    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5,  1.5) + o);
-    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o);
-    shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o);
-    shadow *= 0.25 ;
+    IVEC2 o = IVEC2(mod(floor(gl_FragCoord.xy), 2.0));
+    shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, 1.5)+o));
+    shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, 1.5)+o));
+    shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, -0.5)+o));
+    shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, -0.5)+o));
+    shadow *= 0.25;
     return shadow;
     return shadow;
 }
 }
 
 
-float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){
+float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
     float border = Shadow_BorderCheck(projCoord.xy);
     float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0)
+    if (border > 0.0){
         return 1.0;
         return 1.0;
+    }
+
     vec4 gather = vec4(0.0);
     vec4 gather = vec4(0.0);
-    gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
-    gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
-    gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
-    gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
-
-    vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
-    vec2 mx = mix( gather.xz, gather.yw, f.x );
-    return mix( mx.x, mx.y, f.y );
+    #if __VERSION__ >= 130
+        #ifdef GL_ARB_gpu_shader5
+            vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
+            gather = SHADOWGATHER(tex, coord);
+        #else
+            gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
+            gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
+            gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
+            gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
+        #endif
+    #else
+        gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0));
+        gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0));
+        gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0));
+        gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0));
+    #endif
+
+   vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );
+   vec2 mx = mix( gather.wx, gather.zy, f.x );
+   return mix( mx.x, mx.y, f.y );
 }
 }
 
 
-float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
+float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){
+
     float shadow = 0.0;
     float shadow = 0.0;
     float border = Shadow_BorderCheck(projCoord.xy);
     float border = Shadow_BorderCheck(projCoord.xy);
     if (border > 0.0)
     if (border > 0.0)
         return 1.0;
         return 1.0;
+
     float bound = KERNEL * 0.5 - 0.5;
     float bound = KERNEL * 0.5 - 0.5;
     bound *= PCFEDGE;
     bound *= PCFEDGE;
     for (float y = -bound; y <= bound; y += PCFEDGE){
     for (float y = -bound; y <= bound; y += PCFEDGE){
         for (float x = -bound; x <= bound; x += PCFEDGE){
         for (float x = -bound; x <= bound; x += PCFEDGE){
-            shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) +
-                            border,
-                            0.0, 1.0);
+            #if __VERSION__ < 130
+                shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + border, 0.0, 1.0);
+            #else
+                shadow += Shadow_DoShadowCompareOffset(tex, projCoord, vec2(x,y));
+            #endif
         }
         }
     }
     }
 
 
@@ -123,51 +175,51 @@ float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){
     return shadow;
     return shadow;
 }
 }
 
 
-
 //12 tap poisson disk
 //12 tap poisson disk
-    const vec2 poissonDisk0 =  vec2(-0.1711046, -0.425016);
-    const vec2 poissonDisk1 =  vec2(-0.7829809, 0.2162201);
-    const vec2 poissonDisk2 =  vec2(-0.2380269, -0.8835521);
-    const vec2 poissonDisk3 =  vec2(0.4198045, 0.1687819);
-    const vec2 poissonDisk4 =  vec2(-0.684418, -0.3186957);
-    const vec2 poissonDisk5 =  vec2(0.6026866, -0.2587841);
-    const vec2 poissonDisk6 =  vec2(-0.2412762, 0.3913516);
-    const vec2 poissonDisk7 =  vec2(0.4720655, -0.7664126);
-    const vec2 poissonDisk8 =  vec2(0.9571564, 0.2680693);
-    const vec2 poissonDisk9 =  vec2(-0.5238616, 0.802707);
-    const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
-    const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
-
-float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){   
+const vec2 poissonDisk0 =  vec2(-0.1711046, -0.425016);
+const vec2 poissonDisk1 =  vec2(-0.7829809, 0.2162201);
+const vec2 poissonDisk2 =  vec2(-0.2380269, -0.8835521);
+const vec2 poissonDisk3 =  vec2(0.4198045, 0.1687819);
+const vec2 poissonDisk4 =  vec2(-0.684418, -0.3186957);
+const vec2 poissonDisk5 =  vec2(0.6026866, -0.2587841);
+const vec2 poissonDisk6 =  vec2(-0.2412762, 0.3913516);
+const vec2 poissonDisk7 =  vec2(0.4720655, -0.7664126);
+const vec2 poissonDisk8 =  vec2(0.9571564, 0.2680693);
+const vec2 poissonDisk9 =  vec2(-0.5238616, 0.802707);
+const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
+const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
+
+
+float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){
     float shadow = 0.0;
     float shadow = 0.0;
     float border = Shadow_BorderCheck(projCoord.xy);
     float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0)
+    if (border > 0.0){
         return 1.0;
         return 1.0;
+    }
 
 
-    vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale);        
-    
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize);
-     shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize);
-
-    shadow = shadow * 0.08333333333;//this is divided by 12
-    return shadow;
-}
+    vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;
+
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
+    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
 
 
+    //this is divided by 12
+    return shadow * 0.08333333333;
+}
 
 
-#ifdef POINTLIGHT       
-    float getPointLightShadows(vec4 worldPos,vec3 lightPos,
-                           SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5,
-                           vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){
+#ifdef POINTLIGHT
+    float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
+                           in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
+                           in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
         float shadow = 1.0;
         float shadow = 1.0;
         vec3 vect = worldPos.xyz - lightPos;
         vec3 vect = worldPos.xyz - lightPos;
         vec3 absv= abs(vect);
         vec3 absv= abs(vect);
@@ -190,42 +242,41 @@ float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){
            }else{
            }else{
                shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
                shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
            }
            }
-        }  
+        }
         return shadow;
         return shadow;
     }
     }
 #else
 #else
  #ifdef PSSM
  #ifdef PSSM
-    float getDirectionalLightShadows(vec4 splits,float shadowPosition,
-                                    SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,
-                                    vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){    
-        float shadow = 1.0;   
+    float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
+                                    in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
+                                    in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){
+        float shadow = 1.0;
         if(shadowPosition < splits.x){
         if(shadowPosition < splits.x){
-            shadow = GETSHADOW(shadowMap0, projCoord0 );   
+            shadow = GETSHADOW(shadowMap0, projCoord0 );
         }else if( shadowPosition <  splits.y){
         }else if( shadowPosition <  splits.y){
             shadowBorderScale = 0.5;
             shadowBorderScale = 0.5;
-            shadow = GETSHADOW(shadowMap1, projCoord1);  
+            shadow = GETSHADOW(shadowMap1, projCoord1);
         }else if( shadowPosition <  splits.z){
         }else if( shadowPosition <  splits.z){
             shadowBorderScale = 0.25;
             shadowBorderScale = 0.25;
-            shadow = GETSHADOW(shadowMap2, projCoord2); 
+            shadow = GETSHADOW(shadowMap2, projCoord2);
         }else if( shadowPosition <  splits.w){
         }else if( shadowPosition <  splits.w){
             shadowBorderScale = 0.125;
             shadowBorderScale = 0.125;
-            shadow = GETSHADOW(shadowMap3, projCoord3); 
+            shadow = GETSHADOW(shadowMap3, projCoord3);
         }
         }
         return shadow;
         return shadow;
     }
     }
  #else
  #else
-    float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){
-        float shadow = 1.0;         
+    float getSpotLightShadows(in SHADOWMAP shadowMap,in  vec4 projCoord){
+        float shadow = 1.0;
         projCoord /= projCoord.w;
         projCoord /= projCoord.w;
-        shadow = GETSHADOW(shadowMap, projCoord);
-        
+        shadow = GETSHADOW(shadowMap,projCoord);
+
         //a small falloff to make the shadow blend nicely into the not lighten
         //a small falloff to make the shadow blend nicely into the not lighten
-        //we translate the texture coordinate value to a -1,1 range so the length 
+        //we translate the texture coordinate value to a -1,1 range so the length
         //of the texture coordinate vector is actually the radius of the lighten area on the ground
         //of the texture coordinate vector is actually the radius of the lighten area on the ground
         projCoord = projCoord * 2.0 - 1.0;
         projCoord = projCoord * 2.0 - 1.0;
         float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
         float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
         return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
         return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
-
     }
     }
  #endif
  #endif
 #endif
 #endif

+ 0 - 242
jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib

@@ -1,242 +0,0 @@
-// Because gpu_shader5 is actually where those
-// gather functions are declared to work on shadowmaps
-#extension GL_ARB_gpu_shader5 : enable
-
-#ifdef HARDWARE_SHADOWS
-    #define SHADOWMAP sampler2DShadow
-    #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset)
-    #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord)
-    #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z)
-#else
-    #define SHADOWMAP sampler2D
-    #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r)
-    #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r) 
-    #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy))
-#endif
-
-
-#if FILTER_MODE == 0
-    #define GETSHADOW Shadow_Nearest
-    #define KERNEL 1.0
-#elif FILTER_MODE == 1
-    #ifdef HARDWARE_SHADOWS
-        #define GETSHADOW Shadow_Nearest
-    #else
-        #define GETSHADOW Shadow_DoBilinear_2x2
-    #endif
-    #define KERNEL 1.0
-#elif FILTER_MODE == 2
-    #define GETSHADOW Shadow_DoDither_2x2
-    #define KERNEL 1.0
-#elif FILTER_MODE == 3
-    #define GETSHADOW Shadow_DoPCF
-    #define KERNEL 4.0
-#elif FILTER_MODE == 4
-    #define GETSHADOW Shadow_DoPCFPoisson
-    #define KERNEL 4.0
-#elif FILTER_MODE == 5
-    #define GETSHADOW Shadow_DoPCF
-    #define KERNEL 8.0
-#endif
-
-
-
-uniform SHADOWMAP m_ShadowMap0;
-uniform SHADOWMAP m_ShadowMap1;
-uniform SHADOWMAP m_ShadowMap2;
-uniform SHADOWMAP m_ShadowMap3;
-#ifdef POINTLIGHT
-uniform SHADOWMAP m_ShadowMap4;
-uniform SHADOWMAP m_ShadowMap5;
-#endif
-
-#ifdef PSSM
-uniform vec4 m_Splits;
-#endif
-uniform float m_ShadowIntensity;
-
-const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE);
-float shadowBorderScale = 1.0;
-
-float Shadow_BorderCheck(in vec2 coord){
-    // Fastest, "hack" method (uses 4-5 instructions)
-    vec4 t = vec4(coord.xy, 0.0, 1.0);
-    t = step(t.wwxy, t.xyzz);
-    return dot(t,t);  
-}
-
-float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){
-    float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0){
-        return 1.0;
-    }
-    return SHADOWCOMPARE(tex,projCoord);
-}
-
-float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){
-    float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0)
-        return 1.0;
-
-    vec2 pixSize = pixSize2 * shadowBorderScale;
-    
-    float shadow = 0.0;
-    ivec2 o = ivec2(mod(floor(gl_FragCoord.xy), 2.0));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, 1.5)+o), projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, 1.5)+o), projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, -0.5)+o), projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, -0.5)+o), projCoord.zw));
-    shadow *= 0.25;
-    return shadow;
-}
-
-float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){
-    float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0)
-        return 1.0;
-  
-    #ifdef GL_ARB_gpu_shader5
-        vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0);
-        vec4 gather = SHADOWGATHER(tex, coord);
-    #else
-        vec4 gather = vec4(0.0);
-        gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1));
-        gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1));
-        gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0));
-        gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0));
-    #endif
-
-   vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE );   
-   vec2 mx = mix( gather.wx, gather.zy, f.x );
-   return mix( mx.x, mx.y, f.y );
-}
-
-float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){    
-
-    vec2 pixSize = pixSize2 * shadowBorderScale;  
-    float shadow = 0.0;
-    float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0)
-        return 1.0;
-
-    float bound = KERNEL * 0.5 - 0.5;
-    bound *= PCFEDGE;
-    for (float y = -bound; y <= bound; y += PCFEDGE){
-        for (float x = -bound; x <= bound; x += PCFEDGE){
-            vec4 coord = vec4(projCoord.xy + vec2(x,y) * pixSize, projCoord.zw);
-            shadow += SHADOWCOMPARE(tex, coord);
-        }
-    }
-
-    shadow = shadow / (KERNEL * KERNEL);
-    return shadow;
-}
-
-
-//12 tap poisson disk
-    const vec2 poissonDisk0 =  vec2(-0.1711046, -0.425016);
-    const vec2 poissonDisk1 =  vec2(-0.7829809, 0.2162201);
-    const vec2 poissonDisk2 =  vec2(-0.2380269, -0.8835521);
-    const vec2 poissonDisk3 =  vec2(0.4198045, 0.1687819);
-    const vec2 poissonDisk4 =  vec2(-0.684418, -0.3186957);
-    const vec2 poissonDisk5 =  vec2(0.6026866, -0.2587841);
-    const vec2 poissonDisk6 =  vec2(-0.2412762, 0.3913516);
-    const vec2 poissonDisk7 =  vec2(0.4720655, -0.7664126);
-    const vec2 poissonDisk8 =  vec2(0.9571564, 0.2680693);
-    const vec2 poissonDisk9 =  vec2(-0.5238616, 0.802707);
-    const vec2 poissonDisk10 = vec2(0.5653144, 0.60262);
-    const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419);
-
-
-float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){  
-
-    float shadow = 0.0;
-    float border = Shadow_BorderCheck(projCoord.xy);
-    if (border > 0.0){
-        return 1.0;
-    }
-     
-    vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale;        
-    
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw));
-    shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw));
-
-    //this is divided by 12
-    return shadow * 0.08333333333;
-}
-
-#ifdef POINTLIGHT       
-    float getPointLightShadows(in vec4 worldPos,in vec3 lightPos,
-                           in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5,
-                           in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){
-        float shadow = 1.0;
-        vec3 vect = worldPos.xyz - lightPos;
-        vec3 absv= abs(vect);
-        float maxComp = max(absv.x,max(absv.y,absv.z));
-        if(maxComp == absv.y){
-           if(vect.y < 0.0){
-               shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w);
-           }else{
-               shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w);
-           }
-        }else if(maxComp == absv.z){
-           if(vect.z < 0.0){
-               shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w);
-           }else{
-               shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w);
-           }
-        }else if(maxComp == absv.x){
-           if(vect.x < 0.0){
-               shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w);
-           }else{
-               shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w);
-           }
-        }  
-        return shadow;
-    }
-#else
- #ifdef PSSM
-    float getDirectionalLightShadows(in vec4 splits,in float shadowPosition,
-                                    in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,
-                                    in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){    
-        float shadow = 1.0;   
-        if(shadowPosition < splits.x){
-            shadow = GETSHADOW(shadowMap0, projCoord0 );   
-        }else if( shadowPosition <  splits.y){
-            shadowBorderScale = 0.5;
-            shadow = GETSHADOW(shadowMap1, projCoord1);  
-        }else if( shadowPosition <  splits.z){
-            shadowBorderScale = 0.25;
-            shadow = GETSHADOW(shadowMap2, projCoord2); 
-        }else if( shadowPosition <  splits.w){
-            shadowBorderScale = 0.125;
-            shadow = GETSHADOW(shadowMap3, projCoord3); 
-        }
-        return shadow;
-    }
- #else
-    float getSpotLightShadows(in SHADOWMAP shadowMap,in  vec4 projCoord){
-        float shadow = 1.0;     
-        projCoord /= projCoord.w;
-        shadow = GETSHADOW(shadowMap,projCoord);
-        
-        //a small falloff to make the shadow blend nicely into the not lighten
-        //we translate the texture coordinate value to a -1,1 range so the length 
-        //of the texture coordinate vector is actually the radius of the lighten area on the ground
-        projCoord = projCoord * 2.0 - 1.0;
-        float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1;
-        return mix(shadow,1.0,clamp(fallOff,0.0,1.0));
-
-    }
- #endif
-#endif

+ 2 - 1
jme3-core/src/main/resources/com/jme3/system/version.properties

@@ -1,11 +1,12 @@
 # THIS IS AN AUTO-GENERATED FILE..
 # THIS IS AN AUTO-GENERATED FILE..
 # DO NOT MODIFY!
 # DO NOT MODIFY!
-build.date=1900-01-01
+build.date=2016-03-25
 git.revision=0
 git.revision=0
 git.branch=unknown
 git.branch=unknown
 git.hash=
 git.hash=
 git.hash.short=
 git.hash.short=
 git.tag=
 git.tag=
 name.full=jMonkeyEngine 3.1.0-UNKNOWN
 name.full=jMonkeyEngine 3.1.0-UNKNOWN
+version.full=3.1.0-UNKNOWN
 version.number=3.1.0
 version.number=3.1.0
 version.tag=SNAPSHOT
 version.tag=SNAPSHOT

+ 21 - 4
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -180,9 +180,23 @@ public class J3MLoader implements AssetLoader {
         return matchList;
         return matchList;
     }
     }
 
 
-    private boolean isTexturePathDeclaredTheTraditionalWay(final int numberOfValues, final int numberOfTextureOptions, final String texturePath) {
-        return (numberOfValues > 1 && (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
-                texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "))) || numberOfTextureOptions == 0;
+    private boolean isTexturePathDeclaredTheTraditionalWay(final List<TextureOptionValue> optionValues, final String texturePath) {
+        final boolean startsWithOldStyle = texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") ||
+                texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip ");
+
+        if (!startsWithOldStyle) {
+            return false;
+        }
+
+        if (optionValues.size() == 1 && (optionValues.get(0).textureOption == TextureOption.Flip || optionValues.get(0).textureOption == TextureOption.Repeat)) {
+            return true;
+        } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Flip && optionValues.get(1).textureOption == TextureOption.Repeat) {
+            return true;
+        } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Repeat && optionValues.get(1).textureOption == TextureOption.Flip) {
+            return true;
+        }
+
+        return false;
     }
     }
 
 
     private Texture parseTextureType(final VarType type, final String value) {
     private Texture parseTextureType(final VarType type, final String value) {
@@ -198,7 +212,7 @@ public class J3MLoader implements AssetLoader {
             String texturePath = value.trim();
             String texturePath = value.trim();
 
 
             // If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way.
             // If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way.
-            if (isTexturePathDeclaredTheTraditionalWay(textureValues.size(), textureOptionValues.size(), texturePath)) {
+            if (isTexturePathDeclaredTheTraditionalWay(textureOptionValues, texturePath)) {
                 boolean flipY = false;
                 boolean flipY = false;
 
 
                 if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) {
                 if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) {
@@ -455,6 +469,8 @@ public class J3MLoader implements AssetLoader {
             renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1]));
             renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1]));
         }else if (split[0].equals("AlphaFunc")){
         }else if (split[0].equals("AlphaFunc")){
             renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1]));
             renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1]));
+        }else if (split[0].equals("LineWidth")){
+            renderState.setLineWidth(Float.parseFloat(split[1]));
         } else {
         } else {
             throw new MatParseException(null, split[0], statement);
             throw new MatParseException(null, split[0], statement);
         }
         }
@@ -636,6 +652,7 @@ public class J3MLoader implements AssetLoader {
 
 
             material = new Material(def);
             material = new Material(def);
             material.setKey(key);
             material.setKey(key);
+            material.setName(split[0].trim());
 //            material.setAssetName(fileName);
 //            material.setAssetName(fileName);
         }else if (split.length == 1){
         }else if (split.length == 1){
             if (extending){
             if (extending){

+ 2 - 0
jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java

@@ -80,6 +80,7 @@ public class J3MLoaderTest {
         final Texture textureMin = Mockito.mock(Texture.class);
         final Texture textureMin = Mockito.mock(Texture.class);
         final Texture textureMag = Mockito.mock(Texture.class);
         final Texture textureMag = Mockito.mock(Texture.class);
         final Texture textureCombined = Mockito.mock(Texture.class);
         final Texture textureCombined = Mockito.mock(Texture.class);
+        final Texture textureLooksLikeOldStyle = Mockito.mock(Texture.class);
 
 
         final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters);
         final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters);
         final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip);
         final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip);
@@ -88,6 +89,7 @@ public class J3MLoaderTest {
         setupMockForTexture("Min", "min.png", false, textureMin);
         setupMockForTexture("Min", "min.png", false, textureMin);
         setupMockForTexture("Mag", "mag.png", false, textureMag);
         setupMockForTexture("Mag", "mag.png", false, textureMag);
         setupMockForTexture("Combined", "combined.png", true, textureCombined);
         setupMockForTexture("Combined", "combined.png", true, textureCombined);
+        setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, textureLooksLikeOldStyle);
 
 
         j3MLoader.load(assetInfo);
         j3MLoader.load(assetInfo);
 
 

+ 2 - 1
jme3-core/src/test/resources/texture-parameters-newstyle.j3m

@@ -6,6 +6,7 @@ Material Test : matdef.j3md {
          Min: MinTrilinear "min.png"
          Min: MinTrilinear "min.png"
          Mag: MagBilinear "mag.png"
          Mag: MagBilinear "mag.png"
          RepeatAxis: WrapRepeat_T "repeat-axis.png"
          RepeatAxis: WrapRepeat_T "repeat-axis.png"
-         Combined: MagNearest MinBilinearNoMipMaps Flip WrapRepeat "combined.png"
+         Combined: Flip MagNearest MinBilinearNoMipMaps WrapRepeat "combined.png"
+         LooksLikeOldStyle: Flip WrapRepeat "oldstyle.png"
      }
      }
 }
 }

+ 1 - 1
jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert

@@ -20,5 +20,5 @@ void main(void)
        Skinning_Compute(modelSpacePos,modelSpaceNormals);
        Skinning_Compute(modelSpacePos,modelSpaceNormals);
    #endif
    #endif
    normal = normalize(TransformNormal(modelSpaceNormals));
    normal = normalize(TransformNormal(modelSpaceNormals));
-   gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
+   gl_Position = TransformWorldViewProjection(modelSpacePos);
 }
 }

+ 3 - 3
jme3-examples/src/main/java/jme3test/TestChooser.java

@@ -260,8 +260,8 @@ public class TestChooser extends JDialog {
                     for (int i = 0; i < appClass.length; i++) {
                     for (int i = 0; i < appClass.length; i++) {
                 	    Class<?> clazz = (Class)appClass[i];
                 	    Class<?> clazz = (Class)appClass[i];
                 		try {
                 		try {
-                			Object app = clazz.newInstance();
-                			if (app instanceof Application) {
+                			if (Application.class.isAssignableFrom(clazz)) {
+                    			Object app = clazz.newInstance();
                 			    if (app instanceof SimpleApplication) {
                 			    if (app instanceof SimpleApplication) {
                 			        final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
                 			        final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class);
                 			        settingMethod.invoke(app, showSetting);
                 			        settingMethod.invoke(app, showSetting);
@@ -283,7 +283,7 @@ public class TestChooser extends JDialog {
                 			    }
                 			    }
                 			} else {
                 			} else {
                                 final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
                                 final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass());
-                                mainMethod.invoke(app, new Object[]{new String[0]});
+                                mainMethod.invoke(clazz, new Object[]{new String[0]});
                 			}
                 			}
                 			// wait for destroy
                 			// wait for destroy
                 			System.gc();
                 			System.gc();

+ 367 - 0
jme3-examples/src/main/java/jme3test/app/TestCloner.java

@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2016 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 jme3test.app;
+
+import java.util.*;
+
+import com.jme3.util.clone.*;
+
+/**
+ *
+ *
+ *  @author    Paul Speed
+ */
+public class TestCloner {
+    
+    public static void main( String... args ) {
+        
+        System.out.println("Clone test:");
+        
+        Cloner cloner = new Cloner();
+        
+        RegularObject ro = new RegularObject(42);
+        System.out.println("Regular Object:" + ro);
+        RegularObject roCloneLegacy = ro.clone();
+        System.out.println("Regular Object Clone:" + roCloneLegacy);
+        RegularObject roClone = cloner.clone(ro);
+        System.out.println("cloner: Regular Object Clone:" + roClone);
+ 
+        System.out.println("------------------------------------");
+        System.out.println();
+        
+        cloner = new Cloner();       
+        RegularSubclass rsc = new RegularSubclass(69, "test");
+        System.out.println("Regular subclass:" + rsc);
+        RegularSubclass rscCloneLegacy = (RegularSubclass)rsc.clone();
+        System.out.println("Regular subclass Clone:" + rscCloneLegacy);
+        RegularSubclass rscClone = cloner.clone(rsc);
+        System.out.println("cloner: Regular subclass Clone:" + rscClone);
+        
+        System.out.println("------------------------------------");
+        System.out.println();
+ 
+        cloner = new Cloner();       
+        Parent parent = new Parent("Foo", 34);
+        System.out.println("Parent:" + parent);
+        Parent parentCloneLegacy = parent.clone();
+        System.out.println("Parent Clone:" + parentCloneLegacy);
+        Parent parentClone = cloner.clone(parent);
+        System.out.println("cloner: Parent Clone:" + parentClone);
+         
+        System.out.println("------------------------------------");
+        System.out.println();
+        
+        cloner = new Cloner();
+        GraphNode root = new GraphNode("root");
+        GraphNode child1 = root.addLink("child1");
+        GraphNode child2 = root.addLink("child2");        
+        GraphNode shared = child1.addLink("shared");
+        child2.addLink(shared);
+        
+        // Add a circular reference to get fancy
+        shared.addLink(root);
+        
+        System.out.println("Simple graph:");
+        root.dump("  ");
+        
+        GraphNode rootClone = cloner.clone(root);
+        System.out.println("clone:");  
+        rootClone.dump("  ");
+        
+        System.out.println("original:");
+        root.dump("  ");
+ 
+        GraphNode reclone = Cloner.deepClone(root);
+        System.out.println("reclone:");  
+        reclone.dump("  ");
+        
+        System.out.println("------------------------------------");
+        System.out.println();
+        cloner = new Cloner();
+        
+        ArrayHolder arrays = new ArrayHolder(5, 3, 7, 3, 7, 2, 1, 4);
+        System.out.println("Array holder:" + arrays);       
+        ArrayHolder arraysClone = cloner.clone(arrays);
+        System.out.println("Array holder clone:" + arraysClone);       
+ 
+           
+        
+    }
+    
+    public static class RegularObject implements Cloneable {
+        protected int i;
+        
+        public RegularObject( int i ) {
+            this.i = i;
+        }
+ 
+        public RegularObject clone() {
+            try {
+                return (RegularObject)super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new RuntimeException(e);
+            }
+        }
+        
+        public String toString() {
+            return getClass().getSimpleName() + "@" + System.identityHashCode(this) 
+                    + "[i=" + i + "]";
+        }
+    }
+    
+    public static class RegularSubclass extends RegularObject {
+        protected String name;
+        
+        public RegularSubclass( int i, String name ) {
+            super(i);
+            this.name = name;
+        }
+               
+        public String toString() {
+            return getClass().getSimpleName() + "@" + System.identityHashCode(this) 
+                    + "[i=" + i + ", name=" + name + "]";
+        }
+    }
+    
+    public static class Parent implements Cloneable, JmeCloneable {
+        
+        private RegularObject ro;
+        private RegularSubclass rsc;
+        
+        public Parent( String name, int age ) {
+            this.ro = new RegularObject(age);
+            this.rsc = new RegularSubclass(age, name);
+        }
+        
+        public Parent clone() {
+            try {
+                return (Parent)super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new RuntimeException(e);
+            }
+        }
+        
+        public Parent jmeClone() {
+            // Ok to delegate to clone() in this case because no deep
+            // cloning is done there.
+            return clone();
+        }
+ 
+        public void cloneFields( Cloner cloner, Object original ) {
+            this.ro = cloner.clone(ro);
+            this.rsc = cloner.clone(rsc);
+        } 
+        
+        public String toString() {
+            return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+                    + "[ro=" + ro + ", rsc=" + rsc + "]";
+        }
+    }
+    
+    public static class GraphNode implements Cloneable, JmeCloneable {
+ 
+        private String name;       
+        private List<GraphNode> links = new ArrayList<>();
+        
+        public GraphNode( String name ) {
+            this.name = name;
+        }
+        
+        public void dump( String indent ) {
+            dump(indent, new HashSet<GraphNode>());        
+        }
+        
+        private void dump( String indent, Set<GraphNode> visited ) {
+            if( visited.contains(this) ) {
+                // already been here
+                System.out.println(indent + this + " ** circular.");
+                return;
+            } 
+            System.out.println(indent + this);
+            visited.add(this);
+            for( GraphNode n : links ) {
+                n.dump(indent + "    ", visited);
+            }
+            visited.remove(this);
+        }
+        
+        public GraphNode addLink( String name ) {
+            GraphNode node = new GraphNode(name);
+            links.add(node);
+            return node;
+        }
+        
+        public GraphNode addLink( GraphNode node ) {
+            links.add(node);
+            return node;
+        }
+        
+        public List<GraphNode> getLinks() {
+            return links;
+        }
+        
+        public GraphNode jmeClone() {
+            try {
+                return (GraphNode)super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new RuntimeException(e);
+            }
+        }
+ 
+        public void cloneFields( Cloner cloner, Object original ) {
+            this.links = cloner.clone(links);
+        } 
+        
+        public String toString() {
+            return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+                    + "[name=" + name + "]";
+        }
+    }
+    
+    public static class ArrayHolder implements JmeCloneable {
+    
+        private int[] intArray;
+        private int[][] intArray2D;
+        private Object[] objects;
+        private RegularObject[] regularObjects;
+        private String[] strings;
+ 
+        public ArrayHolder( int... values ) {
+            this.intArray = values;
+            this.intArray2D = new int[values.length][2];
+            for( int i = 0; i < values.length; i++ ) {
+                intArray2D[i][0] = values[i] + 1;
+                intArray2D[i][1] = values[i] * 2;
+            }
+            this.objects = new Object[values.length];
+            this.regularObjects = new RegularObject[values.length];
+            this.strings = new String[values.length];
+            for( int i = 0; i < values.length; i++ ) {
+                objects[i] = values[i];
+                regularObjects[i] = new RegularObject(values[i]);
+                strings[i] = String.valueOf(values[i]);   
+            }
+        }
+        
+        public ArrayHolder jmeClone() {
+            try {
+                return (ArrayHolder)super.clone();
+            } catch( CloneNotSupportedException e ) {
+                throw new RuntimeException(e);
+            }
+        }
+ 
+        public void cloneFields( Cloner cloner, Object original ) {
+            intArray = cloner.clone(intArray);
+            intArray2D = cloner.clone(intArray2D);
+            
+            // Boxed types are not cloneable so this will fail
+            //objects = cloner.clone(objects);
+            
+            regularObjects = cloner.clone(regularObjects);
+            
+            // Strings are also not cloneable
+            //strings = cloner.clone(strings);
+        }
+        
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("intArray=" + intArray);
+            for( int i = 0; i < intArray.length; i++ ) {
+                if( i == 0 ) {
+                    sb.append("[");
+                } else {
+                    sb.append(", ");
+                }
+                sb.append(intArray[i]);
+            }
+            sb.append("], ");
+            
+            sb.append("intArray2D=" + intArray2D);
+            for( int i = 0; i < intArray2D.length; i++ ) {
+                if( i == 0 ) {
+                    sb.append("[");
+                } else {
+                    sb.append(", ");
+                }
+                sb.append("intArray2D[" + i + "]=" + intArray2D[i]);
+                for( int j = 0; j < 2; j++ ) {
+                    if( j == 0 ) {
+                        sb.append("[");
+                    } else {
+                        sb.append(", ");
+                    }
+                    sb.append(intArray2D[i][j]);                    
+                }
+                sb.append("], ");
+            }
+            sb.append("], ");
+            
+            sb.append("objectArray=" + objects);
+            for( int i = 0; i < objects.length; i++ ) {
+                if( i == 0 ) {
+                    sb.append("[");
+                } else {
+                    sb.append(", ");
+                }
+                sb.append(objects[i]);
+            }
+            sb.append("], ");
+            
+            sb.append("objectArray=" + regularObjects);
+            for( int i = 0; i < regularObjects.length; i++ ) {
+                if( i == 0 ) {
+                    sb.append("[");
+                } else {
+                    sb.append(", ");
+                }
+                sb.append(regularObjects[i]);
+            }
+            sb.append("], ");
+            
+            sb.append("stringArray=" + strings);
+            for( int i = 0; i < strings.length; i++ ) {
+                if( i == 0 ) {
+                    sb.append("[");
+                } else {
+                    sb.append(", ");
+                }
+                sb.append(strings[i]);
+            }
+            sb.append("]");
+            
+            return getClass().getSimpleName() + "@" + System.identityHashCode(this)
+                    + "[" + sb + "]";
+        }       
+    }
+}

+ 15 - 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.renderer.ViewPort;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.Control;
 import com.jme3.scene.control.Control;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 /**
 /**
  * PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank
  * PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank
  * @author normenhansen
  * @author normenhansen
  */
  */
-public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener {
+public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener, JmeCloneable {
 
 
     protected Spatial spatial;
     protected Spatial spatial;
     protected boolean enabled = true;
     protected boolean enabled = true;
@@ -94,10 +96,21 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro
         createWheels();
         createWheels();
     }
     }
 
 
+    @Override
     public Control cloneForSpatial(Spatial spatial) {
     public Control cloneForSpatial(Spatial spatial) {
         throw new UnsupportedOperationException("Not supported yet.");
         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) {
     public void setSpatial(Spatial spatial) {
         this.spatial = spatial;
         this.spatial = spatial;
         setUserObject(spatial);
         setUserObject(spatial);
@@ -179,6 +192,7 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro
     }
     }
 
 
     public void setPhysicsSpace(PhysicsSpace space) {
     public void setPhysicsSpace(PhysicsSpace space) {
+        createVehicle(space);
         if (space == null) {
         if (space == null) {
             if (this.space != null) {
             if (this.space != null) {
                 this.space.removeCollisionObject(this);
                 this.space.removeCollisionObject(this);

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

@@ -147,13 +147,13 @@ public class TestHoveringTank extends SimpleApplication implements AnalogListene
         spaceCraft.setLocalRotation(new Quaternion(new float[]{0, 0.01f, 0}));
         spaceCraft.setLocalRotation(new Quaternion(new float[]{0, 0.01f, 0}));
 
 
         hoverControl = new PhysicsHoverControl(colShape, 500);
         hoverControl = new PhysicsHoverControl(colShape, 500);
-        hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
 
 
         spaceCraft.addControl(hoverControl);
         spaceCraft.addControl(hoverControl);
 
 
 
 
         rootNode.attachChild(spaceCraft);
         rootNode.attachChild(spaceCraft);
         getPhysicsSpace().add(hoverControl);
         getPhysicsSpace().add(hoverControl);
+        hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
 
 
         ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
         ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
         spaceCraft.addControl(chaseCam);
         spaceCraft.addControl(chaseCam);

+ 1 - 1
jme3-examples/src/main/java/jme3test/collision/TestMousePick.java

@@ -128,12 +128,12 @@ public class TestMousePick extends SimpleApplication {
     /** A red ball that marks the last spot that was "hit" by the "shot". */
     /** A red ball that marks the last spot that was "hit" by the "shot". */
     protected void initMark() {
     protected void initMark() {
         Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));
         Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));
-        arrow.setLineWidth(3);
 
 
         //Sphere sphere = new Sphere(30, 30, 0.2f);
         //Sphere sphere = new Sphere(30, 30, 0.2f);
         mark = new Geometry("BOOM!", arrow);
         mark = new Geometry("BOOM!", arrow);
         //mark = new Geometry("BOOM!", sphere);
         //mark = new Geometry("BOOM!", sphere);
         Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mark_mat.getAdditionalRenderState().setLineWidth(3);
         mark_mat.setColor("Color", ColorRGBA.Red);
         mark_mat.setColor("Color", ColorRGBA.Red);
         mark.setMaterial(mark_mat);
         mark.setMaterial(mark_mat);
     }
     }

+ 29 - 10
jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java

@@ -40,12 +40,14 @@ import com.jme3.input.controls.KeyTrigger;
 import com.jme3.light.AmbientLight;
 import com.jme3.light.AmbientLight;
 import com.jme3.light.DirectionalLight;
 import com.jme3.light.DirectionalLight;
 import com.jme3.material.Material;
 import com.jme3.material.Material;
+import com.jme3.material.RenderState;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.FastMath;
 import com.jme3.math.FastMath;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
 import com.jme3.post.FilterPostProcessor;
 import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.ssao.SSAOFilter;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
@@ -69,6 +71,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
     private Geometry ground;
     private Geometry ground;
     private Material matGroundU;
     private Material matGroundU;
     private Material matGroundL;
     private Material matGroundL;
+    private AmbientLight al;
 
 
     public static void main(String[] args) {
     public static void main(String[] args) {
         TestDirectionalLightShadow app = new TestDirectionalLightShadow();
         TestDirectionalLightShadow app = new TestDirectionalLightShadow();
@@ -99,7 +102,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
         mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
         mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
         mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
         mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
         mat[1].setBoolean("UseMaterialColors", true);
         mat[1].setBoolean("UseMaterialColors", true);
-        mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
+        mat[1].setColor("Ambient", ColorRGBA.White);
         mat[1].setColor("Diffuse", ColorRGBA.White.clone());
         mat[1].setColor("Diffuse", ColorRGBA.White.clone());
 
 
 
 
@@ -110,9 +113,14 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
         TangentBinormalGenerator.generate(obj[1]);
         TangentBinormalGenerator.generate(obj[1]);
         TangentBinormalGenerator.generate(obj[0]);
         TangentBinormalGenerator.generate(obj[0]);
 
 
+        Spatial t = obj[0].clone(false);
+        t.setLocalScale(10f);
+        t.setMaterial(mat[1]);
+        rootNode.attachChild(t);
+        t.setLocalTranslation(0, 25, 0);
 
 
         for (int i = 0; i < 60; i++) {
         for (int i = 0; i < 60; i++) {
-            Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
+            t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
             t.setLocalScale(FastMath.nextRandomFloat() * 10f);
             t.setLocalScale(FastMath.nextRandomFloat() * 10f);
             t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
             t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
             rootNode.attachChild(t);
             rootNode.attachChild(t);
@@ -142,8 +150,8 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
         rootNode.addLight(l);
         rootNode.addLight(l);
 
 
 
 
-        AmbientLight al = new AmbientLight();
-        al.setColor(ColorRGBA.White.mult(0.5f));
+        al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(0.02f));
         rootNode.addLight(al);
         rootNode.addLight(al);
 
 
         Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
         Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false);
@@ -156,8 +164,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
     @Override
     @Override
     public void simpleInitApp() {
     public void simpleInitApp() {
         // put the camera in a bad position
         // put the camera in a bad position
-        cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f));
-        cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f));
+//        cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f));
+//        cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f));
+
+        cam.setLocation(new Vector3f(3.3720117f, 42.838284f, -83.43792f));
+        cam.setRotation(new Quaternion(0.13833192f, -0.08969371f, 0.012581267f, 0.9862358f));
 
 
         flyCam.setMoveSpeed(100);
         flyCam.setMoveSpeed(100);
 
 
@@ -166,7 +177,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
         dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3);
         dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3);
         dlsr.setLight(l);
         dlsr.setLight(l);
         dlsr.setLambda(0.55f);
         dlsr.setLambda(0.55f);
-        dlsr.setShadowIntensity(0.6f);
+        dlsr.setShadowIntensity(0.8f);
         dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
         dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
         dlsr.displayDebug();
         dlsr.displayDebug();
         viewPort.addProcessor(dlsr);
         viewPort.addProcessor(dlsr);
@@ -174,7 +185,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
         dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3);
         dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3);
         dlsf.setLight(l);
         dlsf.setLight(l);
         dlsf.setLambda(0.55f);
         dlsf.setLambda(0.55f);
-        dlsf.setShadowIntensity(0.6f);
+        dlsf.setShadowIntensity(0.8f);
         dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
         dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
         dlsf.setEnabled(false);
         dlsf.setEnabled(false);
 
 
@@ -205,10 +216,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
         inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP));
         inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP));
         inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN));
         inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN));
         inputManager.addMapping("pp", new KeyTrigger(KeyInput.KEY_P));
         inputManager.addMapping("pp", new KeyTrigger(KeyInput.KEY_P));
+        inputManager.addMapping("backShadows", new KeyTrigger(KeyInput.KEY_B));
 
 
 
 
         inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown",
         inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown",
-                "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance");
+                "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance", "ShadowUp", "ShadowDown", "backShadows");
 
 
         ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort);
         ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort);
 
 
@@ -255,12 +267,19 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act
             dlsf.setLambda(dlsr.getLambda() - 0.01f);
             dlsf.setLambda(dlsr.getLambda() - 0.01f);
             System.out.println("Lambda : " + dlsr.getLambda());
             System.out.println("Lambda : " + dlsr.getLambda());
         }
         }
-
+        if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && keyPressed) {
+            al.setColor(ColorRGBA.White.mult((1 - dlsr.getShadowIntensity()) * 0.2f));
+        }
 
 
         if (name.equals("debug") && keyPressed) {
         if (name.equals("debug") && keyPressed) {
             dlsr.displayFrustum();
             dlsr.displayFrustum();
         }
         }
 
 
+        if (name.equals("backShadows") && keyPressed) {
+            dlsr.setRenderBackFacesShadows(!dlsr.isRenderBackFacesShadows());
+            dlsf.setRenderBackFacesShadows(!dlsf.isRenderBackFacesShadows());
+        }
+
         if (name.equals("stabilize") && keyPressed) {
         if (name.equals("stabilize") && keyPressed) {
             dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization());
             dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization());
             dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization());
             dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization());

+ 22 - 3
jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java

@@ -32,7 +32,10 @@
 package jme3test.light;
 package jme3test.light;
 
 
 import com.jme3.app.SimpleApplication;
 import com.jme3.app.SimpleApplication;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.light.AmbientLight;
 import com.jme3.light.PointLight;
 import com.jme3.light.PointLight;
+import com.jme3.math.ColorRGBA;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
 import com.jme3.post.FilterPostProcessor;
 import com.jme3.post.FilterPostProcessor;
@@ -45,7 +48,7 @@ import com.jme3.shadow.EdgeFilteringMode;
 import com.jme3.shadow.PointLightShadowFilter;
 import com.jme3.shadow.PointLightShadowFilter;
 import com.jme3.shadow.PointLightShadowRenderer;
 import com.jme3.shadow.PointLightShadowRenderer;
 
 
-public class TestPointLightShadows extends SimpleApplication {
+public class TestPointLightShadows extends SimpleApplication implements ActionListener{
     public static final int SHADOWMAP_SIZE = 512;
     public static final int SHADOWMAP_SIZE = 512;
 
 
     public static void main(String[] args) {
     public static void main(String[] args) {
@@ -55,13 +58,19 @@ public class TestPointLightShadows extends SimpleApplication {
     Node lightNode;
     Node lightNode;
     PointLightShadowRenderer plsr;
     PointLightShadowRenderer plsr;
     PointLightShadowFilter plsf;
     PointLightShadowFilter plsf;
+    AmbientLight al;
 
 
     @Override
     @Override
-    public void simpleInitApp() {
+    public void simpleInitApp () {
         flyCam.setMoveSpeed(10);
         flyCam.setMoveSpeed(10);
         cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f));
         cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f));
         cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f));
         cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f));
 
 
+        al = new AmbientLight(ColorRGBA.White.mult(0.02f));
+        rootNode.addLight(al);
+
+
+
 
 
         Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o");
         Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o");
         scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
         scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
@@ -89,6 +98,7 @@ public class TestPointLightShadows extends SimpleApplication {
         plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
         plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
         plsr.setShadowZExtend(15);
         plsr.setShadowZExtend(15);
         plsr.setShadowZFadeLength(5);
         plsr.setShadowZFadeLength(5);
+        plsr.setShadowIntensity(0.9f);
        // plsr.setFlushQueues(false);
        // plsr.setFlushQueues(false);
         //plsr.displayFrustum();
         //plsr.displayFrustum();
         plsr.displayDebug();
         plsr.displayDebug();
@@ -99,18 +109,27 @@ public class TestPointLightShadows extends SimpleApplication {
         plsf.setLight((PointLight) scene.getLocalLightList().get(0));    
         plsf.setLight((PointLight) scene.getLocalLightList().get(0));    
         plsf.setShadowZExtend(15);
         plsf.setShadowZExtend(15);
         plsf.setShadowZFadeLength(5);
         plsf.setShadowZFadeLength(5);
+        plsf.setShadowIntensity(0.8f);
         plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
         plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4);
         plsf.setEnabled(false);
         plsf.setEnabled(false);
 
 
         FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
         FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
         fpp.addFilter(plsf);
         fpp.addFilter(plsf);
         viewPort.addProcessor(fpp);
         viewPort.addProcessor(fpp);
-              
+        inputManager.addListener(this,"ShadowUp","ShadowDown");
         ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort);
         ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort);
+
     }
     }
 
 
     @Override
     @Override
     public void simpleUpdate(float tpf) {
     public void simpleUpdate(float tpf) {
  //      lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f);
  //      lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f);
     }
     }
+
+    @Override
+    public void onAction(String name, boolean isPressed, float tpf) {
+        if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && isPressed) {
+            al.setColor(ColorRGBA.White.mult((1 - plsr.getShadowIntensity()) * 0.2f));
+        }
+    }
 }
 }

+ 12 - 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.texture.Texture.WrapMode;
 import com.jme3.util.SkyFactory;
 import com.jme3.util.SkyFactory;
 import com.jme3.util.TangentBinormalGenerator;
 import com.jme3.util.TangentBinormalGenerator;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
 import java.io.IOException;
 import java.io.IOException;
 
 
 public class TestPssmShadow extends SimpleApplication implements ActionListener {
 public class TestPssmShadow extends SimpleApplication implements ActionListener {
@@ -249,10 +251,20 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener
             time = 0;
             time = 0;
         }
         }
 
 
+        @Override   
+        public Object jmeClone() {
+            return null;
+        }     
+
+        @Override   
+        public void cloneFields( Cloner cloner, Object original ) { 
+        }
+             
         @Override
         @Override
         protected void controlRender(RenderManager rm, ViewPort vp) {
         protected void controlRender(RenderManager rm, ViewPort vp) {
         }
         }
 
 
+        @Override
         public Control cloneForSpatial(Spatial spatial) {
         public Control cloneForSpatial(Spatial spatial) {
             return null;
             return null;
         }
         }

+ 1 - 1
jme3-examples/src/main/java/jme3test/light/TestSpotLight.java

@@ -58,7 +58,7 @@ public class TestSpotLight extends SimpleApplication {
     Geometry lightMdl;
     Geometry lightMdl;
     public void setupLighting(){
     public void setupLighting(){
       AmbientLight al=new AmbientLight();
       AmbientLight al=new AmbientLight();
-      al.setColor(ColorRGBA.White.mult(0.8f));
+      al.setColor(ColorRGBA.White.mult(0.02f));
       rootNode.addLight(al);
       rootNode.addLight(al);
         
         
       spot=new SpotLight();
       spot=new SpotLight();

+ 1 - 6
jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java

@@ -65,7 +65,7 @@ public class TestSpotLightShadows extends SimpleApplication {
 
 
     public void setupLighting() {
     public void setupLighting() {
         AmbientLight al = new AmbientLight();
         AmbientLight al = new AmbientLight();
-        al.setColor(ColorRGBA.White.mult(0.3f));
+        al.setColor(ColorRGBA.White.mult(0.02f));
         rootNode.addLight(al);
         rootNode.addLight(al);
 
 
         rootNode.setShadowMode(ShadowMode.CastAndReceive);
         rootNode.setShadowMode(ShadowMode.CastAndReceive);
@@ -132,11 +132,6 @@ public class TestSpotLightShadows extends SimpleApplication {
         }, "stop");
         }, "stop");
 
 
         inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1));
         inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1));
-        
-        MaterialDebugAppState s = new MaterialDebugAppState();
-        s.registerBinding("Common/MatDefs/Shadow/PostShadow15.frag", rootNode);
-        s.registerBinding(new KeyTrigger(KeyInput.KEY_R), rootNode);
-        stateManager.attach(s);
         flyCam.setDragToRotate(true);
         flyCam.setDragToRotate(true);
     }
     }
 
 

+ 1 - 1
jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java

@@ -95,7 +95,7 @@ public class TestSpotLightTerrain extends SimpleApplication {
         rootNode.addLight(sl);
         rootNode.addLight(sl);
 
 
         AmbientLight ambLight = new AmbientLight();
         AmbientLight ambLight = new AmbientLight();
-        ambLight.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 0.2f));
+        ambLight.setColor(ColorRGBA.Black);
         rootNode.addLight(ambLight);
         rootNode.addLight(ambLight);
 
 
         cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f));
         cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f));

+ 0 - 1
jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java

@@ -72,7 +72,6 @@ public class TestTangentGenBadModels extends SimpleApplication {
                     "debug tangents geom",
                     "debug tangents geom",
                     TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f)
                     TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f)
                 );
                 );
-                debug.getMesh().setLineWidth(1);
                 debug.setMaterial(debugMat);
                 debug.setMaterial(debugMat);
                 debug.setCullHint(Spatial.CullHint.Never);
                 debug.setCullHint(Spatial.CullHint.Never);
                 debug.setLocalTransform(g.getWorldTransform());
                 debug.setLocalTransform(g.getWorldTransform());

+ 100 - 0
jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java

@@ -0,0 +1,100 @@
+package jme3test.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.*;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TangentBinormalGenerator;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+
+/**
+ * test
+ *
+ * @author normenhansen
+ */
+public class TestTangentSpace extends SimpleApplication {
+    
+    public static void main(String[] args) {
+        TestTangentSpace app = new TestTangentSpace();
+        app.start();
+    }
+    
+    private Node debugNode = new Node("debug");
+    
+    @Override
+    public void simpleInitApp() {
+        renderManager.setSinglePassLightBatchSize(2);
+        renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
+        initView();
+        
+        Spatial s = assetManager.loadModel("Models/Test/BasicCubeLow.obj");
+        rootNode.attachChild(s);
+
+        Material m = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        m.setTexture("NormalMap", assetManager.loadTexture("Models/Test/Normal_pixel.png"));
+
+        Geometry g = (Geometry)s;
+        Geometry g2 = (Geometry) g.deepClone();
+        g2.move(5, 0, 0);
+        g.getParent().attachChild(g2);
+
+        g.setMaterial(m);
+        g2.setMaterial(m);
+
+        //Regular tangent generation (left geom)
+        TangentBinormalGenerator.generate(g2.getMesh(), true);
+
+        //MikkTSPace Tangent generation (right geom)        
+
+        MikktspaceTangentGenerator.generate(g);
+        
+        createDebugTangents(g2);
+        createDebugTangents(g);
+        
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("toggleDebug") && isPressed) {
+                    if (debugNode.getParent() == null) {
+                        rootNode.attachChild(debugNode);
+                    } else {
+                        debugNode.removeFromParent();
+                    }
+                }
+            }
+        }, "toggleDebug");
+
+        inputManager.addMapping("toggleDebug", new KeyTrigger(KeyInput.KEY_SPACE));
+        
+        
+        DirectionalLight dl = new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal());
+        rootNode.addLight(dl);
+    }
+    
+    private void initView() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        cam.setLocation(new Vector3f(8.569681f, 3.335546f, 5.4372444f));
+        cam.setRotation(new Quaternion(-0.07608022f, 0.9086564f, -0.18992864f, -0.3639813f));
+        flyCam.setMoveSpeed(10);
+    }
+    
+    private void createDebugTangents(Geometry geom) {
+        Geometry debug = new Geometry(
+                "Debug " + geom.getName(),
+                TangentBinormalGenerator.genTbnLines(geom.getMesh(), 0.8f)
+        );
+        Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+        debug.setMaterial(debugMat);
+        debug.setCullHint(Spatial.CullHint.Never);
+        debug.getLocalTranslation().set(geom.getWorldTranslation());
+        debugNode.attachChild(debug);
+    }
+}

+ 6 - 6
jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java

@@ -50,10 +50,11 @@ public class TestDebugShapes extends SimpleApplication {
         app.start();
         app.start();
     }
     }
 
 
-    public Geometry putShape(Mesh shape, ColorRGBA color){
+    public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){
         Geometry g = new Geometry("shape", shape);
         Geometry g = new Geometry("shape", shape);
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat.getAdditionalRenderState().setWireframe(true);
         mat.getAdditionalRenderState().setWireframe(true);
+        mat.getAdditionalRenderState().setLineWidth(lineWidth);
         mat.setColor("Color", color);
         mat.setColor("Color", color);
         g.setMaterial(mat);
         g.setMaterial(mat);
         rootNode.attachChild(g);
         rootNode.attachChild(g);
@@ -62,20 +63,19 @@ public class TestDebugShapes extends SimpleApplication {
 
 
     public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){
     public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){
         Arrow arrow = new Arrow(dir);
         Arrow arrow = new Arrow(dir);
-        arrow.setLineWidth(4); // make arrow thicker
-        putShape(arrow, color).setLocalTranslation(pos);
+        putShape(arrow, color, 4).setLocalTranslation(pos);
     }
     }
 
 
     public void putBox(Vector3f pos, float size, ColorRGBA color){
     public void putBox(Vector3f pos, float size, ColorRGBA color){
-        putShape(new WireBox(size, size, size), color).setLocalTranslation(pos);
+        putShape(new WireBox(size, size, size), color, 1).setLocalTranslation(pos);
     }
     }
 
 
     public void putGrid(Vector3f pos, ColorRGBA color){
     public void putGrid(Vector3f pos, ColorRGBA color){
-        putShape(new Grid(6, 6, 0.2f), color).center().move(pos);
+        putShape(new Grid(6, 6, 0.2f), color, 1).center().move(pos);
     }
     }
 
 
     public void putSphere(Vector3f pos, ColorRGBA color){
     public void putSphere(Vector3f pos, ColorRGBA color){
-        putShape(new WireSphere(1), color).setLocalTranslation(pos);
+        putShape(new WireSphere(1), color, 1).setLocalTranslation(pos);
     }
     }
 
 
     @Override
     @Override

+ 14 - 0
jme3-examples/src/main/java/jme3test/network/TestChatClient.java

@@ -41,6 +41,8 @@ import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionEvent;
 import java.io.IOException;
 import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.*;
 import javax.swing.*;
 import jme3test.network.TestChatServer.ChatMessage;
 import jme3test.network.TestChatServer.ChatMessage;
 
 
@@ -115,6 +117,18 @@ public class TestChatClient extends JFrame {
 
 
     public static void main(String... args) throws Exception {
     public static void main(String... args) throws Exception {
     
     
+        // Increate the logging level for networking...
+        System.out.println("Setting logging to max");
+        Logger networkLog = Logger.getLogger("com.jme3.network"); 
+        networkLog.setLevel(Level.FINEST);
+ 
+        // And we have to tell JUL's handler also   
+        // turn up logging in a very convoluted way
+        Logger rootLog = Logger.getLogger("");
+        if( rootLog.getHandlers().length > 0 ) {
+            rootLog.getHandlers()[0].setLevel(Level.FINEST);
+        }
+                
         // Note: in JME 3.1 this is generally unnecessary as the server will
         // Note: in JME 3.1 this is generally unnecessary as the server will
         // send a message with all server-registered classes.
         // send a message with all server-registered classes.
         // TestChatServer.initializeClasses();
         // TestChatServer.initializeClasses();

+ 20 - 1
jme3-examples/src/main/java/jme3test/network/TestChatServer.java

@@ -31,6 +31,9 @@
  */
  */
 package jme3test.network;
 package jme3test.network;
 
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import com.jme3.network.*;
 import com.jme3.network.*;
 import com.jme3.network.serializing.Serializable;
 import com.jme3.network.serializing.Serializable;
 import com.jme3.network.serializing.Serializer;
 import com.jme3.network.serializing.Serializer;
@@ -56,11 +59,15 @@ public class TestChatServer {
     private boolean isRunning;
     private boolean isRunning;
     
     
     public TestChatServer() throws IOException {
     public TestChatServer() throws IOException {
-        initializeClasses();
 
 
         // Use this to test the client/server name version check
         // Use this to test the client/server name version check
         this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT);
         this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT);
 
 
+        // Initialize our own messages only after the server has been created.
+        // It registers some additional messages with the serializer by default
+        // that need to go before custom messages.
+        initializeClasses();
+
         ChatHandler handler = new ChatHandler();
         ChatHandler handler = new ChatHandler();
         server.addMessageListener(handler, ChatMessage.class);
         server.addMessageListener(handler, ChatMessage.class);
         
         
@@ -121,6 +128,18 @@ public class TestChatServer {
     }
     }
 
 
     public static void main(String... args) throws Exception {
     public static void main(String... args) throws Exception {
+ 
+        // Increate the logging level for networking...
+        System.out.println("Setting logging to max");
+        Logger networkLog = Logger.getLogger("com.jme3.network"); 
+        networkLog.setLevel(Level.FINEST);
+ 
+        // And we have to tell JUL's handler also   
+        // turn up logging in a very convoluted way
+        Logger rootLog = Logger.getLogger("");
+        if( rootLog.getHandlers().length > 0 ) {
+            rootLog.getHandlers()[0].setLevel(Level.FINEST);
+        }        
     
     
         TestChatServer chatServer = new TestChatServer();
         TestChatServer chatServer = new TestChatServer();
         chatServer.start();
         chatServer.start();

+ 87 - 0
jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2009-2012 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 jme3test.scene;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+
+public class TestLineWidthRenderState extends SimpleApplication {
+
+    private Material mat;
+
+    public static void main(String[] args){
+        TestLineWidthRenderState app = new TestLineWidthRenderState();
+        app.start();
+    }
+
+
+
+    @Override
+    public void simpleInitApp() {
+        setDisplayFps(false);
+        setDisplayStatView(false);
+        cam.setLocation(new Vector3f(5.5826545f, 3.6192513f, 8.016988f));
+        cam.setRotation(new Quaternion(-0.04787097f, 0.9463123f, -0.16569641f, -0.27339742f));
+
+        Box b = new Box(1, 1, 1);
+        Geometry geom = new Geometry("Box", b);
+        mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Blue);
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.getAdditionalRenderState().setLineWidth(2);
+        geom.setMaterial(mat);
+        rootNode.attachChild(geom);
+
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if(name.equals("up") && isPressed){
+                    mat.getAdditionalRenderState().setLineWidth(mat.getAdditionalRenderState().getLineWidth() + 1);
+                }
+                if(name.equals("down") && isPressed){
+                    mat.getAdditionalRenderState().setLineWidth(Math.max(mat.getAdditionalRenderState().getLineWidth() - 1, 1));
+                }
+            }
+        }, "up", "down");
+        inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_U));
+        inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_J));
+    }
+}

+ 1 - 0
jme3-plugins/build.gradle

@@ -14,4 +14,5 @@ sourceSets {
 
 
 dependencies {
 dependencies {
     compile project(':jme3-core')
     compile project(':jme3-core')
+    testCompile project(':jme3-desktop')
 }
 }

+ 78 - 0
jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java

@@ -0,0 +1,78 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+/**
+ * Saves a Material to a j3m file with proper formatting.
+ *
+ * usage is :
+ * <pre>
+ *     J3MExporter exporter = new J3MExporter();
+ *     exporter.save(material, myFile);
+ *     //or
+ *     exporter.save(material, myOutputStream);
+ * </pre>
+ *
+ * @author tsr
+ * @author nehon (documentation and safety check)
+ */
+public class J3MExporter implements JmeExporter {
+
+    private final J3MRootOutputCapsule rootCapsule;
+
+    /**
+     * Create a J3MExporter
+     */
+    public J3MExporter() {
+        rootCapsule = new J3MRootOutputCapsule(this);
+    }
+
+    @Override
+    public void save(Savable object, OutputStream f) throws IOException {
+
+        if (!(object instanceof Material)) {
+            throw new IllegalArgumentException("J3MExporter can only save com.jme3.material.Material class");
+        }
+
+        OutputStreamWriter out = new OutputStreamWriter(f, Charset.forName("UTF-8"));
+
+        rootCapsule.clear();
+        object.write(this);
+        rootCapsule.writeToStream(out);
+
+        out.flush();
+    }
+
+    @Override
+    public void save(Savable object, File f) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(f)) {
+            save(object, fos);
+        }
+    }
+
+    @Override
+    public OutputCapsule getCapsule(Savable object) {
+        if ((object instanceof Material) || (object instanceof MaterialDef)) {
+            return rootCapsule;
+        }
+
+        return rootCapsule.getCapsule(object);
+    }
+
+}

+ 383 - 0
jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java

@@ -0,0 +1,383 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.material.MatParam;
+import com.jme3.material.MatParamTexture;
+import com.jme3.math.*;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.util.IntMap;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author tsr
+ */
+public class J3MOutputCapsule implements OutputCapsule {
+
+    private final HashMap<String, String> parameters;
+    protected final J3MExporter exporter;
+
+    public J3MOutputCapsule(J3MExporter exporter) {
+        this.exporter = exporter;
+        parameters = new HashMap<>();
+    }
+
+    public void writeToStream(OutputStreamWriter out) throws IOException {
+        for (String key : parameters.keySet()) {
+            out.write("      ");
+            writeParameter(out, key, parameters.get(key));
+            out.write("\n");
+        }
+    }
+
+    protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException {
+        out.write(name);
+        out.write(" : ");
+        out.write(value);
+    }
+
+    public void clear() {
+        parameters.clear();
+    }
+
+    protected void putParameter(String name, String value) {
+        parameters.put(name, value);
+    }
+
+    @Override
+    public void write(boolean value, String name, boolean defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+
+        putParameter(name, ((value) ? "On" : "Off"));
+    }
+
+    @Override
+    public void writeStringSavableMap(Map<String, ? extends Savable> map, String name, Map<String, ? extends Savable> defVal) throws IOException {
+        for (String key : map.keySet()) {
+            Savable value = map.get(key);
+            if (defVal == null || !defVal.containsKey(key) || !defVal.get(key).equals(value)) {
+                putParameter(key, format(value));
+            }
+        }
+    }
+
+    protected String format(Savable value) {
+        if (value instanceof MatParamTexture) {
+            return formatMatParamTexture((MatParamTexture) value);
+        }
+        if (value instanceof MatParam) {
+            return formatMatParam((MatParam) value);
+        }
+
+        throw new UnsupportedOperationException(value.getClass() + ": Not supported yet.");
+    }
+
+    private String formatMatParam(MatParam param){
+        VarType type = param.getVarType();
+        Object val = param.getValue();
+        switch (type) {
+            case Boolean:
+            case Float:
+            case Int:
+                return val.toString();
+            case Vector2:
+                Vector2f v2 = (Vector2f) val;
+                return v2.getX() + " " + v2.getY();
+            case Vector3:
+                Vector3f v3 = (Vector3f) val;
+                return v3.getX() + " " + v3.getY() + " " + v3.getZ();
+            case Vector4:
+                // can be either ColorRGBA, Vector4f or Quaternion
+                if (val instanceof Vector4f) {
+                    Vector4f v4 = (Vector4f) val;
+                    return v4.getX() + " " + v4.getY() + " "
+                            + v4.getZ() + " " + v4.getW();
+                } else if (val instanceof ColorRGBA) {
+                    ColorRGBA color = (ColorRGBA) val;
+                    return color.getRed() + " " + color.getGreen() + " "
+                            + color.getBlue() + " " + color.getAlpha();
+                } else if (val instanceof Quaternion) {
+                    Quaternion quat = (Quaternion) val;
+                    return quat.getX() + " " + quat.getY() + " "
+                            + quat.getZ() + " " + quat.getW();
+                } else {
+                    throw new UnsupportedOperationException("Unexpected Vector4 type: " + val);
+                }
+
+            default:
+                return null; // parameter type not supported in J3M
+        }
+    }
+
+    protected static String formatMatParamTexture(MatParamTexture param) {
+        StringBuilder ret = new StringBuilder();
+        Texture tex = (Texture) param.getValue();
+        TextureKey key;
+        if (tex != null) {
+            key = (TextureKey) tex.getKey();
+
+            if (key != null && key.isFlipY()) {
+                ret.append("Flip ");
+            }
+
+            ret.append(formatWrapMode(tex, Texture.WrapAxis.S));
+            ret.append(formatWrapMode(tex, Texture.WrapAxis.T));
+            ret.append(formatWrapMode(tex, Texture.WrapAxis.R));
+
+            //Min and Mag filter
+            Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps;
+            if (tex.getImage().hasMipmaps() || (key != null && key.isGenerateMips())) {
+                def = Texture.MinFilter.Trilinear;
+            }
+            if (tex.getMinFilter() != def) {
+                ret.append("Min").append(tex.getMinFilter().name()).append(" ");
+            }
+
+            if (tex.getMagFilter() != Texture.MagFilter.Bilinear) {
+                ret.append("Mag").append(tex.getMagFilter().name()).append(" ");
+            }
+
+            ret.append("\"").append(key.getName()).append("\"");
+        }
+
+        return ret.toString();
+    }
+
+    protected static String formatWrapMode(Texture texVal, Texture.WrapAxis axis) {
+        WrapMode mode;
+        try {
+            mode = texVal.getWrap(axis);
+        } catch (IllegalArgumentException e) {
+            //this axis doesn't exist on the texture
+            return "";
+        }
+        if (mode != WrapMode.EdgeClamp) {
+            return "Wrap" + mode.name() + "_" + axis.name() + " ";
+        }
+        return "";
+    }
+
+    @Override
+    public void write(Enum value, String name, Enum defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+
+        putParameter(name, value.toString());
+    }
+
+    @Override
+    public void write(float value, String name, float defVal) throws IOException {
+        if (value == defVal) {
+            return;
+        }
+
+        putParameter(name, Float.toString(value));
+    }
+
+    @Override
+    public void write(float[] value, String name, float[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(float[][] value, String name, float[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(double value, String name, double defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(double[] value, String name, double[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(double[][] value, String name, double[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(long value, String name, long defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(long[] value, String name, long[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(long[][] value, String name, long[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(short value, String name, short defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(short[] value, String name, short[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(short[][] value, String name, short[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(boolean[] value, String name, boolean[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(String value, String name, String defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(String[] value, String name, String[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(String[][] value, String name, String[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(BitSet value, String name, BitSet defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(Savable object, String name, Savable defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(Savable[] objects, String name, Savable[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeFloatBufferArrayList(ArrayList<FloatBuffer> array, String name, ArrayList<FloatBuffer> defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeByteBufferArrayList(ArrayList<ByteBuffer> array, String name, ArrayList<ByteBuffer> defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeSavableMap(Map<? extends Savable, ? extends Savable> map, String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void writeIntSavableMap(IntMap<? extends Savable> map, String name, IntMap<? extends Savable> defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(byte value, String name, byte defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(byte[] value, String name, byte[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(byte[][] value, String name, byte[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(int value, String name, int defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(int[] value, String name, int[] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+    @Override
+    public void write(int[][] value, String name, int[][] defVal) throws IOException {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+
+}

+ 82 - 0
jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java

@@ -0,0 +1,82 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+
+/**
+ *
+ * @author tsr
+ */
+public class J3MRenderStateOutputCapsule extends J3MOutputCapsule {    
+    protected final static HashMap<String, String> NAME_MAP;
+    protected String offsetUnit;
+    
+    static {
+        NAME_MAP = new HashMap<>();
+        NAME_MAP.put( "wireframe", "Wireframe");
+        NAME_MAP.put( "cullMode", "FaceCull");
+        NAME_MAP.put( "depthWrite", "DepthWrite");
+        NAME_MAP.put( "depthTest", "DepthTest");
+        NAME_MAP.put( "blendMode", "Blend");
+        NAME_MAP.put( "alphaFallOff", "AlphaTestFalloff");
+        NAME_MAP.put( "offsetFactor", "PolyOffset");
+        NAME_MAP.put( "colorWrite", "ColorWrite");
+        NAME_MAP.put( "pointSprite", "PointSprite");
+        NAME_MAP.put( "depthFunc", "DepthFunc");
+        NAME_MAP.put( "alphaFunc", "AlphaFunc");
+        NAME_MAP.put( "lineWidth", "LineWidth");
+    }
+    public J3MRenderStateOutputCapsule(J3MExporter exporter) {
+        super(exporter);
+    }
+
+    public OutputCapsule getCapsule(Savable object) {
+        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+    }
+    
+    @Override
+    public void clear() {
+        super.clear();
+        offsetUnit = "";
+    }
+
+    @Override
+    public void writeToStream(OutputStreamWriter out) throws IOException {
+        out.write("    AdditionalRenderState {\n");
+        super.writeToStream(out);
+        out.write("    }\n");
+    }  
+    
+    @Override
+    protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException {
+        out.write(name);
+        out.write(" ");        
+        out.write(value);
+        
+        if( "PolyOffset".equals(name) ) {
+            out.write(" ");
+            out.write(offsetUnit);
+        }        
+    }
+    
+    @Override
+    protected void putParameter(String name, String value ) {
+        if( "offsetUnits".equals(name) ) {
+            offsetUnit = value;
+            return;
+        }
+        
+        if( !NAME_MAP.containsKey(name) )
+            return;
+        
+        super.putParameter(NAME_MAP.get(name), value);
+    }
+}

+ 99 - 0
jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java

@@ -0,0 +1,99 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.material.plugin.export.material;
+
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.HashMap;
+
+/**
+ * @author tsr
+ */
+public class J3MRootOutputCapsule extends J3MOutputCapsule {
+
+    private final HashMap<Savable, J3MOutputCapsule> outCapsules;
+    private String name;
+    private String materialDefinition;
+    private Boolean isTransparent;
+
+    public J3MRootOutputCapsule(J3MExporter exporter) {
+        super(exporter);
+        outCapsules = new HashMap<>();
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+        isTransparent = null;
+        name = "";
+        materialDefinition = "";
+        outCapsules.clear();
+
+    }
+
+    public OutputCapsule getCapsule(Savable object) {
+        if (!outCapsules.containsKey(object)) {
+            outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter));
+        }
+
+        return outCapsules.get(object);
+    }
+
+    @Override
+    public void writeToStream(OutputStreamWriter out) throws IOException {
+        out.write("Material " + name + " : " + materialDefinition + " {\n\n");
+        if (isTransparent != null)
+            out.write("    Transparent " + ((isTransparent) ? "On" : "Off") + "\n\n");
+
+        out.write("    MaterialParameters {\n");
+        super.writeToStream(out);
+        out.write("    }\n\n");
+
+        for (J3MOutputCapsule c : outCapsules.values()) {
+            c.writeToStream(out);
+        }
+        out.write("}\n");
+    }
+
+    @Override
+    public void write(String value, String name, String defVal) throws IOException {
+        switch (name) {
+            case "material_def":
+                materialDefinition = value;
+                break;
+            case "name":
+                this.name = value;
+                break;
+            default:
+                throw new UnsupportedOperationException(name + " string material parameter not supported yet");
+        }
+    }
+
+    @Override
+    public void write(boolean value, String name, boolean defVal) throws IOException {
+        if( value == defVal)
+            return;
+
+        switch (name) {
+            case "is_transparent":
+                isTransparent = value;
+                break;
+            default:
+                throw new UnsupportedOperationException(name + " boolean material parameter not supported yet");
+        }
+    }
+
+    @Override
+    public void write(Savable object, String name, Savable defVal) throws IOException {
+        if(object != null && !object.equals(defVal)) {
+            object.write(exporter);
+        }
+    }
+
+}

部分文件因为文件数量过多而无法显示