Procházet zdrojové kódy

Feature: added support for loading assets linked from external blender
files.

jmekaelthas před 10 roky
rodič
revize
eb767e7580
18 změnil soubory, kde provedl 780 přidání a 662 odebrání
  1. 8 304
      jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
  2. 134 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
  3. 68 23
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
  4. 169 94
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java
  5. 1 70
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java
  6. 2 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java
  7. 8 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java
  8. 46 41
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java
  9. 2 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java
  10. 4 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java
  11. 23 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  12. 6 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  13. 6 7
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  14. 38 32
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
  15. 59 31
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  16. 10 8
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java
  17. 3 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java
  18. 193 28
      jme3-core/src/main/java/com/jme3/scene/UserData.java

+ 8 - 304
jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java

@@ -32,36 +32,19 @@
 package com.jme3.asset;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Queue;
 
-import com.jme3.animation.Animation;
-import com.jme3.bounding.BoundingVolume;
-import com.jme3.collision.Collidable;
-import com.jme3.collision.CollisionResults;
-import com.jme3.collision.UnsupportedCollisionException;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
 import com.jme3.material.Material;
 import com.jme3.material.RenderState.FaceCullMode;
-import com.jme3.math.ColorRGBA;
-import com.jme3.post.Filter;
-import com.jme3.scene.CameraNode;
-import com.jme3.scene.LightNode;
-import com.jme3.scene.Node;
-import com.jme3.scene.SceneGraphVisitor;
-import com.jme3.scene.Spatial;
-import com.jme3.texture.Texture;
 
 /**
  * Blender key. Contains path of the blender file and its loading properties.
  * @author Marcin Roguski (Kaelthas)
  */
 public class BlenderKey extends ModelKey {
-
     protected static final int         DEFAULT_FPS               = 25;
     /**
      * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
@@ -72,7 +55,7 @@ public class BlenderKey extends ModelKey {
      * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
      */
     protected int                      featuresToLoad            = FeaturesToLoad.ALL;
-    /** This variable determines if assets that are not linked to the objects should be loaded. */
+    /** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */
     protected boolean                  loadUnlinkedAssets;
     /** The root path for all the assets. */
     protected String                   assetRootPath;
@@ -268,6 +251,7 @@ public class BlenderKey extends ModelKey {
      * @param featuresToLoad
      *            bitwise flag of FeaturesToLoad interface values
      */
+    @Deprecated
     public void includeInLoading(int featuresToLoad) {
         this.featuresToLoad |= featuresToLoad;
     }
@@ -277,10 +261,12 @@ public class BlenderKey extends ModelKey {
      * @param featuresNotToLoad
      *            bitwise flag of FeaturesToLoad interface values
      */
+    @Deprecated
     public void excludeFromLoading(int featuresNotToLoad) {
         featuresToLoad &= ~featuresNotToLoad;
     }
 
+    @Deprecated
     public boolean shouldLoad(int featureToLoad) {
         return (featuresToLoad & featureToLoad) != 0;
     }
@@ -290,6 +276,7 @@ public class BlenderKey extends ModelKey {
      * the blender file loader.
      * @return features that will be loaded by the blender file loader
      */
+    @Deprecated
     public int getFeaturesToLoad() {
         return featuresToLoad;
     }
@@ -317,15 +304,6 @@ public class BlenderKey extends ModelKey {
         this.loadUnlinkedAssets = loadUnlinkedAssets;
     }
 
-    /**
-     * This method creates an object where loading results will be stores. Only those features will be allowed to store
-     * that were specified by features-to-load flag.
-     * @return an object to store loading results
-     */
-    public LoadingResults prepareLoadingResults() {
-        return new LoadingResults(featuresToLoad);
-    }
-
     /**
      * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y
      * is up axis.
@@ -699,8 +677,11 @@ public class BlenderKey extends ModelKey {
 
     /**
      * This interface describes the features of the scene that are to be loaded.
+     * @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency
+     *             everything must be loaded because in blender one feature might depend on another
      * @author Marcin Roguski (Kaelthas)
      */
+    @Deprecated
     public static interface FeaturesToLoad {
 
         int SCENES     = 0x0000FFFF;
@@ -745,281 +726,4 @@ public class BlenderKey extends ModelKey {
          */
         ALL_NAMES_MATCH;
     }
-
-    /**
-     * This class holds the loading results according to the given loading flag.
-     * @author Marcin Roguski (Kaelthas)
-     */
-    public static class LoadingResults extends Spatial {
-
-        /** Bitwise mask of features that are to be loaded. */
-        private final int        featuresToLoad;
-        /** The scenes from the file. */
-        private List<Node>       scenes;
-        /** Objects from all scenes. */
-        private List<Node>       objects;
-        /** Materials from all objects. */
-        private List<Material>   materials;
-        /** Textures from all objects. */
-        private List<Texture>    textures;
-        /** Animations of all objects. */
-        private List<Animation>  animations;
-        /** All cameras from the file. */
-        private List<CameraNode> cameras;
-        /** All lights from the file. */
-        private List<LightNode>  lights;
-        /** Loaded sky. */
-        private Spatial          sky;
-        /** Scene filters (ie. FOG). */
-        private List<Filter>     filters;
-        /**
-         * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
-         * is set to default (as in blender editor.
-         */
-        private ColorRGBA        backgroundColor = ColorRGBA.Gray;
-
-        /**
-         * Private constructor prevents users to create an instance of this class from outside the
-         * @param featuresToLoad
-         *            bitwise mask of features that are to be loaded
-         * @see FeaturesToLoad FeaturesToLoad
-         */
-        private LoadingResults(int featuresToLoad) {
-            this.featuresToLoad = featuresToLoad;
-            if ((featuresToLoad & FeaturesToLoad.SCENES) != 0) {
-                scenes = new ArrayList<Node>();
-            }
-            if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) {
-                objects = new ArrayList<Node>();
-                if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) {
-                    materials = new ArrayList<Material>();
-                    if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) {
-                        textures = new ArrayList<Texture>();
-                    }
-                }
-                if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) {
-                    animations = new ArrayList<Animation>();
-                }
-            }
-            if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) {
-                cameras = new ArrayList<CameraNode>();
-            }
-            if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) {
-                lights = new ArrayList<LightNode>();
-            }
-        }
-
-        /**
-         * This method returns a bitwise flag describing what features of the blend file will be included in the result.
-         * @return bitwise mask of features that are to be loaded
-         * @see FeaturesToLoad FeaturesToLoad
-         */
-        public int getLoadedFeatures() {
-            return featuresToLoad;
-        }
-
-        /**
-         * This method adds a scene to the result set.
-         * @param scene
-         *            scene to be added to the result set
-         */
-        public void addScene(Node scene) {
-            if (scenes != null) {
-                scenes.add(scene);
-            }
-        }
-
-        /**
-         * This method adds an object to the result set.
-         * @param object
-         *            object to be added to the result set
-         */
-        public void addObject(Node object) {
-            if (objects != null) {
-                objects.add(object);
-            }
-        }
-
-        /**
-         * This method adds a material to the result set.
-         * @param material
-         *            material to be added to the result set
-         */
-        public void addMaterial(Material material) {
-            if (materials != null) {
-                materials.add(material);
-            }
-        }
-
-        /**
-         * This method adds a texture to the result set.
-         * @param texture
-         *            texture to be added to the result set
-         */
-        public void addTexture(Texture texture) {
-            if (textures != null) {
-                textures.add(texture);
-            }
-        }
-
-        /**
-         * This method adds a camera to the result set.
-         * @param camera
-         *            camera to be added to the result set
-         */
-        public void addCamera(CameraNode camera) {
-            if (cameras != null) {
-                cameras.add(camera);
-            }
-        }
-
-        /**
-         * This method adds a light to the result set.
-         * @param light
-         *            light to be added to the result set
-         */
-        public void addLight(LightNode light) {
-            if (lights != null) {
-                lights.add(light);
-            }
-        }
-
-        /**
-         * This method sets the sky of the scene. Only one sky can be set.
-         * @param sky
-         *            the sky to be set
-         */
-        public void setSky(Spatial sky) {
-            this.sky = sky;
-        }
-
-        /**
-         * This method adds a scene filter. Filters are used to load FOG or other
-         * scene effects that blender can define.
-         * @param filter
-         *            the filter to be added
-         */
-        public void addFilter(Filter filter) {
-            if (filter != null) {
-                if (filters == null) {
-                    filters = new ArrayList<Filter>(5);
-                }
-                filters.add(filter);
-            }
-        }
-
-        /**
-         * @param backgroundColor
-         *            the background color
-         */
-        public void setBackgroundColor(ColorRGBA backgroundColor) {
-            this.backgroundColor = backgroundColor;
-        }
-
-        /**
-         * @return all loaded scenes
-         */
-        public List<Node> getScenes() {
-            return scenes;
-        }
-
-        /**
-         * @return all loaded objects
-         */
-        public List<Node> getObjects() {
-            return objects;
-        }
-
-        /**
-         * @return all loaded materials
-         */
-        public List<Material> getMaterials() {
-            return materials;
-        }
-
-        /**
-         * @return all loaded textures
-         */
-        public List<Texture> getTextures() {
-            return textures;
-        }
-
-        /**
-         * @return all loaded animations
-         */
-        public List<Animation> getAnimations() {
-            return animations;
-        }
-
-        /**
-         * @return all loaded cameras
-         */
-        public List<CameraNode> getCameras() {
-            return cameras;
-        }
-
-        /**
-         * @return all loaded lights
-         */
-        public List<LightNode> getLights() {
-            return lights;
-        }
-
-        /**
-         * @return the scene's sky
-         */
-        public Spatial getSky() {
-            return sky;
-        }
-
-        /**
-         * @return scene filters
-         */
-        public List<Filter> getFilters() {
-            return filters;
-        }
-
-        /**
-         * @return the background color
-         */
-        public ColorRGBA getBackgroundColor() {
-            return backgroundColor;
-        }
-
-        @Override
-        public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
-            return 0;
-        }
-
-        @Override
-        public void updateModelBound() {
-        }
-
-        @Override
-        public void setModelBound(BoundingVolume modelBound) {
-        }
-
-        @Override
-        public int getVertexCount() {
-            return 0;
-        }
-
-        @Override
-        public int getTriangleCount() {
-            return 0;
-        }
-
-        @Override
-        public Spatial deepClone() {
-            return null;
-        }
-
-        @Override
-        public void depthFirstTraversal(SceneGraphVisitor visitor) {
-        }
-
-        @Override
-        protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
-        }
-    }
 }

+ 134 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java

@@ -31,17 +31,34 @@
  */
 package com.jme3.scene.plugins.blender;
 
+import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
+import com.jme3.animation.Animation;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.BlenderKey;
 import com.jme3.export.Savable;
+import com.jme3.light.Light;
 import com.jme3.math.FastMath;
 import com.jme3.math.Quaternion;
+import com.jme3.post.Filter;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.materials.MaterialContext;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.objects.Properties;
+import com.jme3.texture.Texture;
 
 /**
  * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can
@@ -49,14 +66,16 @@ import com.jme3.scene.plugins.blender.objects.Properties;
  * @author Marcin Roguski
  */
 public abstract class AbstractBlenderHelper {
+    private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName());
+
     /** The blender context. */
-    protected BlenderContext blenderContext;
+    protected BlenderContext    blenderContext;
     /** The version of the blend file. */
-    protected final int  blenderVersion;
+    protected final int         blenderVersion;
     /** This variable indicates if the Y asxis is the UP axis or not. */
-    protected boolean    fixUpAxis;
+    protected boolean           fixUpAxis;
     /** Quaternion used to rotate data when Y is up axis. */
-    protected Quaternion upAxisRotationQuaternion;
+    protected Quaternion        upAxisRotationQuaternion;
 
     /**
      * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
@@ -129,4 +148,115 @@ public abstract class AbstractBlenderHelper {
             }
         }
     }
+
+    /**
+     * The method loads library of a given ID from linked blender file.
+     * @param id
+     *            the ID of the linked feature (it contains its name and blender path)
+     * @return loaded feature or null if none was found
+     * @throws BlenderFileException
+     *             and exception is throw when problems with reading a blend file occur
+     */
+    @SuppressWarnings("unchecked")
+    protected Object loadLibrary(Structure id) throws BlenderFileException {
+        Pointer pLib = (Pointer) id.getFieldValue("lib");
+        if (pLib.isNotNull()) {
+            String fullName = id.getFieldValue("name").toString();// we need full name with the prefix
+            String nameOfFeatureToLoad = id.getName();
+            Structure library = pLib.fetchData().get(0);
+            String path = library.getFieldValue("filepath").toString();
+
+            if (!blenderContext.getLinkedFeatures().keySet().contains(path)) {
+                File file = new File(path);
+                List<String> pathsToCheck = new ArrayList<String>();
+                String currentPath = file.getName();
+                do {
+                    pathsToCheck.add(currentPath);
+                    file = file.getParentFile();
+                    if (file != null) {
+                        currentPath = file.getName() + '/' + currentPath;
+                    }
+                } while (file != null);
+
+                Spatial loadedAsset = null;
+                BlenderKey blenderKey = null;
+                for (String p : pathsToCheck) {
+                    blenderKey = new BlenderKey(p);
+                    blenderKey.setLoadUnlinkedAssets(true);
+                    try {
+                        loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey);
+                        break;// break if no exception was thrown
+                    } catch (AssetNotFoundException e) {
+                        LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", p);
+                    }
+                }
+
+                if (loadedAsset != null) {
+                    Map<String, Map<String, Object>> linkedData = loadedAsset.getUserData("linkedData");
+                    for (Entry<String, Map<String, Object>> entry : linkedData.entrySet()) {
+                        String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey();
+
+                        List<Node> scenes = (List<Node>) entry.getValue().get("scenes");
+                        for (Node scene : scenes) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene);
+                        }
+                        List<Node> objects = (List<Node>) entry.getValue().get("objects");
+                        for (Node object : objects) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object);
+                        }
+                        List<TemporalMesh> meshes = (List<TemporalMesh>) entry.getValue().get("meshes");
+                        for (TemporalMesh mesh : meshes) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh);
+                        }
+                        List<MaterialContext> materials = (List<MaterialContext>) entry.getValue().get("materials");
+                        for (MaterialContext materialContext : materials) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "MA" + materialContext.getName(), materialContext);
+                        }
+                        List<Texture> textures = (List<Texture>) entry.getValue().get("textures");
+                        for (Texture texture : textures) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "TE" + texture.getName(), texture);
+                        }
+                        List<Texture> images = (List<Texture>) entry.getValue().get("images");
+                        for (Texture image : images) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image);
+                        }
+                        List<Animation> animations = (List<Animation>) entry.getValue().get("animations");
+                        for (Animation animation : animations) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "AC" + animation.getName(), animation);
+                        }
+                        List<Camera> cameras = (List<Camera>) entry.getValue().get("cameras");
+                        for (Camera camera : cameras) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera);
+                        }
+                        List<Light> lights = (List<Light>) entry.getValue().get("lights");
+                        for (Light light : lights) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, "LA" + light.getName(), light);
+                        }
+                        Spatial sky = (Spatial) entry.getValue().get("sky");
+                        if (sky != null) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, sky.getName(), sky);
+                        }
+                        List<Filter> filters = (List<Filter>) entry.getValue().get("filters");
+                        for (Filter filter : filters) {
+                            blenderContext.addLinkedFeature(linkedDataFilePath, filter.getName(), filter);
+                        }
+                    }
+                } else {
+                    LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path);
+                }
+            }
+
+            Object result = blenderContext.getLinkedFeature(path, fullName);
+            if (result == null) {
+                LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path });
+            } else {
+                blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id);
+                blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
+            }
+            return result;
+        } else {
+            LOGGER.warning("Library link points to nothing!");
+        }
+        return null;
+    }
 }

+ 68 - 23
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -53,6 +53,7 @@ import com.jme3.scene.plugins.blender.constraints.Constraint;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.DnaBlockData;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
@@ -64,49 +65,51 @@ import com.jme3.scene.plugins.blender.file.Structure;
  */
 public class BlenderContext {
     /** The blender file version. */
-    private int                                 blenderVersion;
+    private int                                    blenderVersion;
     /** The blender key. */
-    private BlenderKey                          blenderKey;
+    private BlenderKey                             blenderKey;
     /** The header of the file block. */
-    private DnaBlockData                        dnaBlockData;
+    private DnaBlockData                           dnaBlockData;
     /** The scene structure. */
-    private Structure                           sceneStructure;
+    private Structure                              sceneStructure;
     /** The input stream of the blend file. */
-    private BlenderInputStream                  inputStream;
+    private BlenderInputStream                     inputStream;
     /** The asset manager. */
-    private AssetManager                        assetManager;
+    private AssetManager                           assetManager;
     /** The blocks read from the file. */
-    protected List<FileBlockHeader>             blocks;
+    protected List<FileBlockHeader>                blocks;
     /**
      * A map containing the file block headers. The key is the old memory address.
      */
-    private Map<Long, FileBlockHeader>          fileBlockHeadersByOma  = new HashMap<Long, FileBlockHeader>();
+    private Map<Long, FileBlockHeader>             fileBlockHeadersByOma  = new HashMap<Long, FileBlockHeader>();
     /** A map containing the file block headers. The key is the block code. */
-    private Map<Integer, List<FileBlockHeader>> fileBlockHeadersByCode = new HashMap<Integer, List<FileBlockHeader>>();
+    private Map<BlockCode, List<FileBlockHeader>>  fileBlockHeadersByCode = new HashMap<BlockCode, List<FileBlockHeader>>();
     /**
      * This map stores the loaded features by their old memory address. The
      * first object in the value table is the loaded structure and the second -
      * the structure already converted into proper data.
      */
-    private Map<Long, Map<LoadedDataType, Object>>                 loadedFeatures         = new HashMap<Long, Map<LoadedDataType, Object>>();
+    private Map<Long, Map<LoadedDataType, Object>> loadedFeatures         = new HashMap<Long, Map<LoadedDataType, Object>>();
+    /** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */
+    private Map<String, Map<String, Object>>       linkedFeatures         = new HashMap<String, Map<String, Object>>();
     /** A stack that hold the parent structure of currently loaded feature. */
-    private Stack<Structure>                    parentStack            = new Stack<Structure>();
+    private Stack<Structure>                       parentStack            = new Stack<Structure>();
     /** A list of constraints for the specified object. */
-    protected Map<Long, List<Constraint>>       constraints            = new HashMap<Long, List<Constraint>>();
+    protected Map<Long, List<Constraint>>          constraints            = new HashMap<Long, List<Constraint>>();
     /** Animations loaded for features. */
-    private Map<Long, List<Animation>>          animations             = new HashMap<Long, List<Animation>>();
+    private Map<Long, List<Animation>>             animations             = new HashMap<Long, List<Animation>>();
     /** Loaded skeletons. */
-    private Map<Long, Skeleton>                 skeletons              = new HashMap<Long, Skeleton>();
+    private Map<Long, Skeleton>                    skeletons              = new HashMap<Long, Skeleton>();
     /** A map between skeleton and node it modifies. */
-    private Map<Skeleton, Node>                 nodesWithSkeletons     = new HashMap<Skeleton, Node>();
+    private Map<Skeleton, Node>                    nodesWithSkeletons     = new HashMap<Skeleton, Node>();
     /** A map of bone contexts. */
-    protected Map<Long, BoneContext>            boneContexts           = new HashMap<Long, BoneContext>();
+    protected Map<Long, BoneContext>               boneContexts           = new HashMap<Long, BoneContext>();
     /** A map og helpers that perform loading. */
-    private Map<String, AbstractBlenderHelper>  helpers                = new HashMap<String, AbstractBlenderHelper>();
+    private Map<String, AbstractBlenderHelper>     helpers                = new HashMap<String, AbstractBlenderHelper>();
     /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
-    private Map<String, Map<Object, Object>>    markers                = new HashMap<String, Map<Object, Object>>();
+    private Map<String, Map<Object, Object>>       markers                = new HashMap<String, Map<Object, Object>>();
     /** A map of blender actions. The key is the action name and the value is the action itself. */
-    private Map<String, BlenderAction>          actions                = new HashMap<String, BlenderAction>();
+    private Map<String, BlenderAction>             actions                = new HashMap<String, BlenderAction>();
 
     /**
      * This method sets the blender file version.
@@ -231,10 +234,10 @@ public class BlenderContext {
      */
     public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {
         fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);
-        List<FileBlockHeader> headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode()));
+        List<FileBlockHeader> headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode());
         if (headers == null) {
             headers = new ArrayList<FileBlockHeader>();
-            fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers);
+            fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers);
         }
         headers.add(fileBlockHeader);
     }
@@ -258,7 +261,7 @@ public class BlenderContext {
      *            the code of file blocks
      * @return a list of file blocks' headers of a specified code
      */
-    public List<FileBlockHeader> getFileBlocks(Integer code) {
+    public List<FileBlockHeader> getFileBlocks(BlockCode code) {
         return fileBlockHeadersByCode.get(code);
     }
 
@@ -299,7 +302,7 @@ public class BlenderContext {
             throw new IllegalArgumentException("One of the given arguments is null!");
         }
         Map<LoadedDataType, Object> map = loadedFeatures.get(oldMemoryAddress);
-        if(map == null) {
+        if (map == null) {
             map = new HashMap<BlenderContext.LoadedDataType, Object>();
             loadedFeatures.put(oldMemoryAddress, map);
         }
@@ -325,6 +328,48 @@ public class BlenderContext {
         return null;
     }
 
+    /**
+     * The method adds linked content to the blender context.
+     * @param blenderFilePath
+     *            the path of linked blender file
+     * @param featureName
+     *            the linked feature name
+     * @param feature
+     *            the linked feature
+     */
+    public void addLinkedFeature(String blenderFilePath, String featureName, Object feature) {
+        if (feature != null) {
+            Map<String, Object> linkedFeatures = this.linkedFeatures.get(blenderFilePath);
+            if (linkedFeatures == null) {
+                linkedFeatures = new HashMap<String, Object>();
+                this.linkedFeatures.put(blenderFilePath, linkedFeatures);
+            }
+            if (!linkedFeatures.containsKey(featureName)) {
+                linkedFeatures.put(featureName, feature);
+            }
+        }
+    }
+
+    /**
+     * The method returns linked feature of a given name from the specified blender path.
+     * @param blenderFilePath
+     *            the blender file path
+     * @param featureName
+     *            the feature name we want to get
+     * @return linked feature or null if none was found
+     */
+    public Object getLinkedFeature(String blenderFilePath, String featureName) {
+        Map<String, Object> linkedFeatures = this.linkedFeatures.get(blenderFilePath);
+        return linkedFeatures != null ? linkedFeatures.get(featureName) : null;
+    }
+
+    /**
+     * @return all linked features for the current blend file
+     */
+    public Map<String, Map<String, Object>> getLinkedFeatures() {
+        return linkedFeatures;
+    }
+
     /**
      * This method adds the structure to the parent stack.
      * 

+ 169 - 94
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java

@@ -33,17 +33,21 @@ package com.jme3.scene.plugins.blender;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.jme3.animation.Animation;
 import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetLoader;
 import com.jme3.asset.BlenderKey;
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
-import com.jme3.asset.BlenderKey.LoadingResults;
 import com.jme3.asset.ModelKey;
 import com.jme3.light.Light;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.Camera;
 import com.jme3.scene.CameraNode;
 import com.jme3.scene.LightNode;
 import com.jme3.scene.Node;
@@ -55,16 +59,20 @@ import com.jme3.scene.plugins.blender.curves.CurvesHelper;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
 import com.jme3.scene.plugins.blender.lights.LightHelper;
+import com.jme3.scene.plugins.blender.materials.MaterialContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.scene.plugins.blender.meshes.MeshHelper;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
 import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
 import com.jme3.scene.plugins.blender.textures.TextureHelper;
+import com.jme3.texture.Texture;
 
 /**
  * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
@@ -83,72 +91,130 @@ public class BlenderLoader implements AssetLoader {
         try {
             this.setup(assetInfo);
 
-            List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
-            BlenderKey blenderKey = blenderContext.getBlenderKey();
-            LoadingResults loadingResults = blenderKey.prepareLoadingResults();
-            
             AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
             animationHelper.loadAnimations();
-            
+
+            BlenderKey blenderKey = blenderContext.getBlenderKey();
+            LoadedFeatures loadedFeatures = new LoadedFeatures();
             for (FileBlockHeader block : blocks) {
                 switch (block.getCode()) {
-                    case FileBlockHeader.BLOCK_OB00:// Object
+                    case BLOCK_OB00:
                         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                        Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
-                        if (object instanceof LightNode) {
-                            loadingResults.addLight((LightNode) object);
-                        } else if (object instanceof CameraNode) {
-                            loadingResults.addCamera((CameraNode) object);
-                        } else if (object instanceof Node) {
-                            if (LOGGER.isLoggable(Level.FINE)) {
-                                LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
-                            }
-                            if (this.isRootObject(loadingResults, (Node) object)) {
-                                loadingResults.addObject((Node) object);
-                            }
+                        Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
+                        if (LOGGER.isLoggable(Level.FINE)) {
+                            LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() });
                         }
+                        if (object.getParent() == null) {
+                            loadedFeatures.objects.add(object);
+                        }
+                        if (object instanceof LightNode && ((LightNode) object).getLight() != null) {
+                            loadedFeatures.lights.add(((LightNode) object).getLight());
+                        } else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) {
+                            loadedFeatures.cameras.add(((CameraNode) object).getCamera());
+                        }
+                        break;
+                    case BLOCK_SC00:// Scene
+                        loadedFeatures.sceneBlocks.add(block);
                         break;
-//                    case FileBlockHeader.BLOCK_MA00:// Material
-//                        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
-//                        MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext);
-//                        if (blenderKey.isLoadUnlinkedAssets() && blenderKey.shouldLoad(FeaturesToLoad.MATERIALS)) {
-//                            loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext)));
-//                        }
-//                        break;
-                    case FileBlockHeader.BLOCK_SC00:// Scene
-                        if (blenderKey.shouldLoad(FeaturesToLoad.SCENES)) {
-                            sceneBlocks.add(block);
+                    case BLOCK_MA00:// Material
+                        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
+                        MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext);
+                        loadedFeatures.materials.add(materialContext);
+                        break;
+                    case BLOCK_ME00:// Mesh
+                        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+                        TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext);
+                        loadedFeatures.meshes.add(temporalMesh);
+                        break;
+                    case BLOCK_IM00:// Image
+                        TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
+                        Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext);
+                        if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded
+                            loadedFeatures.images.add(image);
                         }
                         break;
-                    case FileBlockHeader.BLOCK_WO00:// World
-                        if (blenderKey.shouldLoad(FeaturesToLoad.WORLD)) {
-                            Structure worldStructure = block.getStructure(blenderContext);
-                            String worldName = worldStructure.getName();
-                            if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
-                                LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
-                                Light ambientLight = landscapeHelper.toAmbientLight(worldStructure);
-                                if(ambientLight != null) {
-                                    loadingResults.addLight(new LightNode(null, ambientLight));
-                                }
-                                loadingResults.setSky(landscapeHelper.toSky(worldStructure));
-                                loadingResults.addFilter(landscapeHelper.toFog(worldStructure));
-                                loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure));
+                    case BLOCK_TE00:
+                        Structure textureStructure = block.getStructure(blenderContext);
+                        int type = ((Number) textureStructure.getFieldValue("type")).intValue();
+                        if (type == TextureHelper.TEX_IMAGE) {
+                            TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class);
+                            Texture texture = texHelper.getTexture(textureStructure, null, blenderContext);
+                            if (texture != null) {// null is returned when texture has no image
+                                loadedFeatures.textures.add(texture);
                             }
+                        } else {
+                            LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object.");
                         }
                         break;
+                    case BLOCK_WO00:// World
+                        LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
+                        Structure worldStructure = block.getStructure(blenderContext);
+
+                        String worldName = worldStructure.getName();
+                        if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
+
+                            Light ambientLight = landscapeHelper.toAmbientLight(worldStructure);
+                            if (ambientLight != null) {
+                                loadedFeatures.objects.add(new LightNode(null, ambientLight));
+                                loadedFeatures.lights.add(ambientLight);
+                            }
+                            loadedFeatures.sky = landscapeHelper.toSky(worldStructure);
+                            loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure);
+
+                            Filter fogFilter = landscapeHelper.toFog(worldStructure);
+                            if (fogFilter != null) {
+                                loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure));
+                            }
+                        }
+                        break;
+                    case BLOCK_AC00:
+                        LOGGER.fine("Loading unlinked animations is not yet supported!");
+                        break;
+                    default:
+                        LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode());
                 }
             }
 
-            // bake constraints after everything is loaded
+            LOGGER.fine("Baking constraints after every feature is loaded.");
             ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
             constraintHelper.bakeConstraints(blenderContext);
 
-            // load the scene at the very end so that the root nodes have no parent during loading or constraints applying
-            for (FileBlockHeader sceneBlock : sceneBlocks) {
-                loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext)));
+            LOGGER.fine("Loading scenes and attaching them to the root object.");
+            for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) {
+                loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext)));
+            }
+
+            LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it.");
+            Node modelRoot = new Node(blenderKey.getName());
+            for (Node scene : loadedFeatures.scenes) {
+                modelRoot.attachChild(scene);
             }
 
-            return loadingResults;
+            if (blenderKey.isLoadUnlinkedAssets()) {
+                LOGGER.fine("Setting loaded content as user data in resulting sptaial.");
+                Map<String, Map<String, Object>> linkedData = new HashMap<String, Map<String, Object>>();
+
+                Map<String, Object> thisFileData = new HashMap<String, Object>();
+                thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList<Object>() : loadedFeatures.scenes);
+                thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList<Object>() : loadedFeatures.objects);
+                thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList<Object>() : loadedFeatures.meshes);
+                thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList<Object>() : loadedFeatures.materials);
+                thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList<Object>() : loadedFeatures.textures);
+                thisFileData.put("images", loadedFeatures.images == null ? new ArrayList<Object>() : loadedFeatures.images);
+                thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList<Object>() : loadedFeatures.animations);
+                thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList<Object>() : loadedFeatures.cameras);
+                thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList<Object>() : loadedFeatures.lights);
+                thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList<Object>() : loadedFeatures.filters);
+                thisFileData.put("backgroundColor", loadedFeatures.backgroundColor);
+                thisFileData.put("sky", loadedFeatures.sky);
+
+                linkedData.put("this", thisFileData);
+                linkedData.putAll(blenderContext.getLinkedFeatures());
+
+                modelRoot.setUserData("linkedData", linkedData);
+            }
+
+            return modelRoot;
         } catch (BlenderFileException e) {
             throw new IOException(e.getLocalizedMessage(), e);
         } catch (Exception e) {
@@ -158,62 +224,36 @@ public class BlenderLoader implements AssetLoader {
         }
     }
 
-    /**
-     * This method indicates if the given spatial is a root object. It means it
-     * has no parent or is directly attached to one of the already loaded scene
-     * nodes.
-     * 
-     * @param loadingResults
-     *            loading results containing the scene nodes
-     * @param spatial
-     *            spatial object
-     * @return <b>true</b> if the given spatial is a root object and
-     *         <b>false</b> otherwise
-     */
-    protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) {
-        if (spatial.getParent() == null) {
-            return true;
-        }
-        for (Node scene : loadingResults.getScenes()) {
-            if (spatial.getParent().equals(scene)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * This method converts the given structure to a scene node.
      * @param structure
      *            structure of a scene
      * @return scene's node
+     * @throws BlenderFileException
+     *             an exception throw when problems with blender file occur
      */
-    private Node toScene(Structure structure) {
+    private Node toScene(Structure structure) throws BlenderFileException {
         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
         Node result = new Node(structure.getName());
-        try {
-            List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase();
-            for (Structure b : base) {
-                Pointer pObject = (Pointer) b.getFieldValue("object");
-                if (pObject.isNotNull()) {
-                    Structure objectStructure = pObject.fetchData().get(0);
+        List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase();
+        for (Structure b : base) {
+            Pointer pObject = (Pointer) b.getFieldValue("object");
+            if (pObject.isNotNull()) {
+                Structure objectStructure = pObject.fetchData().get(0);
 
-                    Object object = objectHelper.toObject(objectStructure, blenderContext);
-                    if (object instanceof LightNode) {
-                        result.addLight(((LightNode) object).getLight());
-                        result.attachChild((LightNode) object);
-                    } else if (object instanceof Node) {
-                        if (LOGGER.isLoggable(Level.FINE)) {
-                            LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
-                        }
-                        if (((Node) object).getParent() == null) {
-                            result.attachChild((Spatial) object);
-                        }
+                Object object = objectHelper.toObject(objectStructure, blenderContext);
+                if (object instanceof LightNode) {
+                    result.addLight(((LightNode) object).getLight());// FIXME: check if this is needed !!!
+                    result.attachChild((LightNode) object);
+                } else if (object instanceof Node) {
+                    if (LOGGER.isLoggable(Level.FINE)) {
+                        LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
+                    }
+                    if (((Node) object).getParent() == null) {
+                        result.attachChild((Spatial) object);
                     }
                 }
             }
-        } catch (BlenderFileException e) {
-            LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
         }
         return result;
     }
@@ -261,7 +301,7 @@ public class BlenderLoader implements AssetLoader {
         blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
         blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
         blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
-        
+
         // reading the blocks (dna block is automatically saved in the blender context when found)
         FileBlockHeader sceneFileBlock = null;
         do {
@@ -269,7 +309,7 @@ public class BlenderLoader implements AssetLoader {
             if (!fileBlock.isDnaBlock()) {
                 blocks.add(fileBlock);
                 // save the scene's file block
-                if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) {
+                if (fileBlock.getCode() == BlockCode.BLOCK_SC00) {
                     sceneFileBlock = fileBlock;
                 }
             }
@@ -287,4 +327,39 @@ public class BlenderLoader implements AssetLoader {
         blenderContext = null;
         blocks = null;
     }
+
+    /**
+     * This class holds the loading results according to the given loading flag.
+     * @author Marcin Roguski (Kaelthas)
+     */
+    private static class LoadedFeatures {
+        private List<FileBlockHeader> sceneBlocks     = new ArrayList<FileBlockHeader>();
+        /** The scenes from the file. */
+        private List<Node>            scenes          = new ArrayList<Node>();
+        /** Objects from all scenes. */
+        private List<Node>            objects         = new ArrayList<Node>();
+        /** All meshes. */
+        private List<TemporalMesh>    meshes          = new ArrayList<TemporalMesh>();
+        /** Materials from all objects. */
+        private List<MaterialContext> materials       = new ArrayList<MaterialContext>();
+        /** Textures from all objects. */
+        private List<Texture>         textures        = new ArrayList<Texture>();
+        /** The images stored in the blender file. */
+        private List<Texture>         images          = new ArrayList<Texture>();
+        /** Animations of all objects. */
+        private List<Animation>       animations      = new ArrayList<Animation>();
+        /** All cameras from the file. */
+        private List<Camera>          cameras         = new ArrayList<Camera>();
+        /** All lights from the file. */
+        private List<Light>           lights          = new ArrayList<Light>();
+        /** Loaded sky. */
+        private Spatial               sky;
+        /** Scene filters (ie. FOG). */
+        private List<Filter>          filters         = new ArrayList<Filter>();
+        /**
+         * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
+         * is set to default (as in blender editor.
+         */
+        private ColorRGBA             backgroundColor = ColorRGBA.Gray;
+    }
 }

+ 1 - 70
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java

@@ -31,79 +31,10 @@
  */
 package com.jme3.scene.plugins.blender;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.jme3.asset.AssetInfo;
-import com.jme3.asset.BlenderKey;
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
-import com.jme3.scene.LightNode;
-import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.plugins.blender.animations.AnimationHelper;
-import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
-import com.jme3.scene.plugins.blender.file.BlenderFileException;
-import com.jme3.scene.plugins.blender.file.FileBlockHeader;
-import com.jme3.scene.plugins.blender.objects.ObjectHelper;
-
 /**
  * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
- * 
+ * @deprecated this class is deprecated; use BlenderLoader instead
  * @author Marcin Roguski (Kaelthas)
  */
 public class BlenderModelLoader extends BlenderLoader {
-
-    private static final Logger LOGGER = Logger.getLogger(BlenderModelLoader.class.getName());
-
-    @Override
-    public Spatial load(AssetInfo assetInfo) throws IOException {
-        try {
-            this.setup(assetInfo);
-
-            AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
-            animationHelper.loadAnimations();
-            
-            BlenderKey blenderKey = blenderContext.getBlenderKey();
-            List<Node> rootObjects = new ArrayList<Node>();
-            for (FileBlockHeader block : blocks) {
-                if (block.getCode() == FileBlockHeader.BLOCK_OB00) {
-                    ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                    Object object = objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
-                    if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
-                        rootObjects.add((LightNode) object);
-                    } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) {
-                        LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
-                        if (((Node) object).getParent() == null) {
-                            rootObjects.add((Node) object);
-                        }
-                    }
-                }
-            }
-
-            // bake constraints after everything is loaded
-            ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-            constraintHelper.bakeConstraints(blenderContext);
-
-            // attach the nodes to the root node at the very end so that the root objects have no parents during constraint applying
-            LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene to it.");
-            Node modelRoot = new Node(blenderKey.getName());
-            for (Node node : rootObjects) {
-                if (node instanceof LightNode) {
-                    modelRoot.addLight(((LightNode) node).getLight());
-                }
-                modelRoot.attachChild(node);
-            }
-
-            return modelRoot;
-        } catch (BlenderFileException e) {
-            throw new IOException(e.getLocalizedMessage(), e);
-        } catch (Exception e) {
-            throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e);
-        } finally {
-            this.clear();
-        }
-    }
 }

+ 2 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java

@@ -25,6 +25,7 @@ import com.jme3.scene.plugins.blender.curves.BezierCurve;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
+import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
@@ -48,7 +49,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
      */
     public void loadAnimations() throws BlenderFileException {
         LOGGER.info("Loading animations that will be later applied to scene features.");
-        List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
+        List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00);
         if (actionHeaders != null) {
             for (FileBlockHeader header : actionHeaders) {
                 Structure actionStructure = header.getStructure(blenderContext);

+ 8 - 7
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java

@@ -5,7 +5,6 @@ import java.util.logging.Logger;
 
 import com.jme3.math.FastMath;
 import com.jme3.renderer.Camera;
-import com.jme3.scene.CameraNode;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
@@ -43,7 +42,7 @@ public class CameraHelper extends AbstractBlenderHelper {
      *             an exception is thrown when there are problems with the
      *             blender file
      */
-    public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
+    public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
         if (blenderVersion >= 250) {
             return this.toCamera250(structure, blenderContext.getSceneStructure());
         } else {
@@ -63,7 +62,7 @@ public class CameraHelper extends AbstractBlenderHelper {
      *             an exception is thrown when there are problems with the
      *             blender file
      */
-    private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
+    private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
         int width = DEFAULT_CAM_WIDTH;
         int height = DEFAULT_CAM_HEIGHT;
         if (sceneStructure != null) {
@@ -99,7 +98,7 @@ public class CameraHelper extends AbstractBlenderHelper {
                 sensor = ((Number) structure.getFieldValue(sensorName)).floatValue();
             }
             float focalLength = ((Number) structure.getFieldValue("lens")).floatValue();
-            float fov = 2.0f * FastMath.atan((sensor / 2.0f) / focalLength);
+            float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength);
             if (sensorVertical) {
                 fovY = fov * FastMath.RAD_TO_DEG;
             } else {
@@ -111,7 +110,8 @@ public class CameraHelper extends AbstractBlenderHelper {
             fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
         }
         camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
-        return new CameraNode(null, camera);
+        camera.setName(structure.getName());
+        return camera;
     }
 
     /**
@@ -124,7 +124,7 @@ public class CameraHelper extends AbstractBlenderHelper {
      *             an exception is thrown when there are problems with the
      *             blender file
      */
-    private CameraNode toCamera249(Structure structure) throws BlenderFileException {
+    private Camera toCamera249(Structure structure) throws BlenderFileException {
         Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
         int type = ((Number) structure.getFieldValue("type")).intValue();
         if (type != 0 && type != 1) {
@@ -142,6 +142,7 @@ public class CameraHelper extends AbstractBlenderHelper {
             aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
         }
         camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend);
-        return new CameraNode(null, camera);
+        camera.setName(structure.getName());
+        return camera;
     }
 }

+ 46 - 41
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java

@@ -31,6 +31,8 @@
  */
 package com.jme3.scene.plugins.blender.file;
 
+import java.util.logging.Logger;
+
 import com.jme3.scene.plugins.blender.BlenderContext;
 
 /**
@@ -39,39 +41,23 @@ import com.jme3.scene.plugins.blender.BlenderContext;
  * @author Marcin Roguski
  */
 public class FileBlockHeader {
+    private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName());
 
-    public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16;                 // TE00
-    public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16;                 // ME00
-    public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16;                 // SR00
-    public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16;                 // CA00
-    public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16;                 // LA00
-    public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16;                 // OB00
-    public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16;                 // MA00
-    public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16;                 // SC00
-    public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16;                 // WO00
-    public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16;                 // TX00
-    public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16;                 // IP00
-    public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16;                 // AC00
-    public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB
-    public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND
-    public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA
-    public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1
-    public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB
     /** Identifier of the file-block [4 bytes]. */
-    private int             code;
+    private BlockCode           code;
     /** Total length of the data after the file-block-header [4 bytes]. */
-    private int             size;
+    private int                 size;
     /**
      * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
      * size)].
      */
-    private long            oldMemoryAddress;
+    private long                oldMemoryAddress;
     /** Index of the SDNA structure [4 bytes]. */
-    private int             sdnaIndex;
+    private int                 sdnaIndex;
     /** Number of structure located in this file-block [4 bytes]. */
-    private int             count;
+    private int                 count;
     /** Start position of the block's data in the stream. */
-    private int             blockPosition;
+    private int                 blockPosition;
 
     /**
      * Constructor. Loads the block header from the given stream during instance creation.
@@ -84,13 +70,13 @@ public class FileBlockHeader {
      */
     public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
         inputStream.alignPosition(4);
-        code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
+        code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte());
         size = inputStream.readInt();
         oldMemoryAddress = inputStream.readPointer();
         sdnaIndex = inputStream.readInt();
         count = inputStream.readInt();
         blockPosition = inputStream.getPosition();
-        if (FileBlockHeader.BLOCK_DNA1 == code) {
+        if (BlockCode.BLOCK_DNA1 == code) {
             blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext));
         } else {
             inputStream.setPosition(blockPosition + size);
@@ -116,7 +102,7 @@ public class FileBlockHeader {
      * This method returns the code of this data block.
      * @return the code of this data block
      */
-    public int getCode() {
+    public BlockCode getCode() {
         return code;
     }
 
@@ -157,7 +143,7 @@ public class FileBlockHeader {
      * @return true if this block is the last one in the file nad false otherwise
      */
     public boolean isLastBlock() {
-        return FileBlockHeader.BLOCK_ENDB == code;
+        return BlockCode.BLOCK_ENDB == code;
     }
 
     /**
@@ -165,25 +151,44 @@ public class FileBlockHeader {
      * @return true if this block is the SDNA block and false otherwise
      */
     public boolean isDnaBlock() {
-        return FileBlockHeader.BLOCK_DNA1 == code;
+        return BlockCode.BLOCK_DNA1 == code;
     }
 
     @Override
     public String toString() {
-        return "FILE BLOCK HEADER [" + this.codeToString(code) + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
+        return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
     }
 
-    /**
-     * This method transforms the coded bloch id into a string value.
-     * @param code
-     *            the id of the block
-     * @return the string value of the block id
-     */
-    protected String codeToString(int code) {
-        char c1 = (char) ((code & 0xFF000000) >> 24);
-        char c2 = (char) ((code & 0xFF0000) >> 16);
-        char c3 = (char) ((code & 0xFF00) >> 8);
-        char c4 = (char) (code & 0xFF);
-        return String.valueOf(c1) + c2 + c3 + c4;
+    public static enum BlockCode {
+        BLOCK_ME00('M' << 24 | 'E' << 16), // mesh
+        BLOCK_CA00('C' << 24 | 'A' << 16), // camera
+        BLOCK_LA00('L' << 24 | 'A' << 16), // lamp
+        BLOCK_OB00('O' << 24 | 'B' << 16), // object
+        BLOCK_MA00('M' << 24 | 'A' << 16), // material
+        BLOCK_SC00('S' << 24 | 'C' << 16), // scene
+        BLOCK_WO00('W' << 24 | 'O' << 16), // world
+        BLOCK_TX00('T' << 24 | 'X' << 16), // texture
+        BLOCK_IP00('I' << 24 | 'P' << 16), // ipo
+        BLOCK_AC00('A' << 24 | 'C' << 16), // action
+        BLOCK_IM00('I' << 24 | 'M' << 16), // image
+        BLOCK_TE00('T' << 24 | 'E' << 16), BLOCK_WM00('W' << 24 | 'M' << 16), BLOCK_SR00('S' << 24 | 'R' << 16), BLOCK_SN00('S' << 24 | 'N' << 16), BLOCK_BR00('B' << 24 | 'R' << 16), BLOCK_LS00('L' << 24 | 'S' << 16), BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), BLOCK_TEST('T' << 24 | 'E' << 16
+                | 'S' << 8 | 'T'), BLOCK_UNKN(0);
+
+        private int code;
+
+        private BlockCode(int code) {
+            this.code = code;
+        }
+
+        public static BlockCode valueOf(int code) {
+            for (BlockCode blockCode : BlockCode.values()) {
+                if (blockCode.code == code) {
+                    return blockCode;
+                }
+            }
+            byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) };
+            LOGGER.warning("Unknown block header: " + new String(codeBytes));
+            return BLOCK_UNKN;
+        }
     }
 }

+ 2 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java

@@ -254,7 +254,8 @@ public class Structure implements Cloneable {
             Structure id = (Structure) fieldValue;
             return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix
         }
-        return null;
+        Object name = this.getFieldValue("name", null);
+        return name == null ? null : name.toString().substring(2);
     }
 
     @Override

+ 4 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java

@@ -40,7 +40,6 @@ import com.jme3.light.PointLight;
 import com.jme3.light.SpotLight;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.FastMath;
-import com.jme3.scene.LightNode;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
@@ -67,8 +66,8 @@ public class LightHelper extends AbstractBlenderHelper {
         super(blenderVersion, blenderContext);
     }
 
-    public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
-        LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
+    public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
+        Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
@@ -111,6 +110,7 @@ public class LightHelper extends AbstractBlenderHelper {
         float g = ((Number) structure.getFieldValue("g")).floatValue();
         float b = ((Number) structure.getFieldValue("b")).floatValue();
         light.setColor(new ColorRGBA(r, g, b, 1.0f));
-        return new LightNode(structure.getName(), light);
+        light.setName(structure.getName());
+        return light;
     }
 }

+ 23 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java

@@ -1,11 +1,15 @@
 package com.jme3.scene.plugins.blender.materials;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
 import com.jme3.material.Material;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.FaceCullMode;
@@ -30,7 +34,7 @@ import com.jme3.util.BufferUtils;
  * This class holds the data about the material.
  * @author Marcin Roguski (Kaelthas)
  */
-public final class MaterialContext {
+public final class MaterialContext implements Savable {
     private static final Logger              LOGGER     = Logger.getLogger(MaterialContext.class.getName());
 
     // texture mapping types
@@ -67,7 +71,7 @@ public final class MaterialContext {
         int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
         diffuseShader = DiffuseShader.values()[diff_shader];
         ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue();
-        
+
         if (shadeless) {
             float r = ((Number) structure.getFieldValue("r")).floatValue();
             float g = ((Number) structure.getFieldValue("g")).floatValue();
@@ -107,6 +111,13 @@ public final class MaterialContext {
         this.transparent = transparent;
     }
 
+    /**
+     * @return the name of the material
+     */
+    public String getName() {
+        return name;
+    }
+
     /**
      * Applies material to a given geometry.
      * 
@@ -314,4 +325,14 @@ public final class MaterialContext {
         float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
         return new ColorRGBA(r, g, b, alpha);
     }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!");
+    }
+
+    @Override
+    public void read(JmeImporter e) throws IOException {
+        throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!");
+    }
 }

+ 6 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java

@@ -161,12 +161,17 @@ public class MaterialHelper extends AbstractBlenderHelper {
      *             an exception is throw when problems with blend file occur
      */
     public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
-        LOGGER.log(Level.FINE, "Loading material.");
         MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
 
+        if ("ID".equals(structure.getType())) {
+            LOGGER.fine("Loading material from external blend file.");
+            return (MaterialContext) this.loadLibrary(structure);
+        }
+
+        LOGGER.fine("Loading material.");
         result = new MaterialContext(structure, blenderContext);
         LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
         Long oma = structure.getOldMemoryAddress();

+ 6 - 7
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java

@@ -40,7 +40,6 @@ import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
 import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
@@ -52,7 +51,6 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.blender.materials.MaterialContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.scene.plugins.blender.objects.Properties;
 
@@ -106,17 +104,18 @@ public class MeshHelper extends AbstractBlenderHelper {
             return temporalMesh.clone();
         }
 
+        if ("ID".equals(meshStructure.getType())) {
+            LOGGER.fine("Loading mesh from external blend file.");
+            return (TemporalMesh) this.loadLibrary(meshStructure);
+        }
+
         String name = meshStructure.getName();
         LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
         temporalMesh = new TemporalMesh(meshStructure, blenderContext);
 
         LOGGER.fine("Loading materials.");
         MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
-        MaterialContext[] materials = null;
-        if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
-            materials = materialHelper.getMaterials(meshStructure, blenderContext);
-        }
-        temporalMesh.setMaterials(materials);
+        temporalMesh.setMaterials(materialHelper.getMaterials(meshStructure, blenderContext));
 
         LOGGER.fine("Reading custom properties.");
         Properties properties = this.loadProperties(meshStructure, blenderContext);

+ 38 - 32
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@@ -40,12 +40,15 @@ import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.light.Light;
 import com.jme3.math.FastMath;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.CameraNode;
 import com.jme3.scene.Geometry;
+import com.jme3.scene.LightNode;
 import com.jme3.scene.Mesh.Mode;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
@@ -106,39 +109,34 @@ public class ObjectHelper extends AbstractBlenderHelper {
      *             an exception is thrown when the given data is inapropriate
      */
     public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
-        LOGGER.fine("Loading blender object.");
+        Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
+        if (loadedResult != null) {
+            return loadedResult;
+        }
 
+        LOGGER.fine("Loading blender object.");
+        if ("ID".equals(objectStructure.getType())) {
+            Node object = (Node) this.loadLibrary(objectStructure);
+            if (object.getParent() != null) {
+                LOGGER.log(Level.FINEST, "Detaching object {0}, loaded from external file, from its parent.", object);
+                object.getParent().detachChild(object);
+            }
+            return object;
+        }
         int type = ((Number) objectStructure.getFieldValue("type")).intValue();
         ObjectType objectType = ObjectType.valueOf(type);
         LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType);
-        if (objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) {
-            LOGGER.fine("Lamps are not included in loading.");
-            return null;
-        }
-        if (objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) {
-            LOGGER.fine("Cameras are not included in loading.");
-            return null;
-        }
-        if (!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) {
-            LOGGER.fine("Objects are not included in loading.");
-            return null;
-        }
+
         int lay = ((Number) objectStructure.getFieldValue("lay")).intValue();
         if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) {
             LOGGER.fine("The layer this object is located in is not included in loading.");
             return null;
         }
 
-        LOGGER.fine("Checking if the object has not been already loaded.");
-        Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
-        if (loadedResult != null) {
-            return loadedResult;
-        }
-
         blenderContext.pushParent(objectStructure);
         String name = objectStructure.getName();
         LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
-        
+
         int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue();
         boolean visible = (restrictflag & 0x01) != 0;
 
@@ -171,7 +169,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     Pointer pMesh = (Pointer) objectStructure.getFieldValue("data");
                     List<Structure> meshesArray = pMesh.fetchData();
                     TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
-                    if(temporalMesh != null) {
+                    if (temporalMesh != null) {
                         result.attachChild(temporalMesh);
                     }
                     break;
@@ -183,7 +181,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
                         CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class);
                         Structure curveData = pCurve.fetchData().get(0);
                         TemporalMesh curvesTemporalMesh = curvesHelper.toCurve(curveData, blenderContext);
-                        if(curvesTemporalMesh != null) {
+                        if (curvesTemporalMesh != null) {
                             result.attachChild(curvesTemporalMesh);
                         }
                     }
@@ -193,10 +191,12 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     if (pLamp.isNotNull()) {
                         LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
                         List<Structure> lampsArray = pLamp.fetchData();
-                        result = lightHelper.toLight(lampsArray.get(0), blenderContext);
-                        if (result == null) {
+                        Light light = lightHelper.toLight(lampsArray.get(0), blenderContext);
+                        if (light == null) {
                             // probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes
                             result = new Node(name);
+                        } else {
+                            result = new LightNode(name, light);
                         }
                     }
                     break;
@@ -205,19 +205,25 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     if (pCamera.isNotNull()) {
                         CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class);
                         List<Structure> camerasArray = pCamera.fetchData();
-                        result = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
+                        Camera camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext);
+                        if (camera == null) {
+                            // just create a node so that we can maintain child-parent relationship for nodes
+                            result = new Node(name);
+                        } else {
+                            result = new CameraNode(name, camera);
+                        }
                     }
                     break;
                 default:
                     LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type);
             }
-            
+
             if (result != null) {
                 LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released).");
                 Long oma = objectStructure.getOldMemoryAddress();
                 blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure);
                 blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
-                
+
                 blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
                 if (objectType == ObjectType.ARMATURE) {
                     blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
@@ -235,13 +241,13 @@ public class ObjectHelper extends AbstractBlenderHelper {
                 for (Modifier modifier : modifiers) {
                     modifier.apply(result, blenderContext);
                 }
-                
+
                 if (result.getChildren() != null && result.getChildren().size() > 0) {
-                    if(result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) {
+                    if (result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) {
                         LOGGER.fine("Converting temporal mesh into jme geometries.");
-                        ((TemporalMesh)result.getChild(0)).toGeometries();
+                        ((TemporalMesh) result.getChild(0)).toGeometries();
                     }
-                    
+
                     LOGGER.fine("Applying proper scale to the geometries.");
                     for (Spatial child : result.getChildren()) {
                         if (child instanceof Geometry) {

+ 59 - 31
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java

@@ -121,7 +121,7 @@ public class TextureHelper extends AbstractBlenderHelper {
      * data. The returned texture has the name set to the value of its blender
      * type.
      * 
-     * @param tex
+     * @param textureStructure
      *            texture structure filled with data
      * @param blenderContext
      *            the blender context
@@ -130,23 +130,29 @@ public class TextureHelper extends AbstractBlenderHelper {
      *             this exception is thrown when the blend file structure is
      *             somehow invalid or corrupted
      */
-    public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
-        Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedDataType.FEATURE);
+    public Texture getTexture(Structure textureStructure, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
+        Texture result = (Texture) blenderContext.getLoadedFeature(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
-        int type = ((Number) tex.getFieldValue("type")).intValue();
-        int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue();
+
+        if ("ID".equals(textureStructure.getType())) {
+            LOGGER.fine("Loading texture from external blend file.");
+            return (Texture) this.loadLibrary(textureStructure);
+        }
+
+        int type = ((Number) textureStructure.getFieldValue("type")).intValue();
+        int imaflag = ((Number) textureStructure.getFieldValue("imaflag")).intValue();
 
         switch (type) {
             case TEX_IMAGE:// (it is first because probably this will be most commonly used)
-                Pointer pImage = (Pointer) tex.getFieldValue("ima");
+                Pointer pImage = (Pointer) textureStructure.getFieldValue("ima");
                 if (pImage.isNotNull()) {
                     Structure image = pImage.fetchData().get(0);
-                    Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext);
+                    Texture loadedTexture = this.loadImageAsTexture(image, imaflag, blenderContext);
                     if (loadedTexture != null) {
                         result = loadedTexture;
-                        this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext);
+                        this.applyColorbandAndColorFactors(textureStructure, result.getImage(), blenderContext);
                     }
                 }
                 break;
@@ -160,7 +166,7 @@ public class TextureHelper extends AbstractBlenderHelper {
             case TEX_MUSGRAVE:
             case TEX_VORONOI:
             case TEX_DISTNOISE:
-                result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext);
+                result = new GeneratedTexture(textureStructure, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext);
                 break;
             case TEX_NONE:// No texture, do nothing
                 break;
@@ -169,13 +175,13 @@ public class TextureHelper extends AbstractBlenderHelper {
             case TEX_PLUGIN:
             case TEX_ENVMAP:
             case TEX_OCEAN:
-                LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() });
+                LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, textureStructure.getName() });
                 break;
             default:
-                throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName());
+                throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + textureStructure.getName());
         }
         if (result != null) {
-            result.setName(tex.getName());
+            result.setName(textureStructure.getName());
             result.setWrap(WrapMode.Repeat);
 
             // decide if the mipmaps will be generated
@@ -195,14 +201,14 @@ public class TextureHelper extends AbstractBlenderHelper {
             }
 
             if (type != TEX_IMAGE) {// only generated textures should have this key
-                result.setKey(new GeneratedTextureKey(tex.getName()));
+                result.setKey(new GeneratedTextureKey(textureStructure.getName()));
             }
 
             if (LOGGER.isLoggable(Level.FINE)) {
-                LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() });
+                LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), textureStructure.getOldMemoryAddress() });
             }
-            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.STRUCTURE, tex);
-            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
+            blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, textureStructure);
+            blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
         }
         return result;
     }
@@ -222,30 +228,40 @@ public class TextureHelper extends AbstractBlenderHelper {
      *             this exception is thrown when the blend file structure is
      *             somehow invalid or corrupted
      */
-    protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException {
+    public Texture loadImageAsTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException {
         LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress());
         Texture result = null;
         Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (im == null) {
-            String texturePath = imageStructure.getFieldValue("name").toString();
-            Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile");
-            if (pPackedFile.isNull()) {
-                LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath);
-                result = this.loadImageFromFile(texturePath, imaflag, blenderContext);
+            if ("ID".equals(imageStructure.getType())) {
+                LOGGER.fine("Loading texture from external blend file.");
+                result = (Texture) this.loadLibrary(imageStructure);
             } else {
-                LOGGER.fine("Packed texture. Reading directly from the blend file!");
-                Structure packedFile = pPackedFile.fetchData().get(0);
-                Pointer pData = (Pointer) packedFile.getFieldValue("data");
-                FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress());
-                blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());
-                ImageLoader imageLoader = new ImageLoader();
-
-                // Should the texture be flipped? It works for sinbad ..
-                result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true));
+                String texturePath = imageStructure.getFieldValue("name").toString();
+                Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile");
+                if (pPackedFile.isNull()) {
+                    LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath);
+                    result = this.loadImageFromFile(texturePath, imaflag, blenderContext);
+                } else {
+                    LOGGER.fine("Packed texture. Reading directly from the blend file!");
+                    Structure packedFile = pPackedFile.fetchData().get(0);
+                    Pointer pData = (Pointer) packedFile.getFieldValue("data");
+                    FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress());
+                    blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition());
+
+                    // Should the texture be flipped? It works for sinbad ..
+                    result = new Texture2D(new ImageLoader().loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true));
+                }
             }
         } else {
             result = new Texture2D(im);
         }
+
+        if (result != null) {// render result is not being loaded
+            blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure);
+            blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result.getImage());
+            result.setName(imageStructure.getName());
+        }
         return result;
     }
 
@@ -524,6 +540,18 @@ public class TextureHelper extends AbstractBlenderHelper {
         return result;
     }
 
+    /**
+     * Reads the texture data from the given material or sky structure.
+     * @param structure
+     *            the structure of material or sky
+     * @param diffuseColorArray
+     *            array of diffuse colors
+     * @param skyTexture
+     *            indicates it we're going to read sky texture or not
+     * @return a list of combined textures
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with reading the blend file occur
+     */
     @SuppressWarnings("unchecked")
     public List<CombinedTexture> readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException {
         DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");

+ 10 - 8
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java

@@ -1,10 +1,12 @@
 package com.jme3.scene.plugins.blender.textures.blending;
 
+import java.util.logging.Logger;
+
+import jme3tools.converters.MipMapGenerator;
+
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.texture.Image;
-import java.util.logging.Logger;
-import jme3tools.converters.MipMapGenerator;
 
 /**
  * An abstract class that contains the basic methods used by the classes that
@@ -103,12 +105,12 @@ import jme3tools.converters.MipMapGenerator;
 
     public void copyBlendingData(TextureBlender textureBlender) {
         if (textureBlender instanceof AbstractTextureBlender) {
-            this.flag = ((AbstractTextureBlender) textureBlender).flag;
-            this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture;
-            this.blendType = ((AbstractTextureBlender) textureBlender).blendType;
-            this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone();
-            this.color = ((AbstractTextureBlender) textureBlender).color.clone();
-            this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor;
+            flag = ((AbstractTextureBlender) textureBlender).flag;
+            negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture;
+            blendType = ((AbstractTextureBlender) textureBlender).blendType;
+            materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone();
+            color = ((AbstractTextureBlender) textureBlender).color.clone();
+            blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor;
         } else {
             LOGGER.warning("Cannot copy blending data from other types than " + this.getClass());
         }

+ 3 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java

@@ -31,13 +31,13 @@
  */
 package com.jme3.scene.plugins.blender.textures.blending;
 
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image.Format;
 
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 /**
  * This class creates the texture blending class depending on the texture type.
  * 
@@ -66,7 +66,6 @@ public class TextureBlenderFactory {
      *            the texture format
      * @return texture blending class
      */
-    @SuppressWarnings("deprecation")
     public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) {
         switch (format) {
             case Luminance8:

+ 193 - 28
jme3-core/src/main/java/com/jme3/scene/UserData.java

@@ -33,6 +33,12 @@ package com.jme3.scene;
 
 import com.jme3.export.*;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * <code>UserData</code> is used to contain user data objects
@@ -48,28 +54,40 @@ public final class UserData implements Savable {
      * shape generation should ignore them.
      */
     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;
+    public static final String JME_SHAREDMESH    = "JmeSharedMesh";
+
+    private static final int   TYPE_INTEGER      = 0;
+    private static final int   TYPE_FLOAT        = 1;
+    private static final int   TYPE_BOOLEAN      = 2;
+    private static final int   TYPE_STRING       = 3;
+    private static final int   TYPE_LONG         = 4;
+    private static final int   TYPE_SAVABLE      = 5;
+    private static final int   TYPE_LIST         = 6;
+    private static final int   TYPE_MAP          = 7;
+    private static final int   TYPE_ARRAY        = 8;
+
+    protected byte             type;
+    protected Object           value;
 
     public UserData() {
     }
 
     /**
-     * Creates a new <code>UserData</code> with the given 
+     * Creates a new <code>UserData</code> with the given
      * type and value.
      * 
-     * @param type Type of data, should be between 0 and 4.
-     * @param value Value of the data
+     * @param type
+     *            Type of data, should be between 0 and 8.
+     * @param value
+     *            Value of the data
      */
     public UserData(byte type, Object value) {
-        assert type >= 0 && type <= 4;
+        assert type >= 0 && type <= 8;
         this.type = type;
         this.value = value;
     }
@@ -85,15 +103,23 @@ public final class UserData implements Savable {
 
     public static byte getObjectType(Object type) {
         if (type instanceof Integer) {
-            return 0;
+            return TYPE_INTEGER;
         } else if (type instanceof Float) {
-            return 1;
+            return TYPE_FLOAT;
         } else if (type instanceof Boolean) {
-            return 2;
+            return TYPE_BOOLEAN;
         } else if (type instanceof String) {
-            return 3;
+            return TYPE_STRING;
         } else if (type instanceof Long) {
-            return 4;
+            return TYPE_LONG;
+        } else if (type instanceof Savable) {
+            return TYPE_SAVABLE;
+        } else if (type instanceof List) {
+            return TYPE_LIST;
+        } else if (type instanceof Map) {
+            return TYPE_MAP;
+        } else if (type instanceof Object[]) {
+            return TYPE_ARRAY;
         } else {
             throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName());
         }
@@ -101,56 +127,195 @@ public final class UserData implements Savable {
 
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(type, "type", (byte)0);
+        oc.write(type, "type", (byte) 0);
 
         switch (type) {
-            case 0:
+            case TYPE_INTEGER:
                 int i = (Integer) value;
                 oc.write(i, "intVal", 0);
                 break;
-            case 1:
+            case TYPE_FLOAT:
                 float f = (Float) value;
                 oc.write(f, "floatVal", 0f);
                 break;
-            case 2:
+            case TYPE_BOOLEAN:
                 boolean b = (Boolean) value;
                 oc.write(b, "boolVal", false);
                 break;
-            case 3:
+            case TYPE_STRING:
                 String s = (String) value;
                 oc.write(s, "strVal", null);
                 break;
-            case 4:
+            case TYPE_LONG:
                 Long l = (Long) value;
                 oc.write(l, "longVal", 0l);
                 break;
+            case TYPE_SAVABLE:
+                Savable sav = (Savable) value;
+                oc.write(sav, "savableVal", null);
+                break;
+            case TYPE_LIST:
+                this.writeList(oc, (List<?>) value, "0");
+                break;
+            case TYPE_MAP:
+                Map<?, ?> map = (Map<?, ?>) value;
+                this.writeList(oc, map.keySet(), "0");
+                this.writeList(oc, map.values(), "1");
+                break;
+            case TYPE_ARRAY:
+                this.writeList(oc, Arrays.asList((Object[]) value), "0");
+                break;
             default:
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException("Unsupported value type: " + value.getClass());
         }
     }
 
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
         type = ic.readByte("type", (byte) 0);
-
         switch (type) {
-            case 0:
+            case TYPE_INTEGER:
                 value = ic.readInt("intVal", 0);
                 break;
-            case 1:
+            case TYPE_FLOAT:
                 value = ic.readFloat("floatVal", 0f);
                 break;
-            case 2:
+            case TYPE_BOOLEAN:
                 value = ic.readBoolean("boolVal", false);
                 break;
-            case 3:
+            case TYPE_STRING:
                 value = ic.readString("strVal", null);
                 break;
-            case 4:
+            case TYPE_LONG:
                 value = ic.readLong("longVal", 0l);
                 break;
+            case TYPE_SAVABLE:
+                value = ic.readSavable("savableVal", null);
+                break;
+            case TYPE_LIST:
+                value = this.readList(ic, "0");
+                break;
+            case TYPE_MAP:
+                Map<Object, Object> map = new HashMap<Object, Object>();
+                List<?> keys = this.readList(ic, "0");
+                List<?> values = this.readList(ic, "1");
+                for (int i = 0; i < keys.size(); ++i) {
+                    map.put(keys.get(i), values.get(i));
+                }
+                value = map;
+                break;
+            case TYPE_ARRAY:
+                value = this.readList(ic, "0").toArray();
+                break;
             default:
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException("Unknown type of stored data: " + type);
+        }
+    }
+
+    /**
+     * The method stores a list in the capsule.
+     * @param oc
+     *            output capsule
+     * @param list
+     *            the list to be stored
+     * @throws IOException
+     */
+    private void writeList(OutputCapsule oc, Collection<?> list, String listName) throws IOException {
+        if (list != null) {
+            oc.write(list.size(), listName + "size", 0);
+            int counter = 0;
+            for (Object o : list) {
+                // t is for 'type'; v is for 'value'
+                if (o instanceof Integer) {
+                    oc.write(TYPE_INTEGER, listName + "t" + counter, 0);
+                    oc.write((Integer) o, listName + "v" + counter, 0);
+                } else if (o instanceof Float) {
+                    oc.write(TYPE_FLOAT, listName + "t" + counter, 0);
+                    oc.write((Float) o, listName + "v" + counter, 0f);
+                } else if (o instanceof Boolean) {
+                    oc.write(TYPE_BOOLEAN, listName + "t" + counter, 0);
+                    oc.write((Boolean) o, listName + "v" + counter, false);
+                } else if (o instanceof String || o == null) {// treat null's like Strings just to store them and keep the List like the user intended
+                    oc.write(TYPE_STRING, listName + "t" + counter, 0);
+                    oc.write((String) o, listName + "v" + counter, null);
+                } else if (o instanceof Long) {
+                    oc.write(TYPE_LONG, listName + "t" + counter, 0);
+                    oc.write((Long) o, listName + "v" + counter, 0L);
+                } else if (o instanceof Savable) {
+                    oc.write(TYPE_SAVABLE, listName + "t" + counter, 0);
+                    oc.write((Savable) o, listName + "v" + counter, null);
+                } else if(o instanceof Object[]) {
+                    oc.write(TYPE_ARRAY, listName + "t" + counter, 0);
+                    this.writeList(oc, Arrays.asList((Object[]) o), listName + "v" + counter);
+                } else if(o instanceof List) {
+                    oc.write(TYPE_LIST, listName + "t" + counter, 0);
+                    this.writeList(oc, (List<?>) o, listName + "v" + counter);
+                } else if(o instanceof Map) {
+                    oc.write(TYPE_MAP, listName + "t" + counter, 0);
+                    Map<?, ?> map = (Map<?, ?>) o;
+                    this.writeList(oc, map.keySet(), listName + "v(keys)" + counter);
+                    this.writeList(oc, map.values(), listName + "v(vals)" + counter);
+                } else {
+                    throw new UnsupportedOperationException("Unsupported type stored in the list: " + o.getClass());
+                }
+                
+                ++counter;
+            }
+        } else {
+            oc.write(0, "size", 0);
+        }
+    }
+
+    /**
+     * The method loads a list from the given input capsule.
+     * @param ic
+     *            the input capsule
+     * @return loaded list (an empty list in case its size is 0)
+     * @throws IOException
+     */
+    private List<?> readList(InputCapsule ic, String listName) throws IOException {
+        int size = ic.readInt(listName + "size", 0);
+        List<Object> list = new ArrayList<Object>(size);
+        for (int i = 0; i < size; ++i) {
+            int type = ic.readInt(listName + "t" + i, 0);
+            switch (type) {
+                case TYPE_INTEGER:
+                    list.add(ic.readInt(listName + "v" + i, 0));
+                    break;
+                case TYPE_FLOAT:
+                    list.add(ic.readFloat(listName + "v" + i, 0));
+                    break;
+                case TYPE_BOOLEAN:
+                    list.add(ic.readBoolean(listName + "v" + i, false));
+                    break;
+                case TYPE_STRING:
+                    list.add(ic.readString(listName + "v" + i, null));
+                    break;
+                case TYPE_LONG:
+                    list.add(ic.readLong(listName + "v" + i, 0L));
+                    break;
+                case TYPE_SAVABLE:
+                    list.add(ic.readSavable(listName + "v" + i, null));
+                    break;
+                case TYPE_ARRAY:
+                    list.add(this.readList(ic, listName + "v" + i).toArray());
+                    break;
+                case TYPE_LIST:
+                    list.add(this.readList(ic, listName + "v" + i));
+                    break;
+                case TYPE_MAP:
+                    Map<Object, Object> map = new HashMap<Object, Object>();
+                    List<?> keys = this.readList(ic, listName + "v(keys)" + i);
+                    List<?> values = this.readList(ic, listName + "v(vals)" + i);
+                    for (int j = 0; j < keys.size(); ++j) {
+                        map.put(keys.get(j), values.get(j));
+                    }
+                    list.add(map);
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of stored data in a list: " + type);
+            }
         }
+        return list;
     }
 }