Quellcode durchsuchen

Feature: added sky loading.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10811 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
Kae..pl vor 12 Jahren
Ursprung
Commit
eb7e7bbaad

+ 81 - 54
engine/src/blender/com/jme3/asset/BlenderKey.java

@@ -46,9 +46,9 @@ import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
-import com.jme3.light.AmbientLight;
 import com.jme3.material.Material;
 import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.math.ColorRGBA;
 import com.jme3.scene.CameraNode;
 import com.jme3.scene.LightNode;
 import com.jme3.scene.Node;
@@ -63,24 +63,24 @@ import com.jme3.texture.Texture;
  */
 public class BlenderKey extends ModelKey {
 
-    protected static final int       DEFAULT_FPS            = 25;
+    protected static final int       DEFAULT_FPS             = 25;
     /**
      * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
      * between the frames.
      */
-    protected int                    fps                    = DEFAULT_FPS;
+    protected int                    fps                     = DEFAULT_FPS;
     /**
      * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
      */
-    protected int                    featuresToLoad         = FeaturesToLoad.ALL;
+    protected int                    featuresToLoad          = FeaturesToLoad.ALL;
     /** This variable determines if assets that are not linked to the objects should be loaded. */
     protected boolean                loadUnlinkedAssets;
     /** The root path for all the assets. */
     protected String                 assetRootPath;
     /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
-    protected boolean                fixUpAxis              = true;
+    protected boolean                fixUpAxis               = true;
     /** Generated textures resolution (PPU - Pixels Per Unit). */
-    protected int                    generatedTexturePPU    = 128;
+    protected int                    generatedTexturePPU     = 128;
     /**
      * The name of world settings that the importer will use. If not set or specified name does not occur in the file
      * then the first world settings in the file will be used.
@@ -92,20 +92,25 @@ public class BlenderKey extends ModelKey {
      */
     protected Material               defaultMaterial;
     /** Face cull mode. By default it is disabled. */
-    protected FaceCullMode           faceCullMode           = FaceCullMode.Back;
+    protected FaceCullMode           faceCullMode            = FaceCullMode.Back;
     /**
      * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
      * If set to -1 then the current layer will be loaded.
      */
-    protected int                    layersToLoad           = -1;
+    protected int                    layersToLoad            = -1;
     /** A variable that toggles the object custom properties loading. */
-    protected boolean                loadObjectProperties   = true;
+    protected boolean                loadObjectProperties    = true;
     /** Maximum texture size. Might be dependant on the graphic card. */
-    protected int                    maxTextureSize         = -1;
+    protected int                    maxTextureSize          = -1;
     /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
     protected boolean                loadGeneratedTextures;
     /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
-    protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
+    protected MipmapGenerationMethod mipmapGenerationMethod  = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
+    /**
+     * If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
+     * textures will get their proper size.
+     */
+    protected int                    skyGeneratedTextureSize = 1000;
 
     /**
      * Constructor used by serialization mechanisms.
@@ -356,6 +361,24 @@ public class BlenderKey extends ModelKey {
         this.mipmapGenerationMethod = mipmapGenerationMethod;
     }
 
+    /**
+     * @return the size of the generated textures for the sky (used if no flat textures are applied)
+     */
+    public int getSkyGeneratedTextureSize() {
+        return skyGeneratedTextureSize;
+    }
+
+    /**
+     * @param skyGeneratedTextureSize
+     *            the size of the generated textures for the sky (used if no flat textures are applied)
+     */
+    public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) {
+        if (skyGeneratedTextureSize <= 0) {
+            throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!");
+        }
+        this.skyGeneratedTextureSize = skyGeneratedTextureSize;
+    }
+
     /**
      * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
      * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
@@ -521,6 +544,7 @@ public class BlenderKey extends ModelKey {
         int TEXTURES   = 0x00000001;
         int CAMERAS    = 0x00000020;
         int LIGHTS     = 0x00000010;
+        int WORLD      = 0x00000040;
         int ALL        = 0xFFFFFFFF;
     }
 
@@ -531,21 +555,28 @@ public class BlenderKey extends ModelKey {
     public static class LoadingResults extends Spatial {
 
         /** Bitwise mask of features that are to be loaded. */
-        private final int        featuresToLoad;
+        private final int           featuresToLoad;
         /** The scenes from the file. */
-        private List<Node>       scenes;
+        private List<Node>          scenes;
         /** Objects from all scenes. */
-        private List<Node>       objects;
+        private List<Node>          objects;
         /** Materials from all objects. */
-        private List<Material>   materials;
+        private List<Material>      materials;
         /** Textures from all objects. */
-        private List<Texture>    textures;
+        private List<Texture>       textures;
         /** Animations of all objects. */
-        private List<AnimationData>   animations;
+        private List<AnimationData> animations;
         /** All cameras from the file. */
-        private List<CameraNode> cameras;
+        private List<CameraNode>    cameras;
         /** All lights from the file. */
-        private List<LightNode>  lights;
+        private List<LightNode>     lights;
+        /** Loaded sky. */
+        private Spatial             sky;
+        /**
+         * 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
@@ -654,7 +685,23 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded scenes.
+         * 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;
+        }
+
+        /**
+         * @param backgroundColor
+         *            the background color
+         */
+        public void setBackgroundColor(ColorRGBA backgroundColor) {
+            this.backgroundColor = backgroundColor;
+        }
+
+        /**
          * @return all loaded scenes
          */
         public List<Node> getScenes() {
@@ -662,7 +709,6 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded objects.
          * @return all loaded objects
          */
         public List<Node> getObjects() {
@@ -670,7 +716,6 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded materials.
          * @return all loaded materials
          */
         public List<Material> getMaterials() {
@@ -678,7 +723,6 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded textures.
          * @return all loaded textures
          */
         public List<Texture> getTextures() {
@@ -686,7 +730,6 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded animations.
          * @return all loaded animations
          */
         public List<AnimationData> getAnimations() {
@@ -694,7 +737,6 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded cameras.
          * @return all loaded cameras
          */
         public List<CameraNode> getCameras() {
@@ -702,13 +744,26 @@ public class BlenderKey extends ModelKey {
         }
 
         /**
-         * This method returns all loaded lights.
          * @return all loaded lights
          */
         public List<LightNode> getLights() {
             return lights;
         }
 
+        /**
+         * @return the scene's sky
+         */
+        public Spatial getSky() {
+            return sky;
+        }
+
+        /**
+         * @return the background color
+         */
+        public ColorRGBA getBackgroundColor() {
+            return backgroundColor;
+        }
+
         public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
             return 0;
         }
@@ -744,32 +799,4 @@ public class BlenderKey extends ModelKey {
         protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
         }
     }
-
-    /**
-     * The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient
-     * light.
-     * @author Marcin Roguski (Kaelthas)
-     */
-    public static class WorldData {
-
-        /** The ambient light. */
-        private AmbientLight ambientLight;
-
-        /**
-         * This method returns the world's ambient light.
-         * @return the world's ambient light
-         */
-        public AmbientLight getAmbientLight() {
-            return ambientLight;
-        }
-
-        /**
-         * This method sets the world's ambient light.
-         * @param ambientLight
-         *            the world's ambient light
-         */
-        public void setAmbientLight(AmbientLight ambientLight) {
-            this.ambientLight = ambientLight;
-        }
-    }
 }

+ 0 - 24
engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java

@@ -37,9 +37,6 @@ import java.util.logging.Logger;
 
 import com.jme3.asset.AssetLoader;
 import com.jme3.asset.BlenderKey.FeaturesToLoad;
-import com.jme3.asset.BlenderKey.WorldData;
-import com.jme3.light.AmbientLight;
-import com.jme3.math.ColorRGBA;
 import com.jme3.scene.CameraNode;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.LightNode;
@@ -166,25 +163,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
     // }
     // return null;
     // }
-
-    /**
-     * This method returns the data read from the WORLD file block. The block contains data that can be stored as
-     * separate jme features and therefore cannot be returned as a single jME scene feature.
-     * @param structure
-     *            the structure with WORLD block data
-     * @return data read from the WORLD block that can be added to the scene
-     */
-    public WorldData toWorldData(Structure structure) {
-        WorldData result = new WorldData();
-
-        // reading ambient light
-        AmbientLight ambientLight = new AmbientLight();
-        float ambr = ((Number) structure.getFieldValue("ambr")).floatValue();
-        float ambg = ((Number) structure.getFieldValue("ambg")).floatValue();
-        float ambb = ((Number) structure.getFieldValue("ambb")).floatValue();
-        ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f));
-        result.setAmbientLight(ambientLight);
-
-        return result;
-    }
 }

+ 8 - 8
engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java

@@ -41,7 +41,6 @@ import com.jme3.asset.AssetInfo;
 import com.jme3.asset.BlenderKey;
 import com.jme3.asset.BlenderKey.FeaturesToLoad;
 import com.jme3.asset.BlenderKey.LoadingResults;
-import com.jme3.asset.BlenderKey.WorldData;
 import com.jme3.asset.ModelKey;
 import com.jme3.scene.CameraNode;
 import com.jme3.scene.LightNode;
@@ -56,6 +55,7 @@ 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.Structure;
+import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
 import com.jme3.scene.plugins.blender.lights.LightHelper;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
 import com.jme3.scene.plugins.blender.meshes.MeshHelper;
@@ -82,7 +82,6 @@ public class BlenderLoader extends AbstractBlenderLoader {
             List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
             BlenderKey blenderKey = blenderContext.getBlenderKey();
             LoadingResults loadingResults = blenderKey.prepareLoadingResults();
-            WorldData worldData = null;// a set of data used in different scene aspects
             for (FileBlockHeader block : blocks) {
                 switch (block.getCode()) {
                     case FileBlockHeader.BLOCK_OB00:// Object
@@ -109,14 +108,14 @@ public class BlenderLoader extends AbstractBlenderLoader {
                         }
                         break;
                     case FileBlockHeader.BLOCK_WO00:// World
-                        if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used
+                        if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.WORLD) != 0) {
                             Structure worldStructure = block.getStructure(blenderContext);
                             String worldName = worldStructure.getName();
                             if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
-                                worldData = this.toWorldData(worldStructure);
-                                if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
-                                    loadingResults.addLight(worldData.getAmbientLight());
-                                }
+                                LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
+                                loadingResults.addLight(landscapeHelper.toAmbientLight(worldStructure));
+                                loadingResults.setSky(landscapeHelper.toSky(worldStructure));
+                                loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure));
                             }
                         }
                         break;
@@ -206,7 +205,8 @@ public class BlenderLoader extends AbstractBlenderLoader {
         blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
         blenderContext.putHelper(IpoHelper.class, new IpoHelper(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 {

+ 184 - 0
engine/src/blender/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java

@@ -0,0 +1,184 @@
+package com.jme3.scene.plugins.blender.landscape;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.light.AmbientLight;
+import com.jme3.light.Light;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.textures.ColorBand;
+import com.jme3.scene.plugins.blender.textures.CombinedTexture;
+import com.jme3.scene.plugins.blender.textures.ImageUtils;
+import com.jme3.scene.plugins.blender.textures.TextureHelper;
+import com.jme3.scene.plugins.blender.textures.TexturePixel;
+import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
+import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.util.SkyFactory;
+
+/**
+ * The class that allows to load the following: <li>the ambient light of the scene <li>the sky of the scene (with or without texture)
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class LandscapeHelper extends AbstractBlenderHelper {
+    private static final Logger LOGGER        = Logger.getLogger(LandscapeHelper.class.getName());
+
+    private static final int    SKYTYPE_BLEND = 1;
+    private static final int    SKYTYPE_REAL  = 2;
+    private static final int    SKYTYPE_PAPER = 4;
+
+    public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) {
+        super(blenderVersion, blenderContext);
+    }
+
+    /**
+     * Loads scene ambient light.
+     * @param worldStructure
+     *            the world's blender structure
+     * @return the scene's ambient light
+     */
+    public Light toAmbientLight(Structure worldStructure) {
+        LOGGER.fine("Loading ambient light.");
+        AmbientLight ambientLight = new AmbientLight();
+        float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue();
+        float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue();
+        float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue();
+        ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f);
+        ambientLight.setColor(ambientLightColor);
+        LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor);
+        return ambientLight;
+    }
+
+    /**
+     * Loads the background color.
+     * @param worldStructure
+     *            the world's structure
+     * @return the horizon color of the world which is used as a background color.
+     */
+    public ColorRGBA toBackgroundColor(Structure worldStructure) {
+        float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue();
+        float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue();
+        float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue();
+        return new ColorRGBA(horr, horg, horb, 1);
+    }
+
+    /**
+     * Loads scene's sky. Sky can be plain or textured.
+     * If no sky type is selected in blender then no sky is loaded.
+     * @param worldStructure
+     *            the world's structure
+     * @return the scene's sky
+     * @throws BlenderFileException
+     *             blender exception is thrown when problems with blender file occur
+     */
+    public Spatial toSky(Structure worldStructure) throws BlenderFileException {
+        int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue();
+        if (skytype == 0) {
+            return null;
+        }
+
+        LOGGER.fine("Loading sky.");
+        ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure);
+
+        float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue();
+        float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue();
+        float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue();
+        ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1);
+
+        // jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky
+        boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures();
+        blenderContext.getBlenderKey().setLoadGeneratedTextures(true);
+
+        TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
+        Map<Number, CombinedTexture> loadedTextures = null;
+        try {
+            loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true);
+        } finally {
+            blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures);
+        }
+
+        TextureCubeMap texture = null;
+        if (loadedTextures != null && loadedTextures.size() > 0) {
+            if (loadedTextures.size() > 1) {
+                throw new IllegalStateException("There should be only one combined texture for sky!");
+            }
+            CombinedTexture combinedTexture = loadedTextures.get(1);
+            texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext);
+        } else {
+            LOGGER.fine("Preparing colors for colorband.");
+            int colorbandType = ColorBand.IPO_CARDINAL;
+            List<ColorRGBA> colorbandColors = new ArrayList<ColorRGBA>(3);
+            colorbandColors.add(horizontalColor);
+            if ((skytype & SKYTYPE_BLEND) != 0) {
+                if ((skytype & SKYTYPE_PAPER) != 0) {
+                    colorbandType = ColorBand.IPO_LINEAR;
+                }
+                if ((skytype & SKYTYPE_REAL) != 0) {
+                    colorbandColors.add(0, zenithColor);
+                }
+                colorbandColors.add(zenithColor);
+            }
+
+            int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
+
+            List<Integer> positions = new ArrayList<Integer>(colorbandColors.size());
+            positions.add(0);
+            if (colorbandColors.size() == 2) {
+                positions.add(size - 1);
+            } else if (colorbandColors.size() == 3) {
+                positions.add(size / 2);
+                positions.add(size - 1);
+            }
+
+            LOGGER.fine("Generating sky texture.");
+            float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues();
+
+            Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
+            PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
+            TexturePixel pixel = new TexturePixel();
+
+            LOGGER.fine("Creating side textures.");
+            int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 };
+            for (int i : sideImagesIndexes) {
+                for (int y = 0; y < size; ++y) {
+                    pixel.red = values[y][0];
+                    pixel.green = values[y][1];
+                    pixel.blue = values[y][2];
+
+                    for (int x = 0; x < size; ++x) {
+                        pixelIO.write(image, i, pixel, x, y);
+                    }
+                }
+            }
+
+            LOGGER.fine("Creating top texture.");
+            pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1);
+            for (int y = 0; y < size; ++y) {
+                for (int x = 0; x < size; ++x) {
+                    pixelIO.write(image, 3, pixel, x, y);
+                }
+            }
+
+            texture = new TextureCubeMap(image);
+        }
+
+        LOGGER.fine("Sky texture created. Creating sky.");
+        return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false);
+    }
+
+    @Override
+    public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
+        return true;
+    }
+}

+ 1 - 93
engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java

@@ -1,7 +1,5 @@
 package com.jme3.scene.plugins.blender.materials;
 
-import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -21,15 +19,11 @@ import com.jme3.scene.VertexBuffer.Format;
 import com.jme3.scene.VertexBuffer.Usage;
 import com.jme3.scene.plugins.blender.BlenderContext;
 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.MaterialHelper.DiffuseShader;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
 import com.jme3.scene.plugins.blender.textures.CombinedTexture;
 import com.jme3.scene.plugins.blender.textures.TextureHelper;
-import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
-import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
 import com.jme3.texture.Texture;
 import com.jme3.util.BufferUtils;
 
@@ -63,7 +57,6 @@ public final class MaterialContext {
     /* package */final boolean                      vTangent;
     /* package */FaceCullMode                       faceCullMode;
 
-    @SuppressWarnings("unchecked")
     /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
         name = structure.getName();
 
@@ -101,56 +94,8 @@ public final class MaterialContext {
             ambientColor = new ColorRGBA(r, g, b, alpha);
         }
 
-        DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
-        int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
-        List<TextureData> texturesList = new ArrayList<TextureData>();
-        for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
-            Pointer p = mtexsArray.get(i);
-            if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
-                TextureData textureData = new TextureData();
-                textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0);
-                textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue();
-                textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue();
-                textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString();
-                if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) {
-                    textureData.uvCoordinatesName = null;
-                }
-                
-                Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex");
-                if (pTex.isNotNull()) {
-                    Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
-                    textureData.textureStructure = tex;
-                    texturesList.add(textureData);
-                }
-            }
-        }
-
-        // loading the textures and merging them
-        Map<Number, List<TextureData>> textureDataMap = this.sortAndFilterTextures(texturesList);
-        loadedTextures = new HashMap<Number, CombinedTexture>();
-        float[] diffuseColorArray = new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a };
         TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
-        for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
-            if (entry.getValue().size() > 0) {
-                CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue());
-                for (TextureData textureData : entry.getValue()) {
-                    int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
-                    boolean negateTexture = (texflag & 0x04) != 0;
-                    Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext);
-                    if (texture != null) {
-                        int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
-                        float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
-                        float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue();
-                        TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac);
-                        combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType,
-                        					textureData.textureStructure, textureData.uvCoordinatesName, blenderContext);
-                    }
-                }
-                if (combinedTexture.getTexturesCount() > 0) {
-                    loadedTextures.put(entry.getKey(), combinedTexture);
-                }
-            }
-        }
+        loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false);
 
         // veryfying if the transparency is present
         // (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
@@ -301,34 +246,6 @@ public final class MaterialContext {
         return false;
     }
 
-    /**
-     * This method sorts the textures by their mapping type. In each group only
-     * textures of one type are put (either two- or three-dimensional). If the
-     * mapping type is MTEX_COL then if the texture has no alpha channel then
-     * all textures before it are discarded and will not be loaded and merged
-     * because texture with no alpha will cover them anyway.
-     * 
-     * @return a map with sorted and filtered textures
-     */
-    private Map<Number, List<TextureData>> sortAndFilterTextures(List<TextureData> textures) {
-        int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA, MTEX_AMB };
-        Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
-        for (TextureData data : textures) {
-            Number mapto = (Number) data.mtex.getFieldValue("mapto");
-            for (int i = 0; i < mappings.length; ++i) {
-                if ((mappings[i] & mapto.intValue()) != 0) {
-                    List<TextureData> datas = result.get(mappings[i]);
-                    if (datas == null) {
-                        datas = new ArrayList<TextureData>();
-                        result.put(mappings[i], datas);
-                    }
-                    datas.add(data);
-                }
-            }
-        }
-        return result;
-    }
-
     /**
      * This method sets the face cull mode.
      * @param faceCullMode
@@ -393,13 +310,4 @@ public final class MaterialContext {
         float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
         return new ColorRGBA(r, g, b, alpha);
     }
-
-    private static class TextureData {
-        Structure mtex;
-        Structure textureStructure;
-        int       uvCoordinatesType;
-        int       projectionType;
-        /** The name of the user's UV coordinates that are used for this texture. */
-        String	  uvCoordinatesName;
-    }
 }

+ 60 - 17
engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java

@@ -31,6 +31,7 @@
  */
 package com.jme3.scene.plugins.blender.textures;
 
+import com.jme3.math.ColorRGBA;
 import com.jme3.math.FastMath;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
@@ -38,6 +39,7 @@ 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 java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.logging.Level;
@@ -59,8 +61,46 @@ public class ColorBand {
     public static final int     IPO_CONSTANT = 4;
 
     private int                 cursorsAmount, ipoType;
+    /** The default amount of possible cursor positions. */
+    private int                 resultSize   = 1001;
     private ColorBandData[]     data;
 
+    /**
+     * A constructor used to instantiate color band by hand instead of reading it from the blend file.
+     * @param ipoType
+     *            the interpolation type
+     * @param colors
+     *            the colorband colors
+     * @param positions
+     *            the positions for colors' cursors
+     * @param resultSize
+     *            the size of the result table
+     */
+    public ColorBand(int ipoType, List<ColorRGBA> colors, List<Integer> positions, int resultSize) {
+        if (colors == null || colors.size() < 1) {
+            throw new IllegalArgumentException("The amount of colorband's colors must be at least 1.");
+        }
+        if (ipoType < IPO_LINEAR || ipoType > IPO_CONSTANT) {
+            throw new IllegalArgumentException("Unknown colorband interpolation type: " + ipoType);
+        }
+        if (positions == null || positions.size() != colors.size()) {
+            throw new IllegalArgumentException("The size of positions and colors list should be equal!");
+        }
+        for (Integer position : positions) {
+            if (position.intValue() < 0 || position.intValue() >= resultSize) {
+                throw new IllegalArgumentException("Invalid position value: " + position + "! Should be from range: [0, " + resultSize + "]!");
+            }
+        }
+
+        cursorsAmount = colors.size();
+        this.ipoType = ipoType;
+        this.resultSize = resultSize;
+        data = new ColorBandData[this.cursorsAmount];
+        for (int i = 0; i < cursorsAmount; ++i) {
+            data[i] = new ColorBandData(colors.get(i), positions.get(i));
+        }
+    }
+
     /**
      * Constructor. Loads the data from the given structure.
      * @param tex
@@ -113,11 +153,8 @@ public class ColorBand {
     public float[][] computeValues() {
         float[][] result = null;
         if (data != null) {
-            result = new float[1001][4];// 1001 - amount of possible cursor
-                                        // positions; 4 = [r, g, b, a]
-
-            if (data.length == 1) {// special case; use only one color for all
-                                   // types of colorband interpolation
+            result = new float[resultSize][4];// resultSize - amount of possible cursor positions; 4 = [r, g, b, a]
+            if (data.length == 1) {// special case; use only one color for all types of colorband interpolation
                 for (int i = 0; i < result.length; ++i) {
                     result[i][0] = data[0].r;
                     result[i][1] = data[0].g;
@@ -167,7 +204,7 @@ public class ColorBand {
                         if (data[0].pos == 0) {
                             cbDataMap.put(Integer.valueOf(-1), data[0]);
                         } else {
-                            ColorBandData cbData = data[0].clone();
+                            ColorBandData cbData = new ColorBandData(data[0]);
                             cbData.pos = 0;
                             cbDataMap.put(Integer.valueOf(-1), cbData);
                             cbDataMap.put(Integer.valueOf(-2), cbData);
@@ -176,7 +213,7 @@ public class ColorBand {
                         if (data[data.length - 1].pos == 1000) {
                             cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
                         } else {
-                            ColorBandData cbData = data[data.length - 1].clone();
+                            ColorBandData cbData = new ColorBandData(data[data.length - 1]);
                             cbData.pos = 1000;
                             cbDataMap.put(Integer.valueOf(data.length), cbData);
                             cbDataMap.put(Integer.valueOf(data.length + 1), cbData);
@@ -307,7 +344,7 @@ public class ColorBand {
      * 
      * @author Marcin Roguski (Kaelthas)
      */
-    private static class ColorBandData implements Cloneable {
+    private static class ColorBandData {
         public final float r, g, b, a;
         public int         pos;
 
@@ -316,6 +353,21 @@ public class ColorBand {
             a = 1;
         }
 
+        /**
+         * Constructor that stores the color and position of the cursor.
+         * @param color
+         *            the cursor's color
+         * @param pos
+         *            the cursor's position
+         */
+        public ColorBandData(ColorRGBA color, int pos) {
+            r = color.r;
+            g = color.g;
+            b = color.b;
+            a = color.a;
+            this.pos = pos;
+        }
+
         /**
          * Copy constructor.
          */
@@ -341,15 +393,6 @@ public class ColorBand {
             this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f);
         }
 
-        @Override
-        public ColorBandData clone() {
-            try {
-                return (ColorBandData) super.clone();
-            } catch (CloneNotSupportedException e) {
-                return new ColorBandData(this);
-            }
-        }
-
         @Override
         public String toString() {
             return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]";

+ 121 - 60
engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java

@@ -3,13 +3,16 @@ package com.jme3.scene.plugins.blender.textures;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import jme3tools.converters.ImageToAwt;
 
+import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
@@ -30,6 +33,8 @@ import com.jme3.texture.Texture.MagFilter;
 import com.jme3.texture.Texture.MinFilter;
 import com.jme3.texture.Texture.WrapMode;
 import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.util.BufferUtils;
 
 /**
  * This class represents a texture that is defined for the material. It can be
@@ -43,6 +48,11 @@ public class CombinedTexture {
 
     /** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */
     private final int           mappingType;
+    /**
+     * If set to true then if a texture without alpha is added then all textures below are discarded because
+     * the new one will cover them anyway. If set to false then all textures are stored.
+     */
+    private boolean             discardCoveredTextures;
     /** The data for each of the textures. */
     private List<TextureData>   textureDatas = new ArrayList<TextureData>();
     /** The result texture. */
@@ -55,9 +65,13 @@ public class CombinedTexture {
      * 
      * @param mappingType
      *            texture mapping type
+     * @param discardCoveredTextures
+     *            if set to true then if a texture without alpha is added then all textures below are discarded because
+     *            the new one will cover them anyway, if set to false then all textures are stored
      */
-    public CombinedTexture(int mappingType) {
+    public CombinedTexture(int mappingType, boolean discardCoveredTextures) {
         this.mappingType = mappingType;
+        this.discardCoveredTextures = discardCoveredTextures;
     }
 
     /**
@@ -75,7 +89,7 @@ public class CombinedTexture {
      * @param textureStructure
      *            the texture sructure
      * @param uvCoordinatesName
-     * 			  the name of the used user's UV coordinates for this texture
+     *            the name of the used user's UV coordinates for this texture
      * @param blenderContext
      *            the blender context
      */
@@ -93,7 +107,7 @@ public class CombinedTexture {
                 textureData.textureStructure = textureStructure;
                 textureData.uvCoordinatesName = uvCoordinatesName;
 
-                if (textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) {
+                if (discardCoveredTextures && textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) {
                     textureDatas.clear();// clear previous textures, they will be covered anyway
                 }
                 textureDatas.add(textureData);
@@ -120,7 +134,6 @@ public class CombinedTexture {
      */
     @SuppressWarnings("unchecked")
     public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
-        TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
         Mesh mesh = geometry.getMesh();
         Texture previousTexture = null;
         UVCoordinatesType masterUVCoordinatesType = null;
@@ -128,7 +141,7 @@ public class CombinedTexture {
         for (TextureData textureData : textureDatas) {
             // decompress compressed textures (all will be merged into one texture anyway)
             if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) {
-                textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage()));
+                textureData.texture.setImage(ImageUtils.decompress(textureData.texture.getImage()));
                 textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
             }
 
@@ -139,8 +152,8 @@ public class CombinedTexture {
                     resultTexture = textureData.texture;
 
                     if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
-                        if(textureData.uvCoordinatesName == null) {
-                            resultUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available
+                        if (textureData.uvCoordinatesName == null) {
+                            resultUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available
                         } else {
                             resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
                         }
@@ -167,11 +180,9 @@ public class CombinedTexture {
                     triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext);
                     resultTexture = previousTexture = triangulatedTexture;
                 } else if (textureData.texture instanceof Texture2D) {
-                    if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName,
-                                             textureData.uvCoordinatesType, textureData.uvCoordinatesName) &&
-                        resultTexture instanceof Texture2D) {
+                    if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, textureData.uvCoordinatesType, textureData.uvCoordinatesName) && resultTexture instanceof Texture2D) {
                         this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight());
-                        this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture);
+                        ImageUtils.merge(resultTexture.getImage(), textureData.texture.getImage());
                         previousTexture = resultTexture;
                     } else {
                         if (!(resultTexture instanceof TriangulatedTexture)) {
@@ -181,8 +192,8 @@ public class CombinedTexture {
                         // first triangulate the current texture
                         List<Vector2f> textureUVS = null;
                         if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
-                            if(textureData.uvCoordinatesName == null) {
-                                textureUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available
+                            if (textureData.uvCoordinatesName == null) {
+                                textureUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available
                             } else {
                                 textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
                             }
@@ -193,7 +204,10 @@ public class CombinedTexture {
                         TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext);
                         // then move the texture to different UV's
                         triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext);
-                        ((TriangulatedTexture) resultTexture).merge(triangulatedTexture);
+                        // merge triangulated textures
+                        for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
+                            ImageUtils.merge(((TriangulatedTexture) resultTexture).getFaceTextureElement(i).image, triangulatedTexture.getImage());
+                        }
                     }
                 }
             }
@@ -203,7 +217,7 @@ public class CombinedTexture {
             if (mappingType == MaterialContext.MTEX_NOR) {
                 for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
                     TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i);
-                    triangleTextureElement.image = textureHelper.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor
+                    triangleTextureElement.image = ImageUtils.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor
                 }
             }
             resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
@@ -218,22 +232,102 @@ public class CombinedTexture {
         resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
     }
 
+    /**
+     * Generates a texture that will be used by the sky spatial.
+     * The result texture has 6 layers. Every image in each layer has equal size and its shape is a square.
+     * The size of each image is the maximum size (width or height) of the textures given.
+     * The default sky generated texture size is used (this value is set in the BlenderKey) if no picture textures
+     * are present or their sizes is lower than the generated texture size.
+     * The textures of lower sizes are properly scaled.
+     * All the textures are mixed into one and put as layers in the result texture.
+     * 
+     * @param horizontalColor
+     *            the horizon color
+     * @param zenithColor
+     *            the zenith color
+     * @param blenderContext
+     *            the blender context
+     * @return texture for the sky
+     */
+    public TextureCubeMap generateSkyTexture(ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) {
+        LOGGER.log(Level.FINE, "Preparing sky texture from {0} applied textures.", textureDatas.size());
+
+        LOGGER.fine("Computing the texture size.");
+        int size = -1;
+        for (TextureData textureData : textureDatas) {
+            if (textureData.texture instanceof Texture2D) {
+                size = Math.max(textureData.texture.getImage().getWidth(), size);
+                size = Math.max(textureData.texture.getImage().getHeight(), size);
+            }
+        }
+        if (size < 0) {
+            size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
+        }
+        LOGGER.log(Level.FINE, "The sky texture size will be: {0}x{0}.", size);
+
+        TextureCubeMap result = null;
+        for (TextureData textureData : textureDatas) {
+            TextureCubeMap texture = null;
+            if (textureData.texture instanceof GeneratedTexture) {
+                texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor);
+            } else {
+                // first create a grayscale version of the image
+                Image image = textureData.texture.getImage();
+                if (image.getWidth() != image.getHeight() || image.getWidth() != size) {
+                    image = ImageUtils.resizeTo(image, size, size);
+                }
+                Image grayscaleImage = ImageUtils.convertToGrayscaleTexture(image);
+
+                // add the sky colors to the image
+                PixelInputOutput sourcePixelIO = PixelIOFactory.getPixelIO(grayscaleImage.getFormat());
+                PixelInputOutput targetPixelIO = PixelIOFactory.getPixelIO(image.getFormat());
+                TexturePixel texturePixel = new TexturePixel();
+                for (int x = 0; x < image.getWidth(); ++x) {
+                    for (int y = 0; y < image.getHeight(); ++y) {
+                        sourcePixelIO.read(grayscaleImage, 0, texturePixel, x, y);
+                        texturePixel.intensity = texturePixel.red;// no matter which factor we use here, in grayscale they are all equal
+                        ImageUtils.color(texturePixel, horizontalColor, zenithColor);
+                        targetPixelIO.write(image, 0, texturePixel, x, y);
+                    }
+                }
+
+                // create the cubemap texture from the coloured image
+                ByteBuffer sourceData = image.getData(0);
+                ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(6);
+                for (int i = 0; i < 6; ++i) {
+                    data.add(BufferUtils.clone(sourceData));
+                }
+                texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data));
+            }
+
+            if (result == null) {
+                result = texture;
+            } else {
+                ImageUtils.mix(result.getImage(), texture.getImage());
+            }
+        }
+        return result;
+    }
+
     /**
      * The method checks if the texture UV coordinates match.
      * It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match.
      * If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal.
      * In other cases they are considered NOT a match.
-     * @param type1 the UV coord type
-     * @param uvSetName1 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
-     * @param type2 the UV coord type
-     * @param uvSetName2 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
+     * @param type1
+     *            the UV coord type
+     * @param uvSetName1
+     *            the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
+     * @param type2
+     *            the UV coord type
+     * @param uvSetName2
+     *            the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
      * @return <b>true</b> if the types match and <b>false</b> otherwise
      */
-    private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1,
-                                     UVCoordinatesType type2, String uvSetName2) {
-        if(type1 == type2) {
-            if(type1 == UVCoordinatesType.TEXCO_UV) {
-                if(uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) {
+    private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, UVCoordinatesType type2, String uvSetName2) {
+        if (type1 == type2) {
+            if (type1 == UVCoordinatesType.TEXCO_UV) {
+                if (uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) {
                     return true;
                 }
             } else {
@@ -242,7 +336,7 @@ public class CombinedTexture {
         }
         return false;
     }
-    
+
     /**
      * This method blends the texture.
      * 
@@ -298,40 +392,7 @@ public class CombinedTexture {
         }
         return false;
     }
-
-    /**
-     * This method merges two given textures. The result is stored in the
-     * 'target' texture.
-     * 
-     * @param target
-     *            the target texture
-     * @param source
-     *            the source texture
-     */
-    private void merge(Texture2D target, Texture2D source) {
-        if (target.getImage().getDepth() != source.getImage().getDepth()) {
-            throw new IllegalArgumentException("Cannot merge images with different depths!");
-        }
-        Image sourceImage = source.getImage();
-        Image targetImage = target.getImage();
-        PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
-        PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
-        TexturePixel sourcePixel = new TexturePixel();
-        TexturePixel targetPixel = new TexturePixel();
-        int depth = target.getImage().getDepth() == 0 ? 1 : target.getImage().getDepth();
-
-        for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
-            for (int x = 0; x < sourceImage.getWidth(); ++x) {
-                for (int y = 0; y < sourceImage.getHeight(); ++y) {
-                    sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
-                    targetIO.read(targetImage, layerIndex, targetPixel, x, y);
-                    targetPixel.merge(sourcePixel);
-                    targetIO.write(targetImage, layerIndex, targetPixel, x, y);
-                }
-            }
-        }
-    }
-
+    
     /**
      * This method determines if the given texture has no alpha channel.
      * 
@@ -460,6 +521,6 @@ public class CombinedTexture {
         /** The texture sructure. */
         public Structure         textureStructure;
         /** The name of the user's UV coordinates that are used for this texture. */
-        public String	  		 uvCoordinatesName;
+        public String            uvCoordinatesName;
     }
 }

+ 73 - 4
engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java

@@ -1,6 +1,13 @@
 package com.jme3.scene.plugins.blender.textures;
 
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
 import com.jme3.bounding.BoundingBox;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
@@ -10,12 +17,12 @@ import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
 import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
 import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator;
+import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
+import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
 import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
 import com.jme3.texture.Texture;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
+import com.jme3.texture.TextureCubeMap;
 
 /**
  * The generated texture loaded from blender file. The texture is not generated
@@ -25,6 +32,13 @@ import java.util.TreeSet;
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class GeneratedTexture extends Texture {
+    private static final int       POSITIVE_X       = 0;
+    private static final int       NEGATIVE_X       = 1;
+    // private static final int POSITIVE_Y = 2;
+    private static final int       NEGATIVE_Y       = 3;
+    private static final int       POSITIVE_Z       = 4;
+    private static final int       NEGATIVE_Z       = 5;
+
     // flag values
     public static final int        TEX_COLORBAND    = 1;
     public static final int        TEX_FLIPBLEND    = 2;
@@ -115,6 +129,61 @@ import java.util.TreeSet;
         return new TriangulatedTexture(triangleTextureElements, blenderContext);
     }
 
+    /**
+     * Creates a texture for the sky. The result texture has 6 layers.
+     * @param size
+     *            the size of the texture (width and height are equal)
+     * @param horizontalColor
+     *            the horizon color
+     * @param zenithColor
+     *            the zenith color
+     * @return the sky texture
+     */
+    public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor) {
+        Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
+        PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
+        TexturePixel pixel = new TexturePixel();
+
+        float delta = 1 / (float) (size - 1);
+        float sideV, sideS = 1, forwardU = 1, forwardV, upS;
+
+        long time = System.currentTimeMillis();
+        for (int x = 0; x < size; ++x) {
+            sideV = 1;
+            forwardV = 1;
+            upS = 0;
+            for (int y = 0; y < size; ++y) {
+                textureGenerator.getPixel(pixel, 1, sideV, sideS);
+                pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right
+
+                textureGenerator.getPixel(pixel, 0, sideV, 1 - sideS);
+                pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left
+
+                textureGenerator.getPixel(pixel, forwardU, forwardV, 0);
+                pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front
+
+                textureGenerator.getPixel(pixel, 1 - forwardU, forwardV, 1);
+                pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back
+
+                textureGenerator.getPixel(pixel, forwardU, 0, upS);
+                pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top
+
+                // textureGenerator.getPixel(pixel, forwardU, 1, upS);
+                // pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);//bottom
+
+                sideV = FastMath.clamp(sideV - delta, 0, 1);
+                forwardV = FastMath.clamp(forwardV - delta, 0, 1);
+                upS = FastMath.clamp(upS + delta, 0, 1);
+            }
+            sideS = FastMath.clamp(sideS - delta, 0, 1);
+            forwardU = FastMath.clamp(forwardU - delta, 0, 1);
+        }
+
+        System.out.println(System.currentTimeMillis() - time);
+
+        return new TextureCubeMap(image);
+    }
+
     @Override
     public void setWrap(WrapAxis axis, WrapMode mode) {
     }

+ 471 - 0
engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageUtils.java

@@ -0,0 +1,471 @@
+package com.jme3.scene.plugins.blender.textures;
+
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorConvertOp;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import jme3tools.converters.ImageToAwt;
+import jme3tools.converters.RGB565;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
+import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.BufferUtils;
+
+/**
+ * This utility class has the methods that deal with images.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public final class ImageUtils {
+    /**
+     * Creates an image of the given size and depth.
+     * @param format
+     *            the image format
+     * @param width
+     *            the image width
+     * @param height
+     *            the image height
+     * @param depth
+     *            the image depth
+     * @return the new image instance
+     */
+    public static Image createEmptyImage(Format format, int width, int height, int depth) {
+        int bufferSize = width * height * (format.getBitsPerPixel() >> 3);
+        if (depth < 2) {
+            return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize));
+        }
+        ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(depth);
+        for (int i = 0; i < depth; ++i) {
+            data.add(BufferUtils.createByteBuffer(bufferSize));
+        }
+        return new Image(Format.RGB8, width, height, depth, data);
+    }
+
+    /**
+     * The method sets a color for the given pixel by merging the two given colors.
+     * The lowIntensityColor will be most visible when the pixel has low intensity.
+     * The highIntensityColor will be most visible when the pixel has high intensity.
+     * 
+     * @param pixel
+     *            the pixel that will have the colors altered
+     * @param lowIntensityColor
+     *            the low intensity color
+     * @param highIntensityColor
+     *            the high intensity color
+     * @return the altered pixel (the same instance)
+     */
+    public static TexturePixel color(TexturePixel pixel, ColorRGBA lowIntensityColor, ColorRGBA highIntensityColor) {
+        float intensity = pixel.intensity;
+        pixel.fromColor(lowIntensityColor);
+        pixel.mult(1 - pixel.intensity);
+        pixel.add(highIntensityColor.mult(intensity));
+        return pixel;
+    }
+
+    /**
+     * This method merges two given images. The result is stored in the
+     * 'target' image.
+     * 
+     * @param targetImage
+     *            the target image
+     * @param sourceImage
+     *            the source image
+     */
+    public static void merge(Image targetImage, Image sourceImage) {
+        if (sourceImage.getDepth() != targetImage.getDepth()) {
+            throw new IllegalArgumentException("The given images should have the same depth to merge them!");
+        }
+        if (sourceImage.getWidth() != targetImage.getWidth()) {
+            throw new IllegalArgumentException("The given images should have the same width to merge them!");
+        }
+        if (sourceImage.getHeight() != targetImage.getHeight()) {
+            throw new IllegalArgumentException("The given images should have the same height to merge them!");
+        }
+
+        PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
+        PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
+        TexturePixel sourcePixel = new TexturePixel();
+        TexturePixel targetPixel = new TexturePixel();
+        int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth();
+
+        for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
+            for (int x = 0; x < sourceImage.getWidth(); ++x) {
+                for (int y = 0; y < sourceImage.getHeight(); ++y) {
+                    sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
+                    targetIO.read(targetImage, layerIndex, targetPixel, x, y);
+                    targetPixel.merge(sourcePixel);
+                    targetIO.write(targetImage, layerIndex, targetPixel, x, y);
+                }
+            }
+        }
+    }
+
+    /**
+     * This method merges two given images. The result is stored in the
+     * 'target' image.
+     * 
+     * @param targetImage
+     *            the target image
+     * @param sourceImage
+     *            the source image
+     */
+    public static void mix(Image targetImage, Image sourceImage) {
+        if (sourceImage.getDepth() != targetImage.getDepth()) {
+            throw new IllegalArgumentException("The given images should have the same depth to merge them!");
+        }
+        if (sourceImage.getWidth() != targetImage.getWidth()) {
+            throw new IllegalArgumentException("The given images should have the same width to merge them!");
+        }
+        if (sourceImage.getHeight() != targetImage.getHeight()) {
+            throw new IllegalArgumentException("The given images should have the same height to merge them!");
+        }
+
+        PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
+        PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
+        TexturePixel sourcePixel = new TexturePixel();
+        TexturePixel targetPixel = new TexturePixel();
+        int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth();
+
+        for (int layerIndex = 0; layerIndex < depth; ++layerIndex) {
+            for (int x = 0; x < sourceImage.getWidth(); ++x) {
+                for (int y = 0; y < sourceImage.getHeight(); ++y) {
+                    sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y);
+                    targetIO.read(targetImage, layerIndex, targetPixel, x, y);
+                    targetPixel.mix(sourcePixel);
+                    targetIO.write(targetImage, layerIndex, targetPixel, x, y);
+                }
+            }
+        }
+    }
+
+    /**
+     * Resizes the image to the given width and height.
+     * @param source
+     *            the source image (this remains untouched, the new image instance is created)
+     * @param width
+     *            the target image width
+     * @param height
+     *            the target image height
+     * @return the resized image
+     */
+    public static Image resizeTo(Image source, int width, int height) {
+        BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
+
+        double scaleX = width / (double) sourceImage.getWidth();
+        double scaleY = height / (double) sourceImage.getHeight();
+        AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
+        AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);
+
+        BufferedImage scaledImage = bilinearScaleOp.filter(sourceImage, new BufferedImage(width, height, sourceImage.getType()));
+        return ImageUtils.toJmeImage(scaledImage, source.getFormat());
+    }
+
+    /**
+     * This method converts the given texture into normal-map texture.
+     * 
+     * @param source
+     *            the source texture
+     * @param strengthFactor
+     *            the normal strength factor
+     * @return normal-map texture
+     */
+    public static Image convertToNormalMapTexture(Image source, float strengthFactor) {
+        BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
+
+        BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+        BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+        ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
+        gscale.filter(sourceImage, heightMap);
+
+        Vector3f S = new Vector3f();
+        Vector3f T = new Vector3f();
+        Vector3f N = new Vector3f();
+
+        for (int x = 0; x < bumpMap.getWidth(); ++x) {
+            for (int y = 0; y < bumpMap.getHeight(); ++y) {
+                // generating bump pixel
+                S.x = 1;
+                S.y = 0;
+                S.z = strengthFactor * ImageUtils.getHeight(heightMap, x + 1, y) - strengthFactor * ImageUtils.getHeight(heightMap, x - 1, y);
+                T.x = 0;
+                T.y = 1;
+                T.z = strengthFactor * ImageUtils.getHeight(heightMap, x, y + 1) - strengthFactor * ImageUtils.getHeight(heightMap, x, y - 1);
+
+                float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1);
+                N.x = -S.z;
+                N.y = -T.z;
+                N.z = 1;
+                N.divideLocal(den);
+
+                // setting thge pixel in the result image
+                bumpMap.setRGB(x, y, ImageUtils.vectorToColor(N.x, N.y, N.z));
+            }
+        }
+        return ImageUtils.toJmeImage(bumpMap, source.getFormat());
+    }
+
+    /**
+     * This method converts the given texture into black and whit (grayscale) texture.
+     * 
+     * @param source
+     *            the source texture
+     * @return grayscale texture
+     */
+    public static Image convertToGrayscaleTexture(Image source) {
+        BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
+        ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
+        op.filter(sourceImage, sourceImage);
+        return ImageUtils.toJmeImage(sourceImage, source.getFormat());
+    }
+
+    /**
+     * This method decompresses the given image. If the given image is already
+     * decompressed nothing happens and it is simply returned.
+     * 
+     * @param image
+     *            the image to decompress
+     * @return the decompressed image
+     */
+    public static Image decompress(Image image) {
+        Format format = image.getFormat();
+        int depth = image.getDepth();
+        if (depth == 0) {
+            depth = 1;
+        }
+        ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
+        int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1];
+        int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null;
+
+        for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
+            ByteBuffer data = image.getData(dataLayerIndex);
+            data.rewind();
+            if (sizes.length == 1) {
+                sizes[0] = data.remaining();
+            }
+            float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap
+            List<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(sizes.length);
+            int maxPosition = 0, resultSize = 0;
+
+            for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) {
+                maxPosition += sizes[sizeIndex];
+                DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format);
+                texelDataList.add(texelData);
+                switch (format) {
+                    case DXT1:// BC1
+                    case DXT1A:
+                        while (data.position() < maxPosition) {
+                            TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
+                            short c0 = data.getShort();
+                            short c1 = data.getShort();
+                            int col0 = RGB565.RGB565_to_ARGB8(c0);
+                            int col1 = RGB565.RGB565_to_ARGB8(c1);
+                            colors[0].fromARGB8(col0);
+                            colors[1].fromARGB8(col1);
+
+                            if (col0 > col1) {
+                                // creating color2 = 2/3color0 + 1/3color1
+                                colors[2].fromPixel(colors[0]);
+                                colors[2].mult(2);
+                                colors[2].add(colors[1]);
+                                colors[2].divide(3);
+
+                                // creating color3 = 1/3color0 + 2/3color1;
+                                colors[3].fromPixel(colors[1]);
+                                colors[3].mult(2);
+                                colors[3].add(colors[0]);
+                                colors[3].divide(3);
+                            } else {
+                                // creating color2 = 1/2color0 + 1/2color1
+                                colors[2].fromPixel(colors[0]);
+                                colors[2].add(colors[1]);
+                                colors[2].mult(0.5f);
+
+                                colors[3].fromARGB8(0);
+                            }
+                            int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
+                            texelData.add(colors, indexes);
+                        }
+                        break;
+                    case DXT3:// BC2
+                        while (data.position() < maxPosition) {
+                            TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
+                            long alpha = data.getLong();
+                            float[] alphas = new float[16];
+                            long alphasIndex = 0;
+                            for (int i = 0; i < 16; ++i) {
+                                alphasIndex |= i << i * 4;
+                                byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4);
+                                alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f;
+                            }
+
+                            short c0 = data.getShort();
+                            short c1 = data.getShort();
+                            int col0 = RGB565.RGB565_to_ARGB8(c0);
+                            int col1 = RGB565.RGB565_to_ARGB8(c1);
+                            colors[0].fromARGB8(col0);
+                            colors[1].fromARGB8(col1);
+
+                            // creating color2 = 2/3color0 + 1/3color1
+                            colors[2].fromPixel(colors[0]);
+                            colors[2].mult(2);
+                            colors[2].add(colors[1]);
+                            colors[2].divide(3);
+
+                            // creating color3 = 1/3color0 + 2/3color1;
+                            colors[3].fromPixel(colors[1]);
+                            colors[3].mult(2);
+                            colors[3].add(colors[0]);
+                            colors[3].divide(3);
+
+                            int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
+                            texelData.add(colors, indexes, alphas, alphasIndex);
+                        }
+                        break;
+                    case DXT5:// BC3
+                        float[] alphas = new float[8];
+                        while (data.position() < maxPosition) {
+                            TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
+                            alphas[0] = data.get() * 255.0f;
+                            alphas[1] = data.get() * 255.0f;
+                            long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40;
+                            if (alphas[0] > alphas[1]) {// 6 interpolated alpha values.
+                                alphas[2] = (6 * alphas[0] + alphas[1]) / 7;
+                                alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7;
+                                alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7;
+                                alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7;
+                                alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7;
+                                alphas[7] = (alphas[0] + 6 * alphas[1]) / 7;
+                            } else {
+                                alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f;
+                                alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f;
+                                alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f;
+                                alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f;
+                                alphas[6] = 0;
+                                alphas[7] = 1;
+                            }
+
+                            short c0 = data.getShort();
+                            short c1 = data.getShort();
+                            int col0 = RGB565.RGB565_to_ARGB8(c0);
+                            int col1 = RGB565.RGB565_to_ARGB8(c1);
+                            colors[0].fromARGB8(col0);
+                            colors[1].fromARGB8(col1);
+
+                            // creating color2 = 2/3color0 + 1/3color1
+                            colors[2].fromPixel(colors[0]);
+                            colors[2].mult(2);
+                            colors[2].add(colors[1]);
+                            colors[2].divide(3);
+
+                            // creating color3 = 1/3color0 + 2/3color1;
+                            colors[3].fromPixel(colors[1]);
+                            colors[3].mult(2);
+                            colors[3].add(colors[0]);
+                            colors[3].divide(3);
+
+                            int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
+                            texelData.add(colors, indexes, alphas, alphaIndices);
+                        }
+                        break;
+                    default:
+                        throw new IllegalStateException("Unknown compressed format: " + format);
+                }
+                newMipmapSizes[sizeIndex] = texelData.getSizeInBytes();
+                resultSize += texelData.getSizeInBytes();
+            }
+            byte[] bytes = new byte[resultSize];
+            int offset = 0;
+            byte[] pixelBytes = new byte[4];
+            for (DDSTexelData texelData : texelDataList) {
+                for (int i = 0; i < texelData.getPixelWidth(); ++i) {
+                    for (int j = 0; j < texelData.getPixelHeight(); ++j) {
+                        if (texelData.getRGBA8(i, j, pixelBytes)) {
+                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0];
+                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1];
+                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2];
+                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3];
+                        } else {
+                            break;
+                        }
+                    }
+                }
+                offset += texelData.getSizeInBytes();
+            }
+            dataArray.add(BufferUtils.createByteBuffer(bytes));
+        }
+
+        Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
+        if (newMipmapSizes != null) {
+            result.setMipMapSizes(newMipmapSizes);
+        }
+        return result;
+    }
+
+    /**
+     * This method returns the height represented by the specified pixel in the
+     * given texture. The given texture should be a height-map.
+     * 
+     * @param image
+     *            the height-map texture
+     * @param x
+     *            pixel's X coordinate
+     * @param y
+     *            pixel's Y coordinate
+     * @return height reprezented by the given texture in the specified location
+     */
+    private static int getHeight(BufferedImage image, int x, int y) {
+        if (x < 0) {
+            x = 0;
+        } else if (x >= image.getWidth()) {
+            x = image.getWidth() - 1;
+        }
+        if (y < 0) {
+            y = 0;
+        } else if (y >= image.getHeight()) {
+            y = image.getHeight() - 1;
+        }
+        return image.getRGB(x, y) & 0xff;
+    }
+
+    /**
+     * This method transforms given vector's coordinates into ARGB color (A is
+     * always = 255).
+     * 
+     * @param x
+     *            X factor of the vector
+     * @param y
+     *            Y factor of the vector
+     * @param z
+     *            Z factor of the vector
+     * @return color representation of the given vector
+     */
+    private static int vectorToColor(float x, float y, float z) {
+        int r = Math.round(255 * (x + 1f) / 2f);
+        int g = Math.round(255 * (y + 1f) / 2f);
+        int b = Math.round(255 * (z + 1f) / 2f);
+        return (255 << 24) + (r << 16) + (g << 8) + b;
+    }
+
+    /**
+     * Converts java awt image to jme image.
+     * @param bufferedImage
+     *            the java awt image
+     * @param format
+     *            the result image format
+     * @return the jme image
+     */
+    private static Image toJmeImage(BufferedImage bufferedImage, Format format) {
+        ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3);
+        ImageToAwt.convert(bufferedImage, format, byteBuffer);
+        return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer);
+    }
+}

+ 96 - 284
engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java

@@ -31,21 +31,17 @@
  */
 package com.jme3.scene.plugins.blender.textures;
 
-import com.jme3.asset.AssetInfo;
-
-import java.awt.color.ColorSpace;
 import java.awt.geom.AffineTransform;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorConvertOp;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import jme3tools.converters.ImageToAwt;
-import jme3tools.converters.RGB565;
-
+import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetNotFoundException;
 import com.jme3.asset.BlenderKey;
@@ -53,20 +49,23 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad;
 import com.jme3.asset.GeneratedTextureKey;
 import com.jme3.asset.TextureKey;
 import com.jme3.math.Vector2f;
-import com.jme3.math.Vector3f;
 import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
 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.textures.UVCoordinatesGenerator.UVCoordinatesType;
+import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
+import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
 import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory;
 import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
 import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
 import com.jme3.texture.Image;
-import com.jme3.texture.Image.Format;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.MinFilter;
 import com.jme3.texture.Texture.WrapMode;
@@ -205,280 +204,6 @@ public class TextureHelper extends AbstractBlenderHelper {
         return result;
     }
 
-    /**
-     * This method converts the given texture into normal-map texture.
-     * 
-     * @param source
-     *            the source texture
-     * @param strengthFactor
-     *            the normal strength factor
-     * @return normal-map texture
-     */
-    public Image convertToNormalMapTexture(Image source, float strengthFactor) {
-        BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
-
-        BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
-        BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
-        ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
-        gscale.filter(sourceImage, heightMap);
-
-        Vector3f S = new Vector3f();
-        Vector3f T = new Vector3f();
-        Vector3f N = new Vector3f();
-
-        for (int x = 0; x < bumpMap.getWidth(); ++x) {
-            for (int y = 0; y < bumpMap.getHeight(); ++y) {
-                // generating bump pixel
-                S.x = 1;
-                S.y = 0;
-                S.z = strengthFactor * this.getHeight(heightMap, x + 1, y) - strengthFactor * this.getHeight(heightMap, x - 1, y);
-                T.x = 0;
-                T.y = 1;
-                T.z = strengthFactor * this.getHeight(heightMap, x, y + 1) - strengthFactor * this.getHeight(heightMap, x, y - 1);
-
-                float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1);
-                N.x = -S.z;
-                N.y = -T.z;
-                N.z = 1;
-                N.divideLocal(den);
-
-                // setting thge pixel in the result image
-                bumpMap.setRGB(x, y, this.vectorToColor(N.x, N.y, N.z));
-            }
-        }
-        ByteBuffer byteBuffer = BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 3);
-        ImageToAwt.convert(bumpMap, Format.RGB8, byteBuffer);
-        return new Image(Format.RGB8, source.getWidth(), source.getHeight(), byteBuffer);
-    }
-
-    /**
-     * This method decompresses the given image. If the given image is already
-     * decompressed nothing happens and it is simply returned.
-     * 
-     * @param image
-     *            the image to decompress
-     * @return the decompressed image
-     */
-    public Image decompress(Image image) {
-        Format format = image.getFormat();
-        int depth = image.getDepth();
-        if (depth == 0) {
-            depth = 1;
-        }
-        ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);
-        int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1];
-        int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null;
-
-        for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
-            ByteBuffer data = image.getData(dataLayerIndex);
-            data.rewind();
-            if (sizes.length == 1) {
-                sizes[0] = data.remaining();
-            }
-            float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap
-            List<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(sizes.length);
-            int maxPosition = 0, resultSize = 0;
-
-            for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) {
-                maxPosition += sizes[sizeIndex];
-                DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format);
-                texelDataList.add(texelData);
-                switch (format) {
-                    case DXT1:// BC1
-                    case DXT1A:
-                        while (data.position() < maxPosition) {
-                            TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
-                            short c0 = data.getShort();
-                            short c1 = data.getShort();
-                            int col0 = RGB565.RGB565_to_ARGB8(c0);
-                            int col1 = RGB565.RGB565_to_ARGB8(c1);
-                            colors[0].fromARGB8(col0);
-                            colors[1].fromARGB8(col1);
-
-                            if (col0 > col1) {
-                                // creating color2 = 2/3color0 + 1/3color1
-                                colors[2].fromPixel(colors[0]);
-                                colors[2].mult(2);
-                                colors[2].add(colors[1]);
-                                colors[2].divide(3);
-
-                                // creating color3 = 1/3color0 + 2/3color1;
-                                colors[3].fromPixel(colors[1]);
-                                colors[3].mult(2);
-                                colors[3].add(colors[0]);
-                                colors[3].divide(3);
-                            } else {
-                                // creating color2 = 1/2color0 + 1/2color1
-                                colors[2].fromPixel(colors[0]);
-                                colors[2].add(colors[1]);
-                                colors[2].mult(0.5f);
-
-                                colors[3].fromARGB8(0);
-                            }
-                            int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
-                            texelData.add(colors, indexes);
-                        }
-                        break;
-                    case DXT3:// BC2
-                        while (data.position() < maxPosition) {
-                            TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
-                            long alpha = data.getLong();
-                            float[] alphas = new float[16];
-                            long alphasIndex = 0;
-                            for (int i = 0; i < 16; ++i) {
-                                alphasIndex |= i << i * 4;
-                                byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4);
-                                alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f;
-                            }
-
-                            short c0 = data.getShort();
-                            short c1 = data.getShort();
-                            int col0 = RGB565.RGB565_to_ARGB8(c0);
-                            int col1 = RGB565.RGB565_to_ARGB8(c1);
-                            colors[0].fromARGB8(col0);
-                            colors[1].fromARGB8(col1);
-
-                            // creating color2 = 2/3color0 + 1/3color1
-                            colors[2].fromPixel(colors[0]);
-                            colors[2].mult(2);
-                            colors[2].add(colors[1]);
-                            colors[2].divide(3);
-
-                            // creating color3 = 1/3color0 + 2/3color1;
-                            colors[3].fromPixel(colors[1]);
-                            colors[3].mult(2);
-                            colors[3].add(colors[0]);
-                            colors[3].divide(3);
-
-                            int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
-                            texelData.add(colors, indexes, alphas, alphasIndex);
-                        }
-                        break;
-                    case DXT5:// BC3
-                        float[] alphas = new float[8];
-                        while (data.position() < maxPosition) {
-                            TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() };
-                            alphas[0] = data.get() * 255.0f;
-                            alphas[1] = data.get() * 255.0f;
-                            long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40;
-                            if (alphas[0] > alphas[1]) {// 6 interpolated alpha values.
-                                alphas[2] = (6 * alphas[0] + alphas[1]) / 7;
-                                alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7;
-                                alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7;
-                                alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7;
-                                alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7;
-                                alphas[7] = (alphas[0] + 6 * alphas[1]) / 7;
-                            } else {
-                                alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f;
-                                alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f;
-                                alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f;
-                                alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f;
-                                alphas[6] = 0;
-                                alphas[7] = 1;
-                            }
-
-                            short c0 = data.getShort();
-                            short c1 = data.getShort();
-                            int col0 = RGB565.RGB565_to_ARGB8(c0);
-                            int col1 = RGB565.RGB565_to_ARGB8(c1);
-                            colors[0].fromARGB8(col0);
-                            colors[1].fromARGB8(col1);
-
-                            // creating color2 = 2/3color0 + 1/3color1
-                            colors[2].fromPixel(colors[0]);
-                            colors[2].mult(2);
-                            colors[2].add(colors[1]);
-                            colors[2].divide(3);
-
-                            // creating color3 = 1/3color0 + 2/3color1;
-                            colors[3].fromPixel(colors[1]);
-                            colors[3].mult(2);
-                            colors[3].add(colors[0]);
-                            colors[3].divide(3);
-
-                            int indexes = data.getInt();// 4-byte table with color indexes in decompressed table
-                            texelData.add(colors, indexes, alphas, alphaIndices);
-                        }
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown compressed format: " + format);
-                }
-                newMipmapSizes[sizeIndex] = texelData.getSizeInBytes();
-                resultSize += texelData.getSizeInBytes();
-            }
-            byte[] bytes = new byte[resultSize];
-            int offset = 0;
-            byte[] pixelBytes = new byte[4];
-            for (DDSTexelData texelData : texelDataList) {
-                for (int i = 0; i < texelData.getPixelWidth(); ++i) {
-                    for (int j = 0; j < texelData.getPixelHeight(); ++j) {
-                        if (texelData.getRGBA8(i, j, pixelBytes)) {
-                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0];
-                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1];
-                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2];
-                            bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3];
-                        } else {
-                            break;
-                        }
-                    }
-                }
-                offset += texelData.getSizeInBytes();
-            }
-            dataArray.add(BufferUtils.createByteBuffer(bytes));
-        }
-
-        Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0));
-        if (newMipmapSizes != null) {
-            result.setMipMapSizes(newMipmapSizes);
-        }
-        return result;
-    }
-
-    /**
-     * This method returns the height represented by the specified pixel in the
-     * given texture. The given texture should be a height-map.
-     * 
-     * @param image
-     *            the height-map texture
-     * @param x
-     *            pixel's X coordinate
-     * @param y
-     *            pixel's Y coordinate
-     * @return height reprezented by the given texture in the specified location
-     */
-    protected int getHeight(BufferedImage image, int x, int y) {
-        if (x < 0) {
-            x = 0;
-        } else if (x >= image.getWidth()) {
-            x = image.getWidth() - 1;
-        }
-        if (y < 0) {
-            y = 0;
-        } else if (y >= image.getHeight()) {
-            y = image.getHeight() - 1;
-        }
-        return image.getRGB(x, y) & 0xff;
-    }
-
-    /**
-     * This method transforms given vector's coordinates into ARGB color (A is
-     * always = 255).
-     * 
-     * @param x
-     *            X factor of the vector
-     * @param y
-     *            Y factor of the vector
-     * @param z
-     *            Z factor of the vector
-     * @return color representation of the given vector
-     */
-    protected int vectorToColor(float x, float y, float z) {
-        int r = Math.round(255 * (x + 1f) / 2f);
-        int g = Math.round(255 * (y + 1f) / 2f);
-        int b = Math.round(255 * (z + 1f) / 2f);
-        return (255 << 24) + (r << 16) + (g << 8) + b;
-    }
-
     /**
      * This class returns a texture read from the file or from packed blender
      * data.
@@ -797,9 +522,96 @@ public class TextureHelper extends AbstractBlenderHelper {
 
         return result;
     }
+    
+    @SuppressWarnings("unchecked")
+    public Map<Number, CombinedTexture> readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException {
+        DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
+        int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue();
+        List<TextureData> texturesList = new ArrayList<TextureData>();
+        for (int i = 0; i < mtexsArray.getTotalSize(); ++i) {
+            Pointer p = mtexsArray.get(i);
+            if (p.isNotNull() && (separatedTextures & 1 << i) == 0) {
+                TextureData textureData = new TextureData();
+                textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0);
+                textureData.uvCoordinatesType = skyTexture ? UVCoordinatesType.TEXCO_ORCO.blenderValue : ((Number) textureData.mtex.getFieldValue("texco")).intValue();
+                textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue();
+                textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString();
+                if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) {
+                    textureData.uvCoordinatesName = null;
+                }
+                
+                Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex");
+                if (pTex.isNotNull()) {
+                    Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0);
+                    textureData.textureStructure = tex;
+                    texturesList.add(textureData);
+                }
+            }
+        }
+
+        // loading the textures and merging them
+        Map<Number, List<TextureData>> textureDataMap = this.sortTextures(texturesList);
+        Map<Number, CombinedTexture> loadedTextures = new HashMap<Number, CombinedTexture>();
+        for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
+            if (entry.getValue().size() > 0) {
+                CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture);
+                for (TextureData textureData : entry.getValue()) {
+                    int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
+                    boolean negateTexture = (texflag & 0x04) != 0;
+                    Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext);
+                    if (texture != null) {
+                        int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue();
+                        float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() };
+                        float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue();
+                        TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac);
+                        combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType,
+                                            textureData.textureStructure, textureData.uvCoordinatesName, blenderContext);
+                    }
+                }
+                if (combinedTexture.getTexturesCount() > 0) {
+                    loadedTextures.put(entry.getKey(), combinedTexture);
+                }
+            }
+        }
+        return loadedTextures;
+    }
+    
+    /**
+     * This method sorts the textures by their mapping type. In each group only
+     * textures of one type are put (either two- or three-dimensional).
+     * 
+     * @return a map with sorted textures
+     */
+    private Map<Number, List<TextureData>> sortTextures(List<TextureData> textures) {
+        int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB };
+        Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
+        for (TextureData data : textures) {
+            Number mapto = (Number) data.mtex.getFieldValue("mapto");
+            for (int i = 0; i < mappings.length; ++i) {
+                if ((mappings[i] & mapto.intValue()) != 0) {
+                    List<TextureData> datas = result.get(mappings[i]);
+                    if (datas == null) {
+                        datas = new ArrayList<TextureData>();
+                        result.put(mappings[i], datas);
+                    }
+                    datas.add(data);
+                }
+            }
+        }
+        return result;
+    }
 
     @Override
     public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
         return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0;
     }
+    
+    public static class TextureData {
+        public Structure mtex;
+        public Structure textureStructure;
+        public int       uvCoordinatesType;
+        public int       projectionType;
+        /** The name of the user's UV coordinates that are used for this texture. */
+        public String    uvCoordinatesName;
+    }
 }

+ 27 - 0
engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java

@@ -275,6 +275,20 @@ public class TexturePixel implements Cloneable {
         this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue;
         this.alpha = (this.alpha + pixel.alpha) * 0.5f;
     }
+    
+    /**
+     * Mixes two pixels.
+     * 
+     * @param pixel
+     *            the pixel we mix with
+     */
+    public void mix(TexturePixel pixel) {
+        this.red = 0.5f * (this.red + pixel.red);
+        this.green = 0.5f * (this.green + pixel.green);
+        this.blue = 0.5f * (this.blue + pixel.blue);
+        this.alpha = 0.5f * (this.alpha + pixel.alpha);
+        this.intensity = 0.5f * (this.intensity + pixel.intensity);
+    }
 
     /**
      * This method negates the colors.
@@ -306,6 +320,19 @@ public class TexturePixel implements Cloneable {
         this.alpha += pixel.alpha;
         this.intensity += pixel.intensity;
     }
+    
+    /**
+     * This method adds the calues of the given pixel to the current pixel.
+     * 
+     * @param pixel
+     *            the pixel we add
+     */
+    public void add(ColorRGBA pixel) {
+        this.red += pixel.r;
+        this.green += pixel.g;
+        this.blue += pixel.b;
+        this.alpha += pixel.a;
+    }
 
     /**
      * This method multiplies the values of the given pixel by the given value.

+ 0 - 27
engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java

@@ -168,33 +168,6 @@ import com.jme3.util.BufferUtils;
         }
     }
 
-    /**
-     * This method merges the current texture with the given one. The given
-     * texture is not changed.
-     * 
-     * @param triangulatedTexture
-     *            the texture we merge current texture with
-     */
-    public void merge(TriangulatedTexture triangulatedTexture) {
-        TexturePixel sourcePixel = new TexturePixel();
-        TexturePixel targetPixel = new TexturePixel();
-        for (TriangleTextureElement triangleTextureElement : this.faceTextures) {
-            Image sourceImage = triangulatedTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image;
-            Image targetImage = triangleTextureElement.image;
-            PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
-            PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
-
-            for (int x = 0; x < sourceImage.getWidth(); ++x) {
-                for (int y = 0; y < sourceImage.getHeight(); ++y) {
-                    sourceIO.read(sourceImage, 0, sourcePixel, x, y);
-                    targetIO.read(targetImage, 0, targetPixel, x, y);
-                    targetPixel.merge(sourcePixel);
-                    targetIO.write(targetImage, 0, targetPixel, x, y);
-                }
-            }
-        }
-    }
-
     /**
      * This method returns the flat texture. It is calculated if required or if
      * it was not created before. Images that are identical are discarded to