瀏覽代碼

* Fixed Blender loader crash when "Image" texture mode is specified but actual image is not selected
* Replaced oracle proprietary exception with UnsupportedOperationException
* Shared Geometry Patch - Still need detection mechanism for old versions!
* Binary/J3O format will now write signature and version
* Binary/J3O format now has version numbers for exported class hierarchies
* Fix crash in TestHoveringTank
* ListMap now uses backing array and a map - increased lookup performance for uniforms/matparams and faster iteration too. Only insertion became slower

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7726 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

sha..rd 14 年之前
父節點
當前提交
348b1d638a
共有 28 個文件被更改,包括 630 次插入418 次删除
  1. 5 3
      engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java
  2. 1 4
      engine/src/blender/com/jme3/scene/plugins/blender/structures/Properties.java
  3. 1 1
      engine/src/core-plugins/com/jme3/export/binary/BinaryClassObject.java
  4. 63 27
      engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java
  5. 59 11
      engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java
  6. 23 2
      engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java
  7. 0 62
      engine/src/core-plugins/com/jme3/export/binary/BinaryLoaderModule.java
  8. 12 12
      engine/src/core/com/jme3/animation/AnimControl.java
  9. 19 19
      engine/src/core/com/jme3/animation/BoneTrack.java
  10. 87 17
      engine/src/core/com/jme3/animation/SkeletonControl.java
  11. 1 1
      engine/src/core/com/jme3/cinematic/Cinematic.java
  12. 1 1
      engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java
  13. 2 1
      engine/src/core/com/jme3/cinematic/events/CinematicEvent.java
  14. 17 15
      engine/src/core/com/jme3/effect/ParticleEmitter.java
  15. 1 1
      engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java
  16. 9 1
      engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
  17. 2 0
      engine/src/core/com/jme3/export/InputCapsule.java
  18. 8 0
      engine/src/core/com/jme3/export/JmeImporter.java
  19. 2 1
      engine/src/core/com/jme3/export/Savable.java
  20. 39 13
      engine/src/core/com/jme3/export/SavableClassUtil.java
  21. 6 0
      engine/src/core/com/jme3/scene/UserData.java
  22. 1 1
      engine/src/core/com/jme3/scene/VertexBuffer.java
  23. 2 2
      engine/src/core/com/jme3/terrain/AbstractGeomap.java
  24. 150 86
      engine/src/core/com/jme3/util/ListMap.java
  25. 106 129
      engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java
  26. 1 1
      engine/src/test/jme3test/bullet/TestHoveringTank.java
  27. 6 2
      engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java
  28. 6 5
      engine/src/xml/com/jme3/export/xml/XMLImporter.java

+ 5 - 3
engine/src/blender/com/jme3/scene/plugins/blender/helpers/v249/TextureHelper.java

@@ -177,8 +177,10 @@ public class TextureHelper extends AbstractBlenderHelper {
 				break;
 			case TEX_IMAGE:// (it is first because probably this will be most commonly used)
 				Pointer pImage = (Pointer) tex.getFieldValue("ima");
-				Structure image = pImage.fetchData(dataRepository.getInputStream()).get(0);
-				result = this.getTextureFromImage(image, dataRepository);
+                                if (pImage.isNotNull()){
+                                    Structure image = pImage.fetchData(dataRepository.getInputStream()).get(0);
+                                    result = this.getTextureFromImage(image, dataRepository);
+                                }
 				break;
 			case TEX_CLOUDS:
 				result = this.clouds(tex, width, height, dataRepository);
@@ -212,7 +214,7 @@ public class TextureHelper extends AbstractBlenderHelper {
 				break;
 			case TEX_PLUGIN:
 			case TEX_ENVMAP:// TODO: implement envmap texture
-				LOGGER.log(Level.WARNING, "Unsupported texture type: " + type + " for texture: " + tex.getName());
+				LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[]{type, tex.getName()});
 				break;
 			default:
 				throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName());

+ 1 - 4
engine/src/blender/com/jme3/scene/plugins/blender/structures/Properties.java

@@ -6,9 +6,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Logger;
-
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
@@ -158,7 +155,7 @@ public class Properties implements Cloneable, Savable {
 				break;
 			}
 			case IDP_NUMTYPES:
-				throw new NotImplementedException();
+				throw new UnsupportedOperationException();
 				// case IDP_ID://not yet implemented in blender
 				// return null;
 			default:

+ 1 - 1
engine/src/core-plugins/com/jme3/export/binary/BinaryClassObject.java

@@ -42,5 +42,5 @@ class BinaryClassObject {
     
     byte[] alias;
     String className;
-    
+    int[] classHierarchyVersions;
 }

+ 63 - 27
engine/src/core-plugins/com/jme3/export/binary/BinaryExporter.java

@@ -34,6 +34,8 @@ package com.jme3.export.binary;
 
 import com.jme3.export.JmeExporter;
 import com.jme3.export.Savable;
+import com.jme3.export.FormatVersion;
+import com.jme3.export.SavableClassUtil;
 import com.jme3.math.FastMath;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -43,6 +45,7 @@ import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 /**
@@ -162,7 +165,7 @@ public class BinaryExporter implements JmeExporter {
 
     public static boolean debug = false;
     public static boolean useFastBufs = true;
-
+      
     public BinaryExporter() {
     }
 
@@ -179,32 +182,44 @@ public class BinaryExporter implements JmeExporter {
         locationTable.clear();
         contentKeys.clear();
         
+        // write signature and version
+        os.write(ByteUtils.convertToBytes(FormatVersion.SIGNATURE));
+        os.write(ByteUtils.convertToBytes(FormatVersion.VERSION));
+        
         int id = processBinarySavable(object);
 
         // write out tag table
-        int ttbytes = 0;
+        int classTableSize = 0;
         int classNum = classes.keySet().size();
-        int aliasWidth = ((int) FastMath.log(classNum, 256) + 1); // make all
-                                                                // aliases a
-                                                                // fixed width
+        int aliasSize = ((int) FastMath.log(classNum, 256) + 1); // make all
+                                                                  // aliases a
+                                                                  // fixed width
+        
         os.write(ByteUtils.convertToBytes(classNum));
         for (String key : classes.keySet()) {
             BinaryClassObject bco = classes.get(key);
 
             // write alias
             byte[] aliasBytes = fixClassAlias(bco.alias,
-                    aliasWidth);
+                    aliasSize);
             os.write(aliasBytes);
-            ttbytes += aliasWidth;
-
+            classTableSize += aliasSize;
+            
+            // jME3 NEW: Write class hierarchy version numbers
+            os.write( bco.classHierarchyVersions.length );
+            for (int version : bco.classHierarchyVersions){
+                os.write(ByteUtils.convertToBytes(version));
+            }
+            classTableSize += 1 + bco.classHierarchyVersions.length * 4;
+            
             // write classname size & classname
             byte[] classBytes = key.getBytes();
             os.write(ByteUtils.convertToBytes(classBytes.length));
             os.write(classBytes);
-            ttbytes += 4 + classBytes.length;
-
+            classTableSize += 4 + classBytes.length;
+            
+            // for each field, write alias, type, and name
             os.write(ByteUtils.convertToBytes(bco.nameFields.size()));
-
             for (String fieldName : bco.nameFields.keySet()) {
                 BinaryClassField bcf = bco.nameFields.get(fieldName);
                 os.write(bcf.alias);
@@ -214,7 +229,7 @@ public class BinaryExporter implements JmeExporter {
                 byte[] fNameBytes = fieldName.getBytes();
                 os.write(ByteUtils.convertToBytes(fNameBytes.length));
                 os.write(fNameBytes);
-                ttbytes += 2 + 4 + fNameBytes.length;
+                classTableSize += 2 + 4 + fNameBytes.length;
             }
         }
 
@@ -242,9 +257,9 @@ public class BinaryExporter implements JmeExporter {
                 alreadySaved.put(savableName + getChunk(pair), bucket);
             }
             bucket.add(pair);
-            byte[] aliasBytes = fixClassAlias(classes.get(savableName).alias, aliasWidth);
+            byte[] aliasBytes = fixClassAlias(classes.get(savableName).alias, aliasSize);
             out.write(aliasBytes);
-            location += aliasWidth;
+            location += aliasSize;
             BinaryOutputCapsule cap = contentTable.get(savable).getContent();
             out.write(ByteUtils.convertToBytes(cap.bytes.length));
             location += 4; // length of bytes
@@ -254,13 +269,13 @@ public class BinaryExporter implements JmeExporter {
 
         // write out location table
         // tag/location
-        int locNum = locationTable.keySet().size();
-        os.write(ByteUtils.convertToBytes(locNum));
-        int locbytes = 0;
+        int numLocations = locationTable.keySet().size();
+        os.write(ByteUtils.convertToBytes(numLocations));
+        int locationTableSize = 0;
         for (Integer key : locationTable.keySet()) {
             os.write(ByteUtils.convertToBytes(key));
             os.write(ByteUtils.convertToBytes(locationTable.get(key)));
-            locbytes += 8;
+            locationTableSize += 8;
         }
 
         // write out number of root ids - hardcoded 1 for now
@@ -278,11 +293,11 @@ public class BinaryExporter implements JmeExporter {
 
         if (debug ) {
             logger.info("Stats:");
-            logger.info("classes: " + classNum);
-            logger.info("class table: " + ttbytes + " bytes");
-            logger.info("objects: " + locNum);
-            logger.info("location table: " + locbytes + " bytes");
-            logger.info("data: " + location + " bytes");
+            logger.log(Level.INFO, "classes: {0}", classNum);
+            logger.log(Level.INFO, "class table: {0} bytes", classTableSize);
+            logger.log(Level.INFO, "objects: {0}", numLocations);
+            logger.log(Level.INFO, "location table: {0} bytes", locationTableSize);
+            logger.log(Level.INFO, "data: {0} bytes", location);
         }
 
         return true;
@@ -331,17 +346,38 @@ public class BinaryExporter implements JmeExporter {
         return contentTable.get(object).getContent();
     }
 
+    private BinaryClassObject createClassObject(Class clazz) throws IOException{
+        BinaryClassObject bco = new BinaryClassObject();
+        bco.alias = generateTag();
+        bco.nameFields = new HashMap<String, BinaryClassField>();
+        
+        ArrayList<Integer> versionList = new ArrayList<Integer>();
+        Class superclass = clazz;
+        do {
+            versionList.add(SavableClassUtil.getSavableVersion(superclass));
+            superclass = superclass.getSuperclass();
+        } while (superclass != null && SavableClassUtil.isImplementingSavable(superclass));
+        
+        int[] versions = new int[versionList.size()];
+        for (int i = 0; i < versionList.size(); i++){
+            versions[i] = versionList.get(i);
+        }
+        bco.classHierarchyVersions = versions;
+        
+        classes.put(clazz.getName(), bco);
+            
+        return bco;
+    }
+    
     public int processBinarySavable(Savable object) throws IOException {
         if (object == null) {
             return -1;
         }
+        Class<? extends Savable> clazz = object.getClass();
         BinaryClassObject bco = classes.get(object.getClass().getName());
         // is this class been looked at before? in tagTable?
         if (bco == null) {
-            bco = new BinaryClassObject();
-            bco.alias = generateTag();
-            bco.nameFields = new HashMap<String, BinaryClassField>();
-            classes.put(object.getClass().getName(), bco);
+            bco = createClassObject(object.getClass());
         }
 
         // is object in contentTable?

+ 59 - 11
engine/src/core-plugins/com/jme3/export/binary/BinaryImporter.java

@@ -32,9 +32,11 @@
 
 package com.jme3.export.binary;
 
-import com.jme3.export.SavableClassFinder;
+import com.jme3.export.SavableClassUtil;
 import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetManager;
+import com.jme3.export.FormatVersion;
+import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.ReadListener;
 import com.jme3.export.Savable;
@@ -57,6 +59,7 @@ import java.util.logging.Logger;
 
 /**
  * @author Joshua Slack
+ * @author Kirill Vainer - Version number, Fast buffer reading
  */
 public final class BinaryImporter implements JmeImporter {
     private static final Logger logger = Logger.getLogger(BinaryImporter.class
@@ -81,6 +84,7 @@ public final class BinaryImporter implements JmeImporter {
 
     private byte[] dataArray;
     private int aliasWidth;
+    private int formatVersion;
 
     private static final boolean fastRead = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
     
@@ -89,6 +93,10 @@ public final class BinaryImporter implements JmeImporter {
     public BinaryImporter() {
     }
     
+    public int getFormatVersion(){
+        return formatVersion;
+    }
+    
     public static boolean canUseFastBuffers(){
         return fastRead;
     }
@@ -139,20 +147,59 @@ public final class BinaryImporter implements JmeImporter {
     public Savable load(InputStream is, ReadListener listener, ByteArrayOutputStream baos) throws IOException {
         contentTable.clear();
         BufferedInputStream bis = new BufferedInputStream(is);
-        int numClasses = ByteUtils.readInt(bis);
+        
+        int numClasses;
+        
+        // Try to read signature
+        int maybeSignature = ByteUtils.readInt(bis);
+        if (maybeSignature == FormatVersion.SIGNATURE){
+            // this is a new version J3O file
+            formatVersion = ByteUtils.readInt(bis);
+            numClasses = ByteUtils.readInt(bis);
+            
+            // check if this binary is from the future
+            if (formatVersion > FormatVersion.VERSION){
+                throw new IOException("The binary file is of newer version than expected! " + 
+                                      formatVersion + " > " + FormatVersion.VERSION);
+            }
+        }else{
+            // this is an old version J3O file
+            // the signature was actually the class count
+            numClasses = maybeSignature;
+            
+            // 0 indicates version before we started adding
+            // version numbers
+            formatVersion = 0; 
+        }
+        
         int bytes = 4;
         aliasWidth = ((int)FastMath.log(numClasses, 256) + 1);
 
         classes.clear();
         for(int i = 0; i < numClasses; i++) {
             String alias = readString(bis, aliasWidth);
-
+            
+            // jME3 NEW: Read class version number
+            int[] classHierarchyVersions;
+            if (formatVersion >= 1){
+                int classHierarchySize = bis.read();
+                classHierarchyVersions = new int[classHierarchySize];
+                for (int j = 0; j < classHierarchySize; j++){
+                    classHierarchyVersions[j] = ByteUtils.readInt(bis);
+                }
+            }else{
+                classHierarchyVersions = new int[]{ 0 };
+            }
+            
+            // read classname and classname size
             int classLength = ByteUtils.readInt(bis);
             String className = readString(bis, classLength);
+            
             BinaryClassObject bco = new BinaryClassObject();
             bco.alias = alias.getBytes();
             bco.className = className;
-
+            bco.classHierarchyVersions = classHierarchyVersions;
+            
             int fields = ByteUtils.readInt(bis);
             bytes += (8 + aliasWidth + classLength);
 
@@ -210,9 +257,9 @@ public final class BinaryImporter implements JmeImporter {
         Savable rVal = readObject(id);
         if (debug) {
             logger.info("Importer Stats: ");
-            logger.info("Tags: "+numClasses);
-            logger.info("Objects: "+numLocs);
-            logger.info("Data Size: "+dataArray.length);
+            logger.log(Level.INFO, "Tags: {0}", numClasses);
+            logger.log(Level.INFO, "Objects: {0}", numLocs);
+            logger.log(Level.INFO, "Data Size: {0}", dataArray.length);
         }
         dataArray = null;
         return rVal;
@@ -247,7 +294,8 @@ public final class BinaryImporter implements JmeImporter {
         return rVal;
     }
 
-    public BinaryInputCapsule getCapsule(Savable id) {
+    @Override
+    public InputCapsule getCapsule(Savable id) {
         return capsuleTable.get(id);
     }
 
@@ -291,11 +339,11 @@ public final class BinaryImporter implements JmeImporter {
             int dataLength = ByteUtils.convertIntFromBytes(dataArray, loc);
             loc+=4;
 
-            BinaryInputCapsule cap = new BinaryInputCapsule(this, bco);
+            Savable out = SavableClassUtil.fromName(bco.className, loaders);
+            
+            BinaryInputCapsule cap = new BinaryInputCapsule(this, out, bco);
             cap.setContent(dataArray, loc, loc+dataLength);
 
-            Savable out = SavableClassFinder.fromName(bco.className, cap, loaders);
-
             capsuleTable.put(out, cap);
             contentTable.put(id, out);
 

+ 23 - 2
engine/src/core-plugins/com/jme3/export/binary/BinaryInputCapsule.java

@@ -39,7 +39,6 @@ import com.jme3.util.IntMap;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
 import java.nio.FloatBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
@@ -60,13 +59,15 @@ final class BinaryInputCapsule implements InputCapsule {
 
     protected BinaryImporter importer;
     protected BinaryClassObject cObj;
+    protected Savable savable;
     protected HashMap<Byte, Object> fieldData;
 
     protected int index = 0;
 
-    public BinaryInputCapsule(BinaryImporter importer, BinaryClassObject bco) {
+    public BinaryInputCapsule(BinaryImporter importer, Savable savable, BinaryClassObject bco) {
         this.importer = importer;
         this.cObj = bco;
+        this.savable = savable;
     }
 
     public void setContent(byte[] content, int start, int limit) {
@@ -255,6 +256,26 @@ final class BinaryInputCapsule implements InputCapsule {
             }
         }
     }
+    
+    public int getSavableVersion(Class<? extends Savable> desiredClass){
+        Class thisClass = savable.getClass();
+        int count = 0;
+        while (thisClass != null && thisClass != desiredClass){
+            thisClass = thisClass.getSuperclass();
+            count ++;
+        }
+        if (thisClass == null){
+            throw new IllegalArgumentException(savable.getClass().getName() + 
+                                               " does not extend " + 
+                                               desiredClass.getName() + "!");
+        }else if (count > cObj.classHierarchyVersions.length){
+            throw new IllegalArgumentException(savable.getClass().getName() + 
+                                               " cannot access version of " +
+                                               desiredClass.getName() + 
+                                               " because it doesn't implement Savable");
+        }
+        return cObj.classHierarchyVersions[count];
+    }
 
     public BitSet readBitSet(String name, BitSet defVal) throws IOException {
         BinaryClassField field = cObj.nameFields.get(name);

+ 0 - 62
engine/src/core-plugins/com/jme3/export/binary/BinaryLoaderModule.java

@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2009-2010 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.export.binary;
-
-import com.jme3.export.InputCapsule;
-import com.jme3.export.Savable;
-import java.io.IOException;
-
-/**
- * BinaryLoaderModule defines two methods, the first provides a key value to
- * look for to issue the load command. This key is typically (and should be)
- * the class name the loader is responsible for. While load handles creating
- * a new instance of the class.
- * @author mpowell
- *
- */
-interface BinaryLoaderModule {
-    
-    String getKey();
-
-    /**
-     * The inputCapsule parameter is not used at all.
-     *
-     * The DOMOutputStream class calls this method with a null parameter, so
-     * if you make use of the parameter, either handle null 'inputCapsule'
-     * or rearrange the class hierarchy to satisfy DOMOuptutStream.
-     *
-     * @param inputCapsule  A value which is currently ignored by all
-     *                      implementation classes.
-     */
-    Savable load(InputCapsule inputCapsule) throws IOException;
-}

+ 12 - 12
engine/src/core/com/jme3/animation/AnimControl.java

@@ -39,7 +39,6 @@ import com.jme3.export.Savable;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.Mesh;
-import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
@@ -68,7 +67,7 @@ import java.util.HashMap;
  *
  * @author Kirill Vainer
  */
-public final class AnimControl extends AbstractControl implements Savable, Cloneable {
+public final class AnimControl extends AbstractControl implements Cloneable {
 
     /**
      * Skeleton object must contain corresponding data for the targets' weight buffers.
@@ -351,16 +350,17 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
         skeleton = (Skeleton) in.readSavable("skeleton", null);
         animationMap = (HashMap<String, BoneAnimation>) in.readStringSavableMap("animations", null);
 
-        //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
-        //if we find a target mesh array the AnimControl creates the SkeletonControl for old files and add it to the spatial.        
-        //When backward compatibility won't be needed anymore this can deleted        
-        Savable[] sav = in.readSavableArray("targets", null);
-        if (sav != null) {
-            Mesh[] tg = null;
-            tg = new Mesh[sav.length];
-            System.arraycopy(sav, 0, tg, 0, sav.length);
-            skeletonControl = new SkeletonControl(tg, skeleton);
-            spatial.addControl(skeletonControl);
+        if (im.getFormatVersion() == 0){
+            //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
+            //if we find a target mesh array the AnimControl creates the SkeletonControl for old files and add it to the spatial.        
+            //When backward compatibility won't be needed anymore this can deleted        
+            Savable[] sav = in.readSavableArray("targets", null);
+            if (sav != null) {
+                Mesh[] targets = new Mesh[sav.length];
+                System.arraycopy(sav, 0, targets, 0, sav.length);
+                skeletonControl = new SkeletonControl(targets, skeleton);
+                spatial.addControl(skeletonControl);
+            }
         }
     }
 }

+ 19 - 19
engine/src/core/com/jme3/animation/BoneTrack.java

@@ -268,27 +268,27 @@ public final class BoneTrack implements Savable {
 
 
         //Backward compatibility for old j3o files generated before revision 6807
-        if (translations == null) {
-            Savable[] sav = ic.readSavableArray("translations", null);
-            if (sav != null) {
-                translations = new CompactVector3Array();
-                Vector3f[] transCopy = new Vector3f[sav.length];
-                System.arraycopy(sav, 0, transCopy, 0, sav.length);
-                translations.add(transCopy);
-                translations.freeze();
+        if (im.getFormatVersion() == 0){
+            if (translations == null) {
+                Savable[] sav = ic.readSavableArray("translations", null);
+                if (sav != null) {
+                    translations = new CompactVector3Array();
+                    Vector3f[] transCopy = new Vector3f[sav.length];
+                    System.arraycopy(sav, 0, transCopy, 0, sav.length);
+                    translations.add(transCopy);
+                    translations.freeze();
+                }
             }
-        }
-        if (rotations == null) {
-            Savable[] sav = ic.readSavableArray("rotations", null);
-            if (sav != null) {
-                rotations = new CompactQuaternionArray();
-                Quaternion[] rotCopy = new Quaternion[sav.length];
-                System.arraycopy(sav, 0, rotCopy, 0, sav.length);
-                rotations.add(rotCopy);
-                rotations.freeze();
+            if (rotations == null) {
+                Savable[] sav = ic.readSavableArray("rotations", null);
+                if (sav != null) {
+                    rotations = new CompactQuaternionArray();
+                    Quaternion[] rotCopy = new Quaternion[sav.length];
+                    System.arraycopy(sav, 0, rotCopy, 0, sav.length);
+                    rotations.add(rotCopy);
+                    rotations.freeze();
+                }
             }
         }
-
-
     }
 }

+ 87 - 17
engine/src/core/com/jme3/animation/SkeletonControl.java

@@ -17,6 +17,7 @@ import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
+import com.jme3.scene.UserData;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.control.AbstractControl;
@@ -25,14 +26,16 @@ import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
+import java.util.ArrayList;
 
 /**
  * The Skeleton control deforms a model according to a skeleton, 
- * It handles the computation of the deformtation matrices and performs the transformations on the mesh
+ * It handles the computation of the deformation matrices and performs 
+ * the transformations on the mesh
  * 
  * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
  */
-public class SkeletonControl extends AbstractControl implements Savable, Cloneable {
+public class SkeletonControl extends AbstractControl implements Cloneable {
 
     /**
      * The skeleton of the model
@@ -49,21 +52,90 @@ public class SkeletonControl extends AbstractControl implements Savable, Cloneab
     private boolean wasMeshUpdated = false;
 
     /**
-     * for serialization only
+     * Serialization only. Do not use.
      */
     public SkeletonControl() {
     }
 
     /**
-     * Creates a skeleton control 
-     * @param targets the meshes controled by the skeleton
+     * Creates a skeleton control.
+     * The list of targets will be acquired automatically when
+     * the control is attached to a node.
+     * 
+     * @param skeleton the skeleton
+     */
+    public SkeletonControl(Skeleton skeleton) {
+        this.skeleton = skeleton;
+    }
+    
+    /**
+     * Creates a skeleton control.
+     * 
+     * @param targets the meshes controlled by the skeleton
      * @param skeleton the skeleton
      */
-    public SkeletonControl(Mesh[] targets, Skeleton skeleton) {
+    @Deprecated
+    public SkeletonControl(Mesh[] targets, Skeleton skeleton){
         this.skeleton = skeleton;
         this.targets = targets;
     }
+    
+    private boolean isMeshAnimated(Mesh mesh){
+        return mesh.getBuffer(Type.BindPosePosition) != null;
+    }
 
+    private Mesh[] findTargets(Node node){
+        Mesh sharedMesh = null;
+        ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
+        
+        for (Spatial child : node.getChildren()){
+            if (!(child instanceof Geometry)){
+                continue; // could be an attachment node, ignore.
+            }
+            
+            Geometry geom = (Geometry) child;
+            
+            // is this geometry using a shared mesh?
+            Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
+            
+            if (childSharedMesh != null){
+                
+                // Don't bother with non-animated shared meshes
+                if (isMeshAnimated(childSharedMesh)){
+                    
+                    // child is using shared mesh,
+                    // so animate the shared mesh but ignore child
+                    if (sharedMesh == null){
+                        sharedMesh = childSharedMesh;
+                    }else if (sharedMesh != childSharedMesh){
+                        throw new IllegalStateException("Two conflicting shared meshes for " + node);
+                    }
+                }
+            }else{
+                Mesh mesh = geom.getMesh();
+                if (isMeshAnimated(mesh)){
+                    animatedMeshes.add(mesh);
+                }
+            }
+        }
+        
+        if (sharedMesh != null){
+            animatedMeshes.add(sharedMesh);
+        }
+        
+        return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
+    }
+    
+    @Override
+    public void setSpatial(Spatial spatial){
+        if (spatial != null){
+            Node node = (Node) spatial;
+            targets = findTargets(node);
+        }else{
+            targets = null;
+        }
+    }
+    
     @Override
     protected void controlRender(RenderManager rm, ViewPort vp) {
         if (!wasMeshUpdated) {
@@ -87,13 +159,11 @@ public class SkeletonControl extends AbstractControl implements Savable, Cloneab
     @Override
     protected void controlUpdate(float tpf) {
         wasMeshUpdated = false;
-
     }
 
     void resetToBind() {
-        for (int i = 0; i < targets.length; i++) {
-            Mesh mesh = targets[i];
-            if (targets[i].getBuffer(Type.BindPosePosition) != null) {
+        for (Mesh mesh : targets){
+            if (isMeshAnimated(mesh)) {
                 VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
                 ByteBuffer bib = (ByteBuffer) bi.getData();
                 if (!bib.hasArray()) {
@@ -177,12 +247,12 @@ public class SkeletonControl extends AbstractControl implements Savable, Cloneab
      * sets the skeleton for this control
      * @param skeleton 
      */
-    public void setSkeleton(Skeleton skeleton) {
-        this.skeleton = skeleton;
-    }
+//    public void setSkeleton(Skeleton skeleton) {
+//        this.skeleton = skeleton;
+//    }
 
     /**
-     * retuns the targets meshes of this ocntrol
+     * returns the targets meshes of this control
      * @return 
      */
     public Mesh[] getTargets() {
@@ -193,9 +263,9 @@ public class SkeletonControl extends AbstractControl implements Savable, Cloneab
      * sets the target  meshes of this control
      * @param targets 
      */
-    public void setTargets(Mesh[] targets) {
-        this.targets = targets;
-    }
+//    public void setTargets(Mesh[] targets) {
+//        this.targets = targets;
+//    }
 
     private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
         int maxWeightsPerVert = mesh.getMaxNumWeights();

+ 1 - 1
engine/src/core/com/jme3/cinematic/Cinematic.java

@@ -61,7 +61,7 @@ import java.util.logging.Logger;
  *
  * @author Nehon
  */
-public class Cinematic extends AbstractCinematicEvent implements Savable, AppState {
+public class Cinematic extends AbstractCinematicEvent implements AppState {
 
     private static final Logger logger = Logger.getLogger(Application.class.getName());
     private String niftyXmlPath = null;

+ 1 - 1
engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java

@@ -48,7 +48,7 @@ import java.util.List;
  *
  * @author Nehon
  */
-public abstract class AbstractCinematicEvent implements CinematicEvent, Savable {
+public abstract class AbstractCinematicEvent implements CinematicEvent {
 
     protected PlayState playState = PlayState.Stopped;
     protected float speed = 1;

+ 2 - 1
engine/src/core/com/jme3/cinematic/events/CinematicEvent.java

@@ -35,12 +35,13 @@ import com.jme3.animation.LoopMode;
 import com.jme3.app.Application;
 import com.jme3.cinematic.Cinematic;
 import com.jme3.cinematic.PlayState;
+import com.jme3.export.Savable;
 
 /**
  *
  * @author Nehon
  */
-public interface CinematicEvent {
+public interface CinematicEvent extends Savable {
 
     /**
      * Starts the animation

+ 17 - 15
engine/src/core/com/jme3/effect/ParticleEmitter.java

@@ -1118,23 +1118,25 @@ public class ParticleEmitter extends Geometry {
 
         particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER);
 
-        // compatibility before the control inside particle emitter
-        // was changed:
-        // find it in the controls and take it out, then add the proper one in
-        for (int i = 0; i < controls.size(); i++) {
-            Object obj = controls.get(i);
-            if (obj instanceof ParticleEmitter) {
-                controls.remove(i);
-                // now add the proper one in
-                controls.add(control);
-                break;
+        if (im.getFormatVersion() == 0){
+            // compatibility before the control inside particle emitter
+            // was changed:
+            // find it in the controls and take it out, then add the proper one in
+            for (int i = 0; i < controls.size(); i++){
+                Object obj = controls.get(i);
+                if (obj instanceof ParticleEmitter){
+                    controls.remove(i);
+                    // now add the proper one in
+                    controls.add(control);
+                    break;
+                }
             }
-        }
 
-        // compatability before gravity was not a vector but a float
-        if (gravity == null) {
-            gravity = new Vector3f();
-            gravity.y = ic.readFloat("gravity", 0);
+            // compatability before gravity was not a vector but a float
+            if (gravity == null){
+                gravity = new Vector3f();
+                gravity.y = ic.readFloat("gravity", 0);
+            }
         }
     }
 }

+ 1 - 1
engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java

@@ -56,7 +56,7 @@ public class DefaultParticleInfluencer implements ParticleInfluencer {
     @Override
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
-        startVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO);
+        startVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone());
         velocityVariation = ic.readFloat("variation", 0.2f);
     }
 

+ 9 - 1
engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java

@@ -1,5 +1,6 @@
 package com.jme3.effect.shapes;
 
+import com.jme3.export.InputCapsule;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -141,11 +142,18 @@ public class EmitterMeshVertexShape implements EmitterShape {
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
         oc.writeSavableArrayList((ArrayList<List<Vector3f>>) vertices, "vertices", null);
+        oc.writeSavableArrayList((ArrayList<List<Vector3f>>) normals, "normals", null);
     }
 
     @Override
     @SuppressWarnings("unchecked")
     public void read(JmeImporter im) throws IOException {
-        this.vertices = im.getCapsule(this).readSavableArrayList("vertices", null);
+        InputCapsule ic = im.getCapsule(this);
+        this.vertices = ic.readSavableArrayList("vertices", null);
+        
+        List<List<Vector3f>> tmpNormals = ic.readSavableArrayList("normals", null);
+        if (tmpNormals != null){
+            this.normals = tmpNormals;
+        }
     }
 }

+ 2 - 0
engine/src/core/com/jme3/export/InputCapsule.java

@@ -47,6 +47,8 @@ import java.util.Map;
  */
 public interface InputCapsule {
 
+    public int getSavableVersion(Class<? extends Savable> clazz);
+    
     // byte primitive
 
     public byte readByte(String name, byte defVal) throws IOException;

+ 8 - 0
engine/src/core/com/jme3/export/JmeImporter.java

@@ -38,4 +38,12 @@ import com.jme3.asset.AssetManager;
 public interface JmeImporter extends AssetLoader {
     public InputCapsule getCapsule(Savable id);
     public AssetManager getAssetManager();
+    
+    /**
+     * Returns the version number written in the header of the J3O/XML
+     * file.
+     * 
+     * @return Global version number for the file
+     */
+    public int getFormatVersion();
 }

+ 2 - 1
engine/src/core/com/jme3/export/Savable.java

@@ -37,7 +37,8 @@ import java.io.IOException;
 /**
  * <code>Savable</code> is an interface for objects that can be serialized
  * using jME's serialization system. 
- * @author Dany
+ * 
+ * @author Kirill Vainer
  */
 public interface Savable {
     void write(JmeExporter ex) throws IOException;

+ 39 - 13
engine/src/core/com/jme3/export/SavableClassFinder.java → engine/src/core/com/jme3/export/SavableClassUtil.java

@@ -41,26 +41,27 @@ import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.export.InputCapsule;
-import com.jme3.export.Savable;
 import com.jme3.material.MatParamTexture;
+import com.jme3.scene.Spatial;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 
 /**
- * <code>SavableClassFinder</code> is used to find classes referenced
- * by savables.
+ * <code>SavableClassUtil</code> contains various utilities to handle
+ * Savable classes. The methods are general enough to not be specific to any
+ * particular implementation.
  * Currently it will remap any classes from old paths to new paths
  * so that old J3O models can still be loaded.
  *
  * @author mpowell
  * @author Kirill Vainer
  */
-public class SavableClassFinder {
+public class SavableClassUtil {
 
     private final static HashMap<String, String> classRemappings = new HashMap<String, String>();
-
+    
     private static void addRemapping(String oldClass, Class<? extends Savable> newClass){
         classRemappings.put(oldClass, newClass.getName());
     }
@@ -83,7 +84,32 @@ public class SavableClassFinder {
             return result;
         }
     }
+    
+    public static boolean isImplementingSavable(Class clazz){
+        Class[] interfaces = clazz.getInterfaces();
+        for (Class interfaceClass : interfaces){
+            if (interfaceClass == Savable.class){
+                return true;
+            }
+        }
+        return false;
+    }
 
+    public static int getSavableVersion(Class<? extends Savable> clazz) throws IOException{
+        try {
+            Field field = clazz.getField("SAVABLE_VERSION");
+            return field.getInt(null);
+        } catch (IllegalAccessException ex) {
+            IOException ioEx = new IOException();
+            ioEx.initCause(ex);
+            throw ioEx;
+        } catch (IllegalArgumentException ex) {
+            throw ex; // can happen if SAVABLE_VERSION is not static
+        } catch (NoSuchFieldException ex) {
+            return 0; // not using versions
+        }
+    }
+    
     /**
      * fromName creates a new Savable from the provided class name. First registered modules
      * are checked to handle special cases, if the modules do not handle the class name, the
@@ -96,29 +122,29 @@ public class SavableClassFinder {
      * @throws ClassNotFoundException thrown if the class name is not in the classpath.
      * @throws IOException when loading ctor parameters fails
      */
-    public static Savable fromName(String className, InputCapsule inputCapsule) throws InstantiationException,
+    public static Savable fromName(String className) throws InstantiationException,
             IllegalAccessException, ClassNotFoundException, IOException {
 
         className = remapClass(className);
         try {
             return (Savable) Class.forName(className).newInstance();
         } catch (InstantiationException e) {
-            Logger.getLogger(SavableClassFinder.class.getName()).log(
+            Logger.getLogger(SavableClassUtil.class.getName()).log(
                     Level.SEVERE, "Could not access constructor of class ''{0}" + "''! \n"
                     + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", className);
             throw e;
         } catch (IllegalAccessException e) {
-            Logger.getLogger(SavableClassFinder.class.getName()).log(
+            Logger.getLogger(SavableClassUtil.class.getName()).log(
                     Level.SEVERE, "{0} \n"
                     + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", e.getMessage());
             throw e;
         }
     }
 
-    public static Savable fromName(String className, InputCapsule inputCapsule, List<ClassLoader> loaders) throws InstantiationException,
+    public static Savable fromName(String className, List<ClassLoader> loaders) throws InstantiationException,
             IllegalAccessException, ClassNotFoundException, IOException {
         if (loaders == null) {
-            return fromName(className, inputCapsule);
+            return fromName(className);
         }
         
         String newClassName = remapClass(className);
@@ -131,6 +157,6 @@ public class SavableClassFinder {
 
         }
 
-        return fromName(className, inputCapsule);
+        return fromName(className);
     }
 }

+ 6 - 0
engine/src/core/com/jme3/scene/UserData.java

@@ -53,6 +53,12 @@ public final class UserData implements Savable {
      */
     public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore";
     
+    /**
+     * For geometries using shared mesh, this will specify the shared
+     * mesh reference.
+     */
+    public static final String JME_SHAREDMESH = "JmeSharedMesh";
+    
     protected byte type;
     protected Object value;
 

+ 1 - 1
engine/src/core/com/jme3/scene/VertexBuffer.java

@@ -63,7 +63,7 @@ import java.nio.ShortBuffer;
  * </ul>
  */
 public class VertexBuffer extends GLObject implements Savable, Cloneable {
-
+  
     /**
      * Type of buffer. Specifies the actual attribute it defines.
      */

+ 2 - 2
engine/src/core/com/jme3/terrain/AbstractGeomap.java

@@ -44,7 +44,7 @@ import java.nio.IntBuffer;
 
 /**
  * implements all writeXXXXArray methods to reduce boilerplate code
- * Geomap implementations are encourged to extend this class
+ * Geomap implementations are encouraged to extend this class
  */
 public abstract class AbstractGeomap implements Geomap {
 
@@ -60,7 +60,7 @@ public abstract class AbstractGeomap implements Geomap {
 
     /*
      * (non-Javadoc)
-     * Subclasses are encourged to provide a better implementation
+     * Subclasses are encouraged to provide a better implementation
      * which directly accesses the data rather than using getHeight
      */
     public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center){

+ 150 - 86
engine/src/core/com/jme3/util/ListMap.java

@@ -33,9 +33,8 @@
 package com.jme3.util;
 
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -92,35 +91,48 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
 
     }
     
-    private final ArrayList<ListMapEntry<K,V>> entries;
+    private final HashMap<K, V> backingMap;
+    private ListMapEntry<K, V>[] entries;
+    
+//    private final ArrayList<ListMapEntry<K,V>> entries;
 
     public ListMap(){
-       entries = new ArrayList<ListMapEntry<K,V>>();
+        entries = new ListMapEntry[4];
+        backingMap = new HashMap<K, V>(4);
+//       entries = new ArrayList<ListMapEntry<K,V>>();
     }
 
     public ListMap(int initialCapacity){
-        entries = new ArrayList<ListMapEntry<K, V>>(initialCapacity);
+        entries = new ListMapEntry[initialCapacity];
+        backingMap = new HashMap<K, V>(initialCapacity);
+//        entries = new ArrayList<ListMapEntry<K, V>>(initialCapacity);
     }
 
     public ListMap(Map<? extends K, ? extends V> map){
-        entries = new ArrayList<ListMapEntry<K, V>>(map.size());
+        entries = new ListMapEntry[map.size()];
+        backingMap = new HashMap<K, V>(map.size());
+//        entries = new ArrayList<ListMapEntry<K, V>>(map.size());
         putAll(map);
     }
 
     public int size() {
-        return entries.size();
+//        return entries.size();
+        return backingMap.size();
     }
 
     public Entry<K, V> getEntry(int index){
-        return entries.get(index);
+//        return entries.get(index);
+        return entries[index];
     }
 
     public V getValue(int index){
-        return entries.get(index).value;
+//        return entries.get(index).value;
+        return entries[index].value;
     }
 
     public K getKey(int index){
-        return entries.get(index).key;
+//        return entries.get(index).key;
+        return entries[index].key;
     }
 
     public boolean isEmpty() {
@@ -130,93 +142,142 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
     private static boolean keyEq(Object keyA, Object keyB){
         return keyA.hashCode() == keyB.hashCode() ? (keyA == keyB) || keyA.equals(keyB) : false;
     }
-
-    private static boolean valEq(Object a, Object b){
-        return a == null ? (b == null) : a.equals(b);
-    }
+//
+//    private static boolean valEq(Object a, Object b){
+//        return a == null ? (b == null) : a.equals(b);
+//    }
 
     public boolean containsKey(Object key) {
-        if (key == null)
-            throw new IllegalArgumentException();
-
-        for (int i = 0; i < entries.size(); i++){
-            ListMapEntry<K,V> entry = entries.get(i);
-            if (keyEq(entry.key, key))
-                return true;
-        }
-        return false;
+        return backingMap.containsKey( (K) key); 
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key))
+//                return true;
+//        }
+//        return false;
     }
 
     public boolean containsValue(Object value) {
-        for (int i = 0; i < entries.size(); i++){
-            if (valEq(entries.get(i).value, value))
-                return true;
-        }
-        return false;
+        return backingMap.containsValue( (V) value); 
+//        for (int i = 0; i < entries.size(); i++){
+//            if (valEq(entries.get(i).value, value))
+//                return true;
+//        }
+//        return false;
     }
 
     public V get(Object key) {
-        if (key == null)
-            throw new IllegalArgumentException();
-
-        for (int i = 0; i < entries.size(); i++){
-            ListMapEntry<K,V> entry = entries.get(i);
-            if (keyEq(entry.key, key))
-                return entry.value;
-        }
-        return null;
+        return backingMap.get( (K) key); 
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key))
+//                return entry.value;
+//        }
+//        return null;
     }
 
     public V put(K key, V value) {
-        if (key == null)
-            throw new IllegalArgumentException();
-
-        // check if entry exists, if yes, overwrite it with new value
-        for (int i = 0; i < entries.size(); i++){
-            ListMapEntry<K,V> entry = entries.get(i);
-            if (keyEq(entry.key, key)){
-                V prevValue = entry.value;
-                entry.value = value;
-                return prevValue;
+        if (backingMap.containsKey(key)){
+            // set the value on the entry
+            int size = size();
+            for (int i = 0; i < size; i++){
+                ListMapEntry<K, V> entry = entries[i];
+                if (keyEq(entry.key, key)){
+                    entry.value = value;
+                    break;
+                }
+            }
+        }else{
+            int size = size();
+            // expand list as necessary
+            if (size == entries.length){
+                ListMapEntry<K, V>[] tmpEntries = entries;
+                entries = new ListMapEntry[size * 2];
+                System.arraycopy(tmpEntries, 0, entries, 0, size);
             }
+            entries[size] = new ListMapEntry<K, V>(key, value);
         }
-        
-        // add a new entry
-        entries.add(new ListMapEntry<K, V>(key, value));
-        return null;
+        return backingMap.put(key, value);
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        // check if entry exists, if yes, overwrite it with new value
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key)){
+//                V prevValue = entry.value;
+//                entry.value = value;
+//                return prevValue;
+//            }
+//        }
+//        
+//        // add a new entry
+//        entries.add(new ListMapEntry<K, V>(key, value));
+//        return null;
     }
 
     public V remove(Object key) {
-        if (key == null)
-            throw new IllegalArgumentException();
-
-        for (int i = 0; i < entries.size(); i++){
-            ListMapEntry<K,V> entry = entries.get(i);
-            if (keyEq(entry.key, key)){
-                return entries.remove(i).value;
+        V element = backingMap.remove( (K) key);
+        if (element != null){
+            // find removed element
+            int size = size() + 1; // includes removed element
+            int removedIndex = -1;
+            for (int i = 0; i < size; i++){
+                ListMapEntry<K, V> entry = entries[i];
+                if (keyEq(entry.key, key)){
+                    removedIndex = i;
+                    break;
+                }
+            }
+            assert removedIndex >= 0;
+            
+            size --;
+            for (int i = removedIndex; i < size; i++){
+                entries[i] = entries[i+1];
             }
         }
-        return null;
+        return element;
+//        if (key == null)
+//            throw new IllegalArgumentException();
+//
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            if (keyEq(entry.key, key)){
+//                return entries.remove(i).value;
+//            }
+//        }
+//        return null;
     }
 
     public void putAll(Map<? extends K, ? extends V> map) {
-        if (map instanceof ListMap){
-            ListMap<K, V> listMap = (ListMap<K, V>) map;
-            ArrayList<ListMapEntry<K, V>> otherEntries = listMap.entries;
-            for (int i = 0; i < otherEntries.size(); i++){
-                ListMapEntry<K, V> entry = otherEntries.get(i);
-                put(entry.key, entry.value);
-            }
-        }else{
-            for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()){
-                put(entry.getKey(), entry.getValue());
-            }
+        for (Entry<? extends K, ? extends V> entry : map.entrySet()){
+            put(entry.getKey(), entry.getValue());
         }
         
+        
+//        if (map instanceof ListMap){
+//            ListMap<K, V> listMap = (ListMap<K, V>) map;
+//            ArrayList<ListMapEntry<K, V>> otherEntries = listMap.entries;
+//            for (int i = 0; i < otherEntries.size(); i++){
+//                ListMapEntry<K, V> entry = otherEntries.get(i);
+//                put(entry.key, entry.value);
+//            }
+//        }else{
+//            for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()){
+//                put(entry.getKey(), entry.getValue());
+//            }
+//        }
     }
 
     public void clear() {
-        entries.clear();
+        backingMap.clear();
+//        entries.clear();
     }
 
     @Override
@@ -227,27 +288,30 @@ public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
     }
 
     public Set<K> keySet() {
-        HashSet<K> keys = new HashSet<K>();
-        for (int i = 0; i < entries.size(); i++){
-            ListMapEntry<K,V> entry = entries.get(i);
-            keys.add(entry.key);
-        }
-        return keys;
+        return backingMap.keySet();
+//        HashSet<K> keys = new HashSet<K>();
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            keys.add(entry.key);
+//        }
+//        return keys;
     }
 
     public Collection<V> values() {
-        ArrayList<V> values = new ArrayList<V>();
-        for (int i = 0; i < entries.size(); i++){
-            ListMapEntry<K,V> entry = entries.get(i);
-            values.add(entry.value);
-        }
-        return values;
+        return backingMap.values();
+//        ArrayList<V> values = new ArrayList<V>();
+//        for (int i = 0; i < entries.size(); i++){
+//            ListMapEntry<K,V> entry = entries.get(i);
+//            values.add(entry.value);
+//        }
+//        return values;
     }
 
     public Set<Entry<K, V>> entrySet() {
-        HashSet<Entry<K, V>> entryset = new HashSet<Entry<K, V>>();
-        entryset.addAll(entries);
-        return entryset;
+        return backingMap.entrySet();
+//        HashSet<Entry<K, V>> entryset = new HashSet<Entry<K, V>>();
+//        entryset.addAll(entries);
+//        return entryset;
     }
 
 }

+ 106 - 129
engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java

@@ -47,6 +47,7 @@ import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial.CullHint;
+import com.jme3.scene.UserData;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.VertexBuffer.Format;
 import com.jme3.scene.VertexBuffer.Type;
@@ -101,28 +102,30 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     private String folderName;
     private AssetManager assetManager;
     private MaterialList materialList;
+    
+    // Data per submesh/sharedgeom
     private ShortBuffer sb;
     private IntBuffer ib;
     private FloatBuffer fb;
     private VertexBuffer vb;
     private Mesh mesh;
     private Geometry geom;
-    private Mesh sharedmesh;
-    private Geometry sharedgeom;
-    private int geomIdx = 0;
-    private int texCoordIdx = 0;
-    private static volatile int nodeIdx = 0;
-    private String ignoreUntilEnd = null;
-    private boolean bigindices = false;
+    private ByteBuffer indicesData;
+    private FloatBuffer weightsFloatData;
     private int vertCount;
-    private int triCount;
+    private boolean usesSharedVerts;
+    private boolean usesBigIndices;
+    
+    // Global data
+    private Mesh sharedMesh;
+    private int meshIndex = 0;
+    private int texCoordIndex = 0;
+    private String ignoreUntilEnd = null;
+    
     private List<Geometry> geoms = new ArrayList<Geometry>();
-    private List<Boolean> usesSharedGeom = new ArrayList<Boolean>();
     private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
     private AnimData animData;
-    private ByteBuffer indicesData;
-    private FloatBuffer weightsFloatData;
-
+    
     public MeshLoader() {
         super();
     }
@@ -130,7 +133,6 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     @Override
     public void startDocument() {
         geoms.clear();
-        usesSharedGeom.clear();
         lodLevels.clear();
 
         sb = null;
@@ -139,14 +141,12 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         vb = null;
         mesh = null;
         geom = null;
-        sharedgeom = null;
-        sharedmesh = null;
+        sharedMesh = null;
 
+        usesSharedVerts = false;
         vertCount = 0;
-        triCount = 0;
-        geomIdx = 0;
-        texCoordIdx = 0;
-        nodeIdx = 0;
+        meshIndex = 0;
+        texCoordIndex = 0;
         ignoreUntilEnd = null;
 
         animData = null;
@@ -171,28 +171,23 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
             sb.put((short) i1).put((short) i2).put((short) i3);
         }
     }
+    
+    private boolean isUsingSharedVerts(Geometry geom){
+        return geom.getUserData(UserData.JME_SHAREDMESH) != null;
+    }
 
     private void startFaces(String count) throws SAXException {
         int numFaces = parseInt(count);
         int numIndices;
 
         if (mesh.getMode() == Mesh.Mode.Triangles) {
-            //mesh.setTriangleCount(numFaces);
             numIndices = numFaces * 3;
         } else {
             throw new SAXException("Triangle strip or fan not supported!");
         }
 
-        int numVerts;
-        if (usesSharedGeom.size() > 0 && usesSharedGeom.get(geoms.size() - 1)) {
-//            sharedgeom.getMesh().updateCounts();
-            numVerts = sharedmesh.getVertexCount();
-        } else {
-//            mesh.updateCounts();
-            numVerts = mesh.getVertexCount();
-        }
         vb = new VertexBuffer(VertexBuffer.Type.Index);
-        if (!bigindices) {
+        if (!usesBigIndices) {
             sb = BufferUtils.createShortBuffer(numIndices);
             ib = null;
             vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
@@ -226,16 +221,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         if (mat.isTransparent()) {
             geom.setQueueBucket(Bucket.Transparent);
         }
-//        else
-//            geom.setShadowMode(ShadowMode.CastAndReceive);
-
-//        if (mat.isReceivesShadows())
-
-
+        
         geom.setMaterial(mat);
     }
 
-    private void startMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
+    private void startSubMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
         mesh = new Mesh();
         if (opType == null || opType.equals("triangle_list")) {
             mesh.setMode(Mesh.Mode.Triangles);
@@ -245,24 +235,25 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
             mesh.setMode(Mesh.Mode.TriangleFan);
         }
 
-        bigindices = parseBool(use32bitIndices, false);
-        boolean sharedverts = parseBool(usesharedvertices, false);
-        if (sharedverts) {
-            usesSharedGeom.add(true);
+        usesBigIndices = parseBool(use32bitIndices, false);
+        usesSharedVerts = parseBool(usesharedvertices, false);
+        if (usesSharedVerts) {
             // import vertexbuffers from shared geom
-            IntMap<VertexBuffer> sharedBufs = sharedmesh.getBuffers();
+            IntMap<VertexBuffer> sharedBufs = sharedMesh.getBuffers();
             for (Entry<VertexBuffer> entry : sharedBufs) {
                 mesh.setBuffer(entry.getValue());
             }
-            // this mesh is shared!
-        } else {
-            usesSharedGeom.add(false);
         }
 
         if (meshName == null) {
-            geom = new Geometry("OgreSubmesh-" + (++geomIdx), mesh);
+            geom = new Geometry("OgreSubmesh-" + (++meshIndex), mesh);
         } else {
-            geom = new Geometry(meshName + "-geom-" + (++geomIdx), mesh);
+            geom = new Geometry(meshName + "-geom-" + (++meshIndex), mesh);
+        }
+        
+        if (usesSharedVerts){
+            // this mesh is shared!
+            geom.setUserData(UserData.JME_SHAREDMESH, sharedMesh);
         }
 
         applyMaterial(geom, matName);
@@ -270,27 +261,16 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void startSharedGeom(String vertexcount) throws SAXException {
-        sharedmesh = new Mesh();
+        sharedMesh = new Mesh();
         vertCount = parseInt(vertexcount);
-//        sharedmesh.setVertexCount(vertCount);
+        usesSharedVerts = false;
 
-        if (meshName == null) {
-            sharedgeom = new Geometry("Ogre-SharedGeom", sharedmesh);
-        } else {
-            sharedgeom = new Geometry(meshName + "-sharedgeom", sharedmesh);
-        }
-
-        sharedgeom.setCullHint(CullHint.Always);
-        geoms.add(sharedgeom);
-        usesSharedGeom.add(false); // shared geometry doesnt use shared geometry (?)
-
-        geom = sharedgeom;
-        mesh = sharedmesh;
+        geom = null;
+        mesh = sharedMesh;
     }
 
     private void startGeometry(String vertexcount) throws SAXException {
         vertCount = parseInt(vertexcount);
-//        mesh.setVertexCount(vertCount);
     }
 
     /**
@@ -298,7 +278,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
      * for all vertices in the buffer.
      */
     private void endBoneAssigns() {
-        if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)) {
+        if (mesh != sharedMesh && isUsingSharedVerts(geom)) {
             return;
         }
 
@@ -341,7 +321,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void startBoneAssigns() {
-        if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)) {
+        if (mesh != sharedMesh && usesSharedVerts) {
             // will use bone assignments from shared mesh (?)
             return;
         }
@@ -424,7 +404,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void startVertex() {
-        texCoordIdx = 0;
+        texCoordIndex = 0;
     }
 
     private void pushAttrib(Type type, Attributes attribs) throws SAXException {
@@ -450,10 +430,10 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void pushTexCoord(Attributes attribs) throws SAXException {
-        if (texCoordIdx >= 8) {
+        if (texCoordIndex >= 8) {
             return; // More than 8 not supported by ogre.
         }
-        Type type = TEXCOORD_TYPES[texCoordIdx];
+        Type type = TEXCOORD_TYPES[texCoordIndex];
 
         VertexBuffer tcvb = mesh.getBuffer(type);
         FloatBuffer buf = (FloatBuffer) tcvb.getData();
@@ -469,7 +449,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
             }
         }
 
-        texCoordIdx++;
+        texCoordIndex++;
     }
 
     private void pushColor(Attributes attribs) throws SAXException {
@@ -529,7 +509,6 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     }
 
     private void startLodGenerated(String depthsqr) {
-//        dist = Float.parseFloat(depthsqr);
     }
 
     private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException {
@@ -560,10 +539,6 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
 
     private void startSkeleton(String name) {
         animData = (AnimData) assetManager.loadAsset(folderName + name + ".xml");
-        //TODO:workaround for meshxml / mesh.xml
-        if (animData == null) {
-            animData = (AnimData) assetManager.loadAsset(folderName + name + "xml");
-        }
     }
 
     private void startSubmeshName(String indexStr, String nameStr) {
@@ -620,7 +595,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         } else if (qName.equals("boneassignments")) {
             startBoneAssigns();
         } else if (qName.equals("submesh")) {
-            startMesh(attribs.getValue("material"),
+            startSubMesh(attribs.getValue("material"),
                     attribs.getValue("usesharedvertices"),
                     attribs.getValue("use32bitindexes"),
                     attribs.getValue("operationtype"));
@@ -653,18 +628,18 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
             if (ignoreUntilEnd.equals(qName)) {
                 ignoreUntilEnd = null;
             }
-
             return;
         }
 
         if (qName.equals("submesh")) {
-            bigindices = false;
+            usesBigIndices = false;
             geom = null;
             mesh = null;
         } else if (qName.equals("submeshes")) {
-            // IMPORTANT: restore sharedgeoemtry, for use with shared boneweights
-            geom = sharedgeom;
-            mesh = sharedmesh;
+            // IMPORTANT: restore sharedmesh, for use with shared boneweights
+            geom = null;
+            mesh = sharedMesh;
+            usesSharedVerts = false;
         } else if (qName.equals("faces")) {
             if (ib != null) {
                 ib.flip();
@@ -711,80 +686,82 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     }
 
     private Node compileModel() {
-        String nodeName;
-        if (meshName == null) {
-            nodeName = "OgreMesh" + (++nodeIdx);
-        } else {
-            nodeName = meshName + "-ogremesh";
+        Node model = new Node(meshName + "-ogremesh");
+        
+        for (int i = 0; i < geoms.size(); i++) {
+            Geometry g = geoms.get(i);
+            Mesh m = g.getMesh();
+            if (sharedMesh != null && isUsingSharedVerts(geom)) {
+                m.setBound(sharedMesh.getBound().clone());
+            }
+            model.attachChild(geoms.get(i));
         }
-
-        Node model = new Node(nodeName);
+        
+        // Do not attach shared geometry to the node!
+        
         if (animData != null) {
-            ArrayList<Mesh> newMeshes = new ArrayList<Mesh>(geoms.size());
-
-            // generate bind pose for mesh and add to skin-list
+            // This model uses animation
+            
+            // generate bind pose for mesh
             // ONLY if not using shared geometry
             // This includes the shared geoemtry itself actually
+            if (sharedMesh != null){
+                sharedMesh.generateBindPose(!HARDWARE_SKINNING);
+            }
+            
             for (int i = 0; i < geoms.size(); i++) {
                 Geometry g = geoms.get(i);
                 Mesh m = geoms.get(i).getMesh();
-                boolean useShared = usesSharedGeom.get(i);
-                // create bind pose
+                boolean useShared = isUsingSharedVerts(g); 
+                
+                
                 if (!useShared) {
+                    // create bind pose
                     m.generateBindPose(!HARDWARE_SKINNING);
-                    newMeshes.add(m);
-                } else {
-                    VertexBuffer bindPos = sharedmesh.getBuffer(Type.BindPosePosition);
-                    VertexBuffer bindNorm = sharedmesh.getBuffer(Type.BindPoseNormal);
-                    VertexBuffer boneIndex = sharedmesh.getBuffer(Type.BoneIndex);
-                    VertexBuffer boneWeight = sharedmesh.getBuffer(Type.BoneWeight);
-
-                    if (bindPos != null) {
-                        m.setBuffer(bindPos);
-                    }
-
-                    if (bindNorm != null) {
-                        m.setBuffer(bindNorm);
-                    }
-
-                    if (boneIndex != null) {
-                        m.setBuffer(boneIndex);
-                    }
-
-                    if (boneWeight != null) {
-                        m.setBuffer(boneWeight);
-                    }
+//                } else {
+                    // Inherit animation data from shared mesh
+//                    VertexBuffer bindPos = sharedMesh.getBuffer(Type.BindPosePosition);
+//                    VertexBuffer bindNorm = sharedMesh.getBuffer(Type.BindPoseNormal);
+//                    VertexBuffer boneIndex = sharedMesh.getBuffer(Type.BoneIndex);
+//                    VertexBuffer boneWeight = sharedMesh.getBuffer(Type.BoneWeight);
+//
+//                    if (bindPos != null) {
+//                        m.setBuffer(bindPos);
+//                    }
+//
+//                    if (bindNorm != null) {
+//                        m.setBuffer(bindNorm);
+//                    }
+//
+//                    if (boneIndex != null) {
+//                        m.setBuffer(boneIndex);
+//                    }
+//
+//                    if (boneWeight != null) {
+//                        m.setBuffer(boneWeight);
+//                    }
                 }
             }
-            Mesh[] meshes = new Mesh[newMeshes.size()];
-            for (int i = 0; i < meshes.length; i++) {
-                meshes[i] = newMeshes.get(i);
-            }
-
+            
+            // Put the animations in the AnimControl
             HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
             ArrayList<BoneAnimation> animList = animData.anims;
             for (int i = 0; i < animList.size(); i++) {
                 BoneAnimation anim = animList.get(i);
                 anims.put(anim.getName(), anim);
             }
-
-            //AnimControl ctrl = new AnimControl(model, meshes, animData.skeleton);            
+           
             AnimControl ctrl = new AnimControl(animData.skeleton);
             ctrl.setAnimations(anims);
             model.addControl(ctrl);
-            SkeletonControl skeletonControl = new SkeletonControl(meshes, animData.skeleton);
+            
+            // Put the skeleton in the skeleton control
+            SkeletonControl skeletonControl = new SkeletonControl(animData.skeleton);
+            
+            // This will acquire the targets from the node
             model.addControl(skeletonControl);
         }
 
-        for (int i = 0; i < geoms.size(); i++) {
-            Geometry g = geoms.get(i);
-            Mesh m = g.getMesh();
-            if (sharedmesh != null && usesSharedGeom.get(i)) {
-                m.setBound(sharedmesh.getBound().clone());
-            }
-            model.attachChild(geoms.get(i));
-        }
-
         return model;
     }
 

+ 1 - 1
engine/src/test/jme3test/bullet/TestHoveringTank.java

@@ -270,7 +270,7 @@ public class TestHoveringTank extends SimpleApplication implements AnalogListene
         rock.setWrap(WrapMode.Repeat);
         matRock.setTexture("DiffuseMap_2", rock);
         matRock.setFloat("DiffuseMap_2_scale", 128);
-        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.png");
+        Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
         normalMap0.setWrap(WrapMode.Repeat);
         Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png");
         normalMap1.setWrap(WrapMode.Repeat);

+ 6 - 2
engine/src/xml/com/jme3/export/xml/DOMInputCapsule.java

@@ -34,7 +34,7 @@ package com.jme3.export.xml;
 
 import com.jme3.export.InputCapsule;
 import com.jme3.export.Savable;
-import com.jme3.export.SavableClassFinder;
+import com.jme3.export.SavableClassUtil;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.IntMap;
 import java.io.IOException;
@@ -78,6 +78,10 @@ public class DOMInputCapsule implements InputCapsule {
         currentElem = doc.getDocumentElement();
     }
 
+    public int getSavableVersion(Class<? extends Savable> clazz) {
+        return 0; // TODO: figure this out ...
+    }
+    
     private static String decodeString(String s) {
         if (s == null) {
             return null;
@@ -973,7 +977,7 @@ public class DOMInputCapsule implements InputCapsule {
             } else if (currentElem.hasAttribute("class")) {
                 className = currentElem.getAttribute("class");
             }
-            tmp = SavableClassFinder.fromName(className, null);
+            tmp = SavableClassUtil.fromName(className, null);
             String refID = currentElem.getAttribute("reference_ID");
             if (refID.length() < 1) refID = currentElem.getAttribute("id");
             if (refID.length() > 0) referencedSavables.put(refID, tmp);

+ 6 - 5
engine/src/xml/com/jme3/export/xml/XMLImporter.java

@@ -52,11 +52,15 @@ public class XMLImporter implements JmeImporter {
 
     private AssetManager assetManager;
     private DOMInputCapsule domIn;
-    // A single-load-state base URI for texture-loading.
     
     public XMLImporter() {
     }
 
+// TODO: .......
+    public int getFormatVersion() {
+        return 0;
+    }
+    
     public AssetManager getAssetManager(){
         return assetManager;
     }
@@ -73,10 +77,7 @@ public class XMLImporter implements JmeImporter {
         return obj;
     }
 
-    synchronized public Savable load(InputStream f) throws IOException {
-        /* Leave this method synchronized.  Calling this method from more than
-         * one thread at a time for the same XMLImporter instance will clobber
-         * the XML Document instantiated here. */
+    public Savable load(InputStream f) throws IOException {
         try {
             domIn = new DOMInputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f), this);
             return domIn.readSavable(null, null);