Ver Fonte

Feature: new triangulation modifier.

jmekaelthas há 11 anos atrás
pai
commit
6e21b0527c
37 ficheiros alterados com 2788 adições e 2320 exclusões
  1. 25 1
      jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java
  2. 1 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java
  3. 13 57
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java
  4. 4 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java
  5. 4 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  6. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  7. 5 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
  8. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  9. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java
  10. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  11. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java
  12. 3 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java
  13. 2 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  14. 5 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java
  15. 222 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java
  16. 533 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java
  17. 258 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java
  18. 364 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java
  19. 0 185
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java
  20. 197 131
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java
  21. 88 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java
  22. 599 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java
  23. 0 586
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java
  24. 0 160
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java
  25. 0 161
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java
  26. 0 171
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java
  27. 27 313
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  28. 109 128
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java
  29. 71 195
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java
  30. 22 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java
  31. 47 34
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java
  32. 54 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java
  33. 68 55
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java
  34. 9 8
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  35. 4 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java
  36. 7 5
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java
  37. 37 97
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java

+ 25 - 1
jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java

@@ -124,7 +124,9 @@ public class BlenderKey extends ModelKey {
     protected boolean                  optimiseTextures;
     /** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
     protected AnimationMatchMethod     animationMatchMethod      = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
-
+    /** The size of points that are loaded and do not belong to any edge of the mesh. */
+    protected float                    pointsSize                = 1;
+    
     /**
      * Constructor used by serialization mechanisms.
      */
@@ -456,6 +458,22 @@ public class BlenderKey extends ModelKey {
     public AnimationMatchMethod getAnimationMatchMethod() {
         return animationMatchMethod;
     }
+    
+    /**
+     * @return the size of points that are loaded and do not belong to any edge of the mesh
+     */
+    public float getPointsSize() {
+        return pointsSize;
+    }
+
+    /**
+     * Sets the size of points that are loaded and do not belong to any edge of the mesh.
+     * @param pointsSize
+     *            The size of points that are loaded and do not belong to any edge of the mesh
+     */
+    public void setPointsSize(float pointsSize) {
+        this.pointsSize = pointsSize;
+    }
 
     /**
      * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
@@ -513,6 +531,7 @@ public class BlenderKey extends ModelKey {
         oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
         oc.write(optimiseTextures, "optimise-textures", false);
         oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
+        oc.write(pointsSize, "points-size", 1);
     }
 
     @Override
@@ -535,6 +554,7 @@ public class BlenderKey extends ModelKey {
         skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
         optimiseTextures = ic.readBoolean("optimise-textures", false);
         animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
+        pointsSize = ic.readFloat("points-size", 1);
     }
 
     @Override
@@ -560,6 +580,7 @@ public class BlenderKey extends ModelKey {
         result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
         result = prime * result + skyGeneratedTextureSize;
         result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
+        result = prime * result + (int)pointsSize;
         return result;
     }
 
@@ -638,6 +659,9 @@ public class BlenderKey extends ModelKey {
         } else if (!usedWorld.equals(other.usedWorld)) {
             return false;
         }
+        if (pointsSize != other.pointsSize) {
+            return false;
+        }
         return true;
     }
 

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

@@ -110,7 +110,7 @@ public abstract class AbstractBlenderHelper {
      * @param properties
      *            the properties to be applied
      */
-    protected void applyProperties(Spatial spatial, Properties properties) {
+    public void applyProperties(Spatial spatial, Properties properties) {
         List<String> propertyNames = properties.getSubPropertiesNames();
         if (propertyNames != null && propertyNames.size() > 0) {
             for (String propertyName : propertyNames) {

+ 13 - 57
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -54,7 +54,6 @@ import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.DnaBlockData;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.blender.meshes.MeshContext;
 
 /**
  * The class that stores temporary data and manages it during loading the belnd
@@ -89,14 +88,7 @@ public class BlenderContext {
      * first object in the value table is the loaded structure and the second -
      * the structure already converted into proper data.
      */
-    private Map<Long, Object[]>                 loadedFeatures         = new HashMap<Long, Object[]>();
-    /**
-     * This map stores the loaded features by their name. Only features with ID
-     * structure can be stored here. The first object in the value table is the
-     * loaded structure and the second - the structure already converted into
-     * proper data.
-     */
-    private Map<String, Object[]>               loadedFeaturesByName   = new HashMap<String, Object[]>();
+    private Map<Long, Map<LoadedDataType, Object>>                 loadedFeatures         = new HashMap<Long, Map<LoadedDataType, Object>>();
     /** A stack that hold the parent structure of currently loaded feature. */
     private Stack<Structure>                    parentStack            = new Stack<Structure>();
     /** A list of constraints for the specified object. */
@@ -107,8 +99,6 @@ public class BlenderContext {
     private Map<Long, Skeleton>                 skeletons              = new HashMap<Long, Skeleton>();
     /** A map between skeleton and node it modifies. */
     private Map<Skeleton, Node>                 nodesWithSkeletons     = new HashMap<Skeleton, Node>();
-    /** A map of mesh contexts. */
-    protected Map<Long, MeshContext>            meshContexts           = new HashMap<Long, MeshContext>();
     /** A map of bone contexts. */
     protected Map<Long, BoneContext>            boneContexts           = new HashMap<Long, BoneContext>();
     /** A map og helpers that perform loading. */
@@ -304,15 +294,16 @@ public class BlenderContext {
      * @param feature
      *            the feature we want to store
      */
-    public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) {
-        if (oldMemoryAddress == null || structure == null || feature == null) {
+    public void addLoadedFeatures(Long oldMemoryAddress, LoadedDataType featureDataType, Object feature) {
+        if (oldMemoryAddress == null || featureDataType == null || feature == null) {
             throw new IllegalArgumentException("One of the given arguments is null!");
         }
-        Object[] storedData = new Object[] { structure, feature };
-        loadedFeatures.put(oldMemoryAddress, storedData);
-        if (featureName != null) {
-            loadedFeaturesByName.put(featureName, storedData);
+        Map<LoadedDataType, Object> map = loadedFeatures.get(oldMemoryAddress);
+        if(map == null) {
+            map = new HashMap<BlenderContext.LoadedDataType, Object>();
+            loadedFeatures.put(oldMemoryAddress, map);
         }
+        map.put(featureDataType, feature);
     }
 
     /**
@@ -326,10 +317,10 @@ public class BlenderContext {
      *            structure or already converted feature
      * @return loaded feature or null if it was not yet loaded
      */
-    public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) {
-        Object[] result = loadedFeatures.get(oldMemoryAddress);
+    public Object getLoadedFeature(Long oldMemoryAddress, LoadedDataType loadedFeatureDataType) {
+        Map<LoadedDataType, Object> result = loadedFeatures.get(oldMemoryAddress);
         if (result != null) {
-            return result[loadedFeatureDataType.getIndex()];
+            return result.get(loadedFeatureDataType);
         }
         return null;
     }
@@ -484,31 +475,6 @@ public class BlenderContext {
         return skeletons.get(skeletonOMA);
     }
 
-    /**
-     * This method sets the mesh context for the given mesh old memory address.
-     * If the context is already set it will be replaced.
-     * 
-     * @param meshOMA
-     *            the mesh's old memory address
-     * @param meshContext
-     *            the mesh's context
-     */
-    public void setMeshContext(Long meshOMA, MeshContext meshContext) {
-        meshContexts.put(meshOMA, meshContext);
-    }
-
-    /**
-     * This method returns the mesh context for the given mesh old memory
-     * address. If no context exists then <b>null</b> is returned.
-     * 
-     * @param meshOMA
-     *            the mesh's old memory address
-     * @return mesh's context
-     */
-    public MeshContext getMeshContext(Long meshOMA) {
-        return meshContexts.get(meshOMA);
-    }
-
     /**
      * This method sets the bone context for the given bone old memory address.
      * If the context is already set it will be replaced.
@@ -645,17 +611,7 @@ public class BlenderContext {
      * 
      * @author Marcin Roguski (Kaelthas)
      */
-    public static enum LoadedFeatureDataType {
-
-        LOADED_STRUCTURE(0), LOADED_FEATURE(1);
-        private int index;
-
-        private LoadedFeatureDataType(int index) {
-            this.index = index;
-        }
-
-        public int getIndex() {
-            return index;
-        }
+    public static enum LoadedDataType {
+        STRUCTURE, FEATURE, TEMPORAL_MESH;
     }
 }

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

@@ -19,6 +19,7 @@ import com.jme3.asset.BlenderKey.AnimationMatchMethod;
 import com.jme3.scene.Node;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
 import com.jme3.scene.plugins.blender.curves.BezierCurve;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
@@ -166,7 +167,9 @@ public class AnimationHelper extends AbstractBlenderHelper {
             }
             curves.clear();
             result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
-            blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result);
+            Long ipoOma = ipoStructure.getOldMemoryAddress();
+            blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.STRUCTURE, ipoStructure);
+            blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.FEATURE, result);
         }
         return result;
     }

+ 4 - 3
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java

@@ -10,7 +10,7 @@ import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
@@ -144,10 +144,11 @@ public class BoneContext {
         Long boneOMA = boneStructure.getOldMemoryAddress();
         bone = new Bone(boneName);
         bones.add(bone);
-        blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
+        blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.STRUCTURE, boneStructure);
+        blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.FEATURE, bone);
         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
 
-        Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedFeatureDataType.LOADED_STRUCTURE);
+        Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedDataType.STRUCTURE);
         // I could load 'imat' here, but apparently in some older blenders there were bugs or unfinished functionalities that stored ZERO matrix in imat field
         // loading 'obmat' and inverting it makes us avoid errors in such cases
         Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "obmat", blenderContext.getBlenderKey().isFixUpAxis()).invertLocal();

+ 2 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java

@@ -5,7 +5,7 @@ import java.util.logging.Logger;
 
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.animations.Ipo;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
@@ -41,7 +41,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
     @Override
     public boolean validate() {
         if (targetOMA != null) {
-            Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
+            Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
             if (nodeTarget == null) {
                 LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name);
                 return false;

+ 5 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java

@@ -15,7 +15,7 @@ import com.jme3.math.Vector3f;
 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.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.AnimationHelper;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.animations.Ipo;
@@ -185,7 +185,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
                     BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
                     simulationRootNodes.add(new SimulationNode(boneContext.getArmatureObjectOMA(), blenderContext));
                 } else if (constraint instanceof SpatialConstraint) {
-                    Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
+                    Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE);
                     while (spatial.getParent() != null) {
                         spatial = spatial.getParent();
                     }
@@ -213,7 +213,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
      * @return thensform of a feature in a given space
      */
     public Transform getTransform(Long oma, String subtargetName, Space space) {
-        Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
+        Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
         boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
         if (isArmature) {
             blenderContext.getSkeleton(oma).updateWorldVectors();
@@ -228,7 +228,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
             Transform result;
             switch (space) {
                 case CONSTRAINT_SPACE_WORLD:
-                    Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedFeatureDataType.LOADED_FEATURE);
+                    Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedDataType.FEATURE);
                     Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4);
                     Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42);
                     Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix);
@@ -295,7 +295,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
      *            the transform we apply
      */
     public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
-        Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
+        Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
         boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
         if (isArmature) {
             Skeleton skeleton = blenderContext.getSkeleton(oma);

+ 2 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java

@@ -24,7 +24,7 @@ import com.jme3.math.Vector3f;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
 import com.jme3.util.TempVars;
@@ -93,7 +93,7 @@ public class SimulationNode {
      */
     private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
         this.blenderContext = blenderContext;
-        Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE);
+        Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedDataType.FEATURE);
         if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) {
             skeleton = blenderContext.getSkeleton(featureOMA);
 

+ 2 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java

@@ -1,7 +1,7 @@
 package com.jme3.scene.plugins.blender.constraints;
 
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.Ipo;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
@@ -20,7 +20,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
     @Override
     public boolean validate() {
         if (targetOMA != null) {
-            return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null;
+            return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null;
         }
         return true;
     }

+ 2 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java

@@ -5,7 +5,7 @@ import java.util.Set;
 import com.jme3.animation.Bone;
 import com.jme3.math.Transform;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
@@ -61,7 +61,7 @@ public abstract class ConstraintDefinition {
      */
     protected Object getOwner() {
         if (ownerOMA != null && owner == null) {
-            owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
+            owner = blenderContext.getLoadedFeature(ownerOMA, LoadedDataType.FEATURE);
             if (owner == null) {
                 throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName());
             }

+ 2 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java

@@ -5,7 +5,7 @@ import com.jme3.animation.Skeleton;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Transform;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
@@ -58,7 +58,7 @@ public class ConstraintDefinitionTransLike extends ConstraintDefinition {
      * @return the target feature; it is either Node or Bone (vertex group subtarger is not yet supported)
      */
     private Object getTarget() {
-        Object target = blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
+        Object target = blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
         if (subtargetName != null && blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, target) != null) {
             Skeleton skeleton = blenderContext.getSkeleton(targetOMA);
             target = skeleton.getBone(subtargetName);

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

@@ -43,7 +43,7 @@ import com.jme3.math.FastMath;
 import com.jme3.scene.LightNode;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
 
@@ -68,7 +68,7 @@ public class LightHelper extends AbstractBlenderHelper {
     }
 
     public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
-        LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+        LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
@@ -111,7 +111,7 @@ public class LightHelper extends AbstractBlenderHelper {
             float g = ((Number) structure.getFieldValue("g")).floatValue();
             float b = ((Number) structure.getFieldValue("b")).floatValue();
             light.setColor(new ColorRGBA(r, g, b, 1.0f));
-            result = new LightNode(null, light);
+            result = new LightNode(structure.getName(), light);
         }
         return result;
     }

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

@@ -1,7 +1,7 @@
 package com.jme3.scene.plugins.blender.materials;
 
-import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -119,7 +119,7 @@ public final class MaterialContext {
      * @param blenderContext
      *            the blender context
      */
-    public void applyMaterial(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
+    public void applyMaterial(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
         Material material = null;
         if (shadeless) {
             material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");

+ 5 - 3
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java

@@ -44,7 +44,7 @@ import com.jme3.math.ColorRGBA;
 import com.jme3.math.FastMath;
 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.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
@@ -162,14 +162,16 @@ public class MaterialHelper extends AbstractBlenderHelper {
      */
     public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
         LOGGER.log(Level.FINE, "Loading material.");
-        MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+        MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
 
         result = new MaterialContext(structure, blenderContext);
         LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
-        blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result);
+        Long oma = structure.getOldMemoryAddress();
+        blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, structure);
+        blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
         return result;
     }
 

+ 222 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java

@@ -0,0 +1,222 @@
+package com.jme3.scene.plugins.blender.meshes;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Line;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+
+/**
+ * A class that represents a single edge between two vertices.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class Edge extends Line {
+    private static final long   serialVersionUID = 7172714692126675311L;
+
+    private static final Logger LOGGER           = Logger.getLogger(Edge.class.getName());
+
+    /** The vertices indexes. */
+    private int                 index1, index2;
+
+    public Edge() {
+    }
+
+    /**
+     * This constructor only stores the indexes of the vertices. The position vertices should be stored
+     * outside this class.
+     * @param index1
+     *            the first index of the edge
+     * @param index2
+     *            the second index of the edge
+     */
+    private Edge(int index1, int index2) {
+        this.index1 = index1;
+        this.index2 = index2;
+    }
+
+    /**
+     * This constructor stores both indexes and vertices list. The list should contain ALL verts and not
+     * only those belonging to the edge.
+     * @param index1
+     *            the first index of the edge
+     * @param index2
+     *            the second index of the edge
+     * @param vertices
+     *            the vertices of the mesh
+     */
+    public Edge(int index1, int index2, List<Vector3f> vertices) {
+        this(index1, index2);
+        this.set(vertices.get(index1), vertices.get(index2));
+    }
+
+    @Override
+    public Edge clone() {
+        Edge result = new Edge(index1, index2);
+        result.setOrigin(this.getOrigin());
+        result.setDirection(this.getDirection());
+        return result;
+    }
+
+    /**
+     * @return the first index of the edge
+     */
+    public int getFirstIndex() {
+        return index1;
+    }
+
+    /**
+     * @return the second index of the edge
+     */
+    public int getSecondIndex() {
+        return index2;
+    }
+
+    /**
+     * Shifts indexes by a given amount.
+     * @param shift
+     *            how much the indexes should be shifted
+     */
+    public void shiftIndexes(int shift) {
+        index1 += shift;
+        index2 += shift;
+    }
+
+    /**
+     * Flips the order of the indexes.
+     */
+    public void flipIndexes() {
+        int temp = index1;
+        index1 = index2;
+        index2 = temp;
+    }
+
+    /**
+     * The method sets the vertices for the first and second index.
+     * @param v1
+     *            the first vertex
+     * @param v2
+     *            the second vertex
+     */
+    public void set(Vector3f v1, Vector3f v2) {
+        this.setOrigin(v1);
+        this.setDirection(v2.subtract(v1));
+    }
+
+    /**
+     * The crossing method first computes the points on both lines (that contain the edges)
+     * who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON
+     * the we consider them to be the same point (the lines cross).
+     * The second step is to check if both points are contained within the edges.
+     * 
+     * The method of computing the crossing point is as follows:
+     * Let's assume that:
+     * (P0, P1) are the points of the first edge
+     * (Q0, Q1) are the points of the second edge
+     * 
+     * u = P1 - P0
+     * v = Q1 - Q0
+     * 
+     * This gives us the equations of two lines:
+     * L1: (x = P1x + ux*t1; y = P1y + uy*t1; z = P1z + uz*t1)
+     * L2: (x = P2x + vx*t2; y = P2y + vy*t2; z = P2z + vz*t2)
+     * 
+     * Comparing the x and y of the first two equations for each line will allow us to compute t1 and t2
+     * (which is implemented below).
+     * Using t1 and t2 we can compute (x, y, z) of each line and that will give us two points that we need to compare.
+     * 
+     * @param edge
+     *            the edge we check against crossing
+     * @return <b>true</b> if the edges cross and false otherwise
+     */
+    public boolean cross(Edge edge) {
+        Vector3f P1 = this.getOrigin(), P2 = edge.getOrigin();
+        Vector3f u = this.getDirection();
+        Vector3f v = edge.getDirection();
+        float t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
+        float t1 = (P2.x - P1.x + v.x * t2) / u.x;
+        Vector3f p1 = P1.add(u.mult(t1));
+        Vector3f p2 = P2.add(v.mult(t2));
+
+        if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
+            // the lines cross, check if p1 and p2 are within the edges
+            Vector3f p = p1.subtract(P1);
+            float cos = p.dot(u) / (p.length() * u.length());
+            if (cos > 0 && p.length() <= u.length()) {
+                // p1 is inside the first edge, lets check the other edge now
+                p = p2.subtract(P2);
+                cos = p.dot(v) / (p.length() * v.length());
+                return cos > 0 && p.length() <= u.length();
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        String result = "Edge [" + index1 + ", " + index2 + "]";
+        if (this.getOrigin() != null && this.getDirection() != null) {
+            result += " -> {" + this.getOrigin() + ", " + this.getOrigin().add(this.getDirection()) + "}";
+        }
+        return result;
+    }
+
+    @Override
+    public int hashCode() {
+        // The hash code must be identical for the same two indexes, no matter their order.
+        final int prime = 31;
+        int result = 1;
+        int lowerIndex = Math.min(index1, index2);
+        int higherIndex = Math.max(index1, index2);
+        result = prime * result + lowerIndex;
+        result = prime * result + higherIndex;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Edge)) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
+        Edge other = (Edge) obj;
+        return Math.min(index1, index2) == Math.min(other.index1, other.index2) && Math.max(index1, index2) == Math.max(other.index1, other.index2);
+    }
+
+    /**
+     * The method loads all edges from the given mesh structure that does not belong to any face.
+     * @param meshStructure
+     *            the mesh structure
+     * @return all edges without faces
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with file reading occur
+     */
+    public static List<Edge> loadAll(Structure meshStructure) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName());
+        List<Edge> result = new ArrayList<Edge>();
+
+        Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
+
+        if (pMEdge.isNotNull()) {
+            List<Structure> edges = pMEdge.fetchData();
+            for (Structure edge : edges) {
+                int flag = ((Number) edge.getFieldValue("flag")).intValue();
+                if ((flag & MeshHelper.EDGE_NOT_IN_FACE_FLAG) != 0) {
+                    int v1 = ((Number) edge.getFieldValue("v1")).intValue();
+                    int v2 = ((Number) edge.getFieldValue("v2")).intValue();
+                    result.add(new Edge(v1, v2));
+                }
+            }
+        }
+        LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size());
+        return result;
+    }
+}

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

@@ -0,0 +1,533 @@
+package com.jme3.scene.plugins.blender.meshes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+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 com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+
+/**
+ * A class that represents a single face in the mesh. The face is a polygon. Its minimum count of
+ * vertices is = 3.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class Face implements Comparator<Integer> {
+    private static final Logger         LOGGER = Logger.getLogger(Face.class.getName());
+
+    /** The indexes loop of the face. */
+    private IndexesLoop                 indexes;
+    /** Indicates if the face is smooth or solid. */
+    private boolean                     smooth;
+    /** The material index of the face. */
+    private int                         materialNumber;
+    /** UV coordinate sets attached to the face. The key is the set name and value are the UV coords. */
+    private Map<String, List<Vector2f>> faceUVCoords;
+    /** The vertex colors of the face. */
+    private List<byte[]>                vertexColors;
+    /** The temporal mesh the face belongs to. */
+    private TemporalMesh                temporalMesh;
+
+    /**
+     * Creates a complete face with all available data.
+     * @param indexes
+     *            the indexes of the face (required)
+     * @param smooth
+     *            indicates if the face is smooth or solid
+     * @param materialNumber
+     *            the material index of the face
+     * @param faceUVCoords
+     *            UV coordinate sets of the face (optional)
+     * @param vertexColors
+     *            the vertex colors of the face (optional)
+     * @param temporalMesh
+     *            the temporal mesh the face belongs to (required)
+     */
+    public Face(Integer[] indexes, boolean smooth, int materialNumber, Map<String, List<Vector2f>> faceUVCoords, List<byte[]> vertexColors, TemporalMesh temporalMesh) {
+        this.setTemporalMesh(temporalMesh);
+        this.indexes = new IndexesLoop(indexes);
+        this.smooth = smooth;
+        this.materialNumber = materialNumber;
+        this.faceUVCoords = faceUVCoords;
+        this.temporalMesh = temporalMesh;
+        this.vertexColors = vertexColors;
+    }
+
+    /**
+     * Default constructor. Used by the clone method.
+     */
+    private Face() {
+    }
+
+    @Override
+    public Face clone() {
+        Face result = new Face();
+        result.indexes = indexes.clone();
+        result.smooth = smooth;
+        result.materialNumber = materialNumber;
+        if (faceUVCoords != null) {
+            result.faceUVCoords = new HashMap<String, List<Vector2f>>(faceUVCoords.size());
+            for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
+                List<Vector2f> uvs = new ArrayList<Vector2f>(entry.getValue().size());
+                for (Vector2f v : entry.getValue()) {
+                    uvs.add(v.clone());
+                }
+                result.faceUVCoords.put(entry.getKey(), uvs);
+            }
+        }
+        if (vertexColors != null) {
+            result.vertexColors = new ArrayList<byte[]>(vertexColors.size());
+            for (byte[] colors : vertexColors) {
+                result.vertexColors.add(colors.clone());
+            }
+        }
+        result.temporalMesh = temporalMesh;
+        return result;
+    }
+
+    /**
+     * Returns the index at the given position in the index loop. If the given position is negative or exceeds
+     * the amount of vertices - it is being looped properly so that it always hits an index.
+     * For example getIndex(-1) will return the index before the 0 - in this case it will be the last one.
+     * @param indexPosition
+     *            the index position
+     * @return index value at the given position
+     */
+    private Integer getIndex(int indexPosition) {
+        if (indexPosition >= indexes.size()) {
+            indexPosition = indexPosition % indexes.size();
+        } else if (indexPosition < 0) {
+            indexPosition = indexes.size() - -indexPosition % indexes.size();
+        }
+        return indexes.get(indexPosition);
+    }
+
+    /**
+     * @return all indexes
+     */
+    public List<Integer> getIndexes() {
+        return indexes.getAll();
+    }
+
+    /**
+     * The method detaches the triangle from the face. This method keeps the indexes loop normalized - every index
+     * has only two neighbours. So if detaching the triangle causes a vertex to have more than two neighbours - it is
+     * also detached and returned as a result.
+     * The result is an empty list if no such situation happens.
+     * @param triangleIndexes
+     *            the indexes of a triangle to be detached
+     * @return a list of faces that need to be detached as well in order to keep them normalized
+     */
+    private List<Face> detachTriangle(Integer[] triangleIndexes) {
+        LOGGER.fine("Detaching triangle.");
+        if (triangleIndexes.length != 3) {
+            throw new IllegalArgumentException("Cannot detach triangle with that does not have 3 indexes!");
+        }
+        List<Face> detachedFaces = new ArrayList<Face>();
+
+        boolean[] edgeRemoved = new boolean[] { indexes.removeEdge(triangleIndexes[0], triangleIndexes[1]), indexes.removeEdge(triangleIndexes[0], triangleIndexes[2]), indexes.removeEdge(triangleIndexes[1], triangleIndexes[2]) };
+        Integer[][] indexesPairs = new Integer[][] { new Integer[] { triangleIndexes[0], triangleIndexes[1] }, new Integer[] { triangleIndexes[0], triangleIndexes[2] }, new Integer[] { triangleIndexes[1], triangleIndexes[2] } };
+
+        for (int i = 0; i < 3; ++i) {
+            if (!edgeRemoved[i]) {
+                List<Integer> path = indexes.findPath(indexesPairs[i][0], indexesPairs[i][1]);
+                if (path == null) {
+                    path = indexes.findPath(indexesPairs[i][1], indexesPairs[i][0]);
+                }
+                if (path == null) {
+                    throw new IllegalStateException("Triangulation failed. Cannot find path between two indexes. Please apply triangulation in Blender as a workaround.");
+                }
+                if (detachedFaces.size() == 0 && path.size() < indexes.size()) {
+                    detachedFaces.add(new Face(path.toArray(new Integer[path.size()]), smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh));
+                    for (int j = 0; j < path.size() - 1; ++j) {
+                        indexes.removeEdge(path.get(j), path.get(j + 1));
+                    }
+                    indexes.removeEdge(path.get(path.size() - 1), path.get(0));
+                } else {
+                    indexes.addEdge(path.get(path.size() - 1), path.get(0));
+                }
+            }
+        }
+
+        return detachedFaces;
+    }
+
+    /**
+     * The method returns the position of the given index in the indexes loop.
+     * @param index
+     *            the index whose position will be queried
+     * @return position of the given index or -1 if such index is not in the index loop
+     */
+    private int indexOf(Integer index) {
+        return indexes.indexOf(index);
+    }
+
+    /**
+     * The method shifts all indexes by a given value.
+     * @param shift
+     *            the value to shift all indexes
+     */
+    public void shiftIndexes(int shift) {
+        indexes.shiftIndexes(shift);
+    }
+
+    /**
+     * Sets the temporal mesh for the face. The given mesh cannot be null.
+     * @param temporalMesh
+     *            the temporal mesh of the face
+     * @throws IllegalArgumentException
+     *             thrown if given temporal mesh is null
+     */
+    public void setTemporalMesh(TemporalMesh temporalMesh) {
+        if (temporalMesh == null) {
+            throw new IllegalArgumentException("No temporal mesh for the face given!");
+        }
+        this.temporalMesh = temporalMesh;
+    }
+
+    /**
+     * Flips the order of the indexes.
+     */
+    public void flipIndexes() {
+        indexes.reverse();
+        if (faceUVCoords != null) {
+            for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
+                Collections.reverse(entry.getValue());
+            }
+        }
+    }
+
+    /**
+     * Flips UV coordinates.
+     * @param u
+     *            indicates if U coords should be flipped
+     * @param v
+     *            indicates if V coords should be flipped
+     */
+    public void flipUV(boolean u, boolean v) {
+        if (faceUVCoords != null) {
+            for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
+                for (Vector2f uv : entry.getValue()) {
+                    uv.set(u ? 1 - uv.x : uv.x, v ? 1 - uv.y : uv.y);
+                }
+            }
+        }
+    }
+
+    /**
+     * @return the UV sets of the face
+     */
+    public Map<String, List<Vector2f>> getUvSets() {
+        return faceUVCoords;
+    }
+
+    /**
+     * @return current vertex count of the face
+     */
+    public int vertexCount() {
+        return indexes.size();
+    }
+
+    /**
+     * The method triangulates the face.
+     * @param vertices
+     *            the vertices of the mesh (all verts and not only those belonging to the face)
+     * @param normals
+     *            the normals of the mesh (all normals and not only those belonging to the face)
+     * @return a list of faces that are triangles
+     */
+    public List<Face> triangulate(List<Vector3f> vertices, List<Vector3f> normals) {
+        LOGGER.fine("Triangulating face.");
+        assert indexes.size() >= 3 : "Invalid indexes amount for face. 3 is the required minimum!";
+        List<Face> result = new ArrayList<Face>();
+
+        List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone()));
+        while (facesToTriangulate.size() > 0) {
+            Face face = facesToTriangulate.remove(0);
+            int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
+            while (face.vertexCount() > 0) {
+                int index1 = face.getIndex(0);
+                int index2 = face.findClosestVertex(index1, -1);
+                int index3 = face.findClosestVertex(index1, index2);
+                
+                LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
+                if(index1 < 0 || index2 < 0 || index3 < 0) {
+                    throw new IllegalStateException("Unable to find two closest vertices while triangulating face in mesh: " + temporalMesh +
+                            "Please apply triangulation modifier in blender as a workaround and load again!");
+                }
+                if(previousIndex1 == index1 && previousIndex2 == index2 && previousIndex3 == index3) {
+                    throw new IllegalStateException("Infinite loop detected during triangulation of mesh: " + temporalMesh +
+                            "Please apply triangulation modifier in blender as a workaround and load again!");
+                }
+                previousIndex1 = index1;
+                previousIndex2 = index2;
+                previousIndex3 = index3;
+                
+                Integer[] indexes = new Integer[] { index1, index2, index3 };
+                Arrays.sort(indexes, this);
+
+                List<Face> detachedFaces = face.detachTriangle(indexes);
+                facesToTriangulate.addAll(detachedFaces);
+
+                int indexOf0 = this.indexOf(indexes[0]);
+                int indexOf1 = this.indexOf(indexes[1]);
+                int indexOf2 = this.indexOf(indexes[2]);
+
+                Map<String, List<Vector2f>> faceUVS = new HashMap<String, List<Vector2f>>();
+                for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
+                    List<Vector2f> uvs = new ArrayList<Vector2f>(3);
+                    uvs.add(entry.getValue().get(indexOf0));
+                    uvs.add(entry.getValue().get(indexOf1));
+                    uvs.add(entry.getValue().get(indexOf2));
+                    faceUVS.put(entry.getKey(), uvs);
+                }
+
+                List<byte[]> vertexColors = null;
+                if (this.vertexColors != null) {
+                    vertexColors = new ArrayList<byte[]>(3);
+                    vertexColors.add(this.vertexColors.get(indexOf0));
+                    vertexColors.add(this.vertexColors.get(indexOf1));
+                    vertexColors.add(this.vertexColors.get(indexOf2));
+                }
+
+                result.add(new Face(indexes, smooth, materialNumber, faceUVS, vertexColors, temporalMesh));
+            }
+        }
+        LOGGER.log(Level.FINE, "Face triangulated on {0} faces.", result.size());
+        return result;
+    }
+
+    /**
+     * @return <b>true</b> if the face is smooth and <b>false</b> otherwise
+     */
+    public boolean isSmooth() {
+        return smooth;
+    }
+
+    /**
+     * @return the material index of the face
+     */
+    public int getMaterialNumber() {
+        return materialNumber;
+    }
+
+    /**
+     * @return the vertices colord of the face
+     */
+    public List<byte[]> getVertexColors() {
+        return vertexColors;
+    }
+
+    @Override
+    public String toString() {
+        return "Face " + indexes;
+    }
+
+    /**
+     * The method finds the closest vertex to the one specified by <b>index</b>.
+     * If the vertexToIgnore is positive than it will be ignored in the result.
+     * The closes vertex must be able to create an edge that is fully contained within the face and does not cross
+     * any other edges.
+     * @param index
+     *            the index of the vertex that needs to have found the nearest neighbour
+     * @param indexToIgnore
+     *            the index to ignore in the result (pass -1 if none is to be ignored)
+     * @return the index of the closest vertex to the given one
+     */
+    private int findClosestVertex(int index, int indexToIgnore) {
+        int result = -1;
+        List<Vector3f> vertices = temporalMesh.getVertices();
+        Vector3f v1 = vertices.get(index);
+        float distance = Float.MAX_VALUE;
+        for (int i : indexes) {
+            if (i != index && i != indexToIgnore) {
+                Vector3f v2 = vertices.get(i);
+                float d = v2.distance(v1);
+                if (d < distance && this.contains(new Edge(index, i, vertices))) {
+                    result = i;
+                    distance = d;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * The method verifies if the edge is contained within the face.
+     * It means it cannot cross any other edge and it must be inside the face and not outside of it.
+     * @param edge
+     *            the edge to be checked
+     * @return <b>true</b> if the given edge is contained within the face and <b>false</b> otherwise
+     */
+    private boolean contains(Edge edge) {
+        int index1 = edge.getFirstIndex();
+        int index2 = edge.getSecondIndex();
+        // check if the line between the vertices is not a border edge of the face
+        if (!indexes.areNeighbours(index1, index2)) {
+            List<Vector3f> vertices = temporalMesh.getVertices();
+
+            Edge e2 = new Edge();
+            for (int i = 0; i < indexes.size(); ++i) {
+                int i1 = this.getIndex(i);
+                int i2 = this.getIndex(i + 1);
+                // check if the edges have no common verts (because if they do, they cannot cross)
+                if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) {
+                    e2.set(vertices.get(i1), vertices.get(i2));
+                    if (edge.cross(e2)) {
+                        return false;
+                    }
+                }
+            }
+
+            // the edge does NOT cross any of other edges, so now we need to verify if it is inside the face or outside
+            // we check it by comparing the angle that is created by vertices: [index1 - 1, index1, index1 + 1]
+            // with the one creaded by vertices: [index1 - 1, index1, index2]
+            // if the latter is greater than it means that the edge is outside the face
+            // IMPORTANT: we assume that all vertices are in one plane (this should be ensured before creating the Face)
+            int indexOfIndex1 = this.indexOf(index1);
+            int indexMinus1 = this.getIndex(indexOfIndex1 - 1);// indexOfIndex1 == 0 ? indexes.get(indexes.size() - 1) : indexes.get(indexOfIndex1 - 1);
+            int indexPlus1 = this.getIndex(indexOfIndex1 + 1);// indexOfIndex1 == indexes.size() - 1 ? 0 : indexes.get(indexOfIndex1 + 1);
+
+            Vector3f edge1 = vertices.get(indexMinus1).subtract(vertices.get(index1)).normalizeLocal();
+            Vector3f edge2 = vertices.get(indexPlus1).subtract(vertices.get(index1)).normalizeLocal();
+            Vector3f newEdge = vertices.get(index2).subtract(vertices.get(index1)).normalizeLocal();
+
+            // verify f the later computed angle is inside or outside the face
+            Vector3f direction1 = edge1.cross(edge2).normalizeLocal();
+            Vector3f direction2 = edge1.cross(newEdge).normalizeLocal();
+            Vector3f normal = temporalMesh.getNormals().get(index1);
+
+            boolean isAngle1Interior = normal.dot(direction1) < 0;
+            boolean isAngle2Interior = normal.dot(direction2) < 0;
+
+            float angle1 = isAngle1Interior ? edge1.angleBetween(edge2) : FastMath.TWO_PI - edge1.angleBetween(edge2);
+            float angle2 = isAngle2Interior ? edge1.angleBetween(newEdge) : FastMath.TWO_PI - edge1.angleBetween(newEdge);
+
+            return angle1 >= angle2;
+        }
+        return true;
+    }
+
+    /**
+     * Loads all faces of a given mesh.
+     * @param meshStructure
+     *            the mesh structure we read the faces from
+     * @param userUVGroups
+     *            UV groups defined by the user
+     * @param verticesColors
+     *            the vertices colors of the mesh
+     * @param temporalMesh
+     *            the temporal mesh the faces will belong to
+     * @param blenderContext
+     *            the blender context
+     * @return list of faces read from the given mesh structure
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with file reading occur
+     */
+    public static List<Face> loadAll(Structure meshStructure, Map<String, List<Vector2f>> userUVGroups, List<byte[]> verticesColors, TemporalMesh temporalMesh, BlenderContext blenderContext) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading all faces from mesh: {0}", meshStructure.getName());
+        List<Face> result = new ArrayList<Face>();
+        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+        if (meshHelper.isBMeshCompatible(meshStructure)) {
+            LOGGER.fine("Reading BMesh.");
+            Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
+            Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
+
+            if (pMPoly.isNotNull() && pMLoop.isNotNull()) {
+                List<Structure> polys = pMPoly.fetchData();
+                List<Structure> loops = pMLoop.fetchData();
+                for (Structure poly : polys) {
+                    int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
+                    int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
+                    int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
+                    boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
+                    Integer[] vertexIndexes = new Integer[totLoop];
+
+                    for (int i = loopStart; i < loopStart + totLoop; ++i) {
+                        vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
+                    }
+
+                    // uvs always must be added wheater we have texture or not
+                    Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>();
+                    for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) {
+                        List<Vector2f> uvs = entry.getValue().subList(loopStart, loopStart + totLoop);
+                        uvCoords.put(entry.getKey(), new ArrayList<Vector2f>(uvs));
+                    }
+
+                    List<byte[]> vertexColors = null;
+                    if (verticesColors != null && verticesColors.size() > 0) {
+                        vertexColors = new ArrayList<byte[]>(totLoop);
+                        for (int i = loopStart; i < loopStart + totLoop; ++i) {
+                            vertexColors.add(verticesColors.get(i));
+                        }
+                    }
+
+                    result.add(new Face(vertexIndexes, smooth, materialNumber, uvCoords, vertexColors, temporalMesh));
+                }
+            }
+        } else {
+            LOGGER.fine("Reading traditional faces.");
+            Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
+            List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null;
+            if (mFaces != null && mFaces.size() > 0) {
+                // indicates if the material with the specified number should have a texture attached
+                for (int i = 0; i < mFaces.size(); ++i) {
+                    Structure mFace = mFaces.get(i);
+                    int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
+                    boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
+
+                    int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
+                    int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
+                    int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
+                    int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
+
+                    int vertCount = v4 == 0 ? 3 : 4;
+
+                    // uvs always must be added wheater we have texture or not
+                    Map<String, List<Vector2f>> faceUVCoords = new HashMap<String, List<Vector2f>>();
+                    for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) {
+                        List<Vector2f> uvCoordsForASingleFace = new ArrayList<Vector2f>(vertCount);
+                        for (int j = 0; j < vertCount; ++j) {
+                            uvCoordsForASingleFace.add(entry.getValue().get(i * 4 + j));
+                        }
+                        faceUVCoords.put(entry.getKey(), uvCoordsForASingleFace);
+                    }
+
+                    List<byte[]> vertexColors = null;
+                    if (verticesColors != null && verticesColors.size() > 0) {
+                        vertexColors = new ArrayList<byte[]>(vertCount);
+
+                        vertexColors.add(verticesColors.get(v1));
+                        vertexColors.add(verticesColors.get(v2));
+                        vertexColors.add(verticesColors.get(v3));
+                        if (vertCount == 4) {
+                            vertexColors.add(verticesColors.get(v4));
+                        }
+                    }
+
+                    result.add(new Face(vertCount == 4 ? new Integer[] { v1, v2, v3, v4 } : new Integer[] { v1, v2, v3 }, smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh));
+                }
+            }
+        }
+        LOGGER.log(Level.FINE, "Loaded {0} faces.", result.size());
+        return result;
+    }
+
+    @Override
+    public int compare(Integer index1, Integer index2) {
+        return this.indexOf(index1) - this.indexOf(index2);
+    }
+}

+ 258 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java

@@ -0,0 +1,258 @@
+package com.jme3.scene.plugins.blender.meshes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * This class represents the Face's indexes loop. It is a simplified implementation of directed graph.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
+    /** The indexes. */
+    private List<Integer>               nodes;
+    /** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */
+    private Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
+
+    /**
+     * The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge.
+     * @param nodes
+     *            the nodes for the loop
+     */
+    public IndexesLoop(Integer[] nodes) {
+        this.nodes = new ArrayList<Integer>(Arrays.asList(nodes));
+        this.prepareEdges(this.nodes);
+    }
+
+    @Override
+    public IndexesLoop clone() {
+        return new IndexesLoop(nodes.toArray(new Integer[nodes.size()]));
+    }
+
+    /**
+     * The method prepares edges for the given indexes.
+     * @param nodes
+     *            the indexes
+     */
+    private void prepareEdges(List<Integer> nodes) {
+        for (int i = 0; i < nodes.size() - 1; ++i) {
+            if (edges.containsKey(nodes.get(i))) {
+                edges.get(nodes.get(i)).add(nodes.get(i + 1));
+            } else {
+                edges.put(nodes.get(i), new ArrayList<Integer>(Arrays.asList(nodes.get(i + 1))));
+            }
+        }
+        edges.put(nodes.get(nodes.size() - 1), new ArrayList<Integer>(Arrays.asList(nodes.get(0))));
+    }
+
+    /**
+     * @return the count of indexes
+     */
+    public int size() {
+        return nodes.size();
+    }
+
+    /**
+     * Adds edge to the loop.
+     * @param from
+     *            the start index
+     * @param to
+     *            the end index
+     */
+    public void addEdge(Integer from, Integer to) {
+        if (nodes.contains(from) && nodes.contains(to)) {
+            if (edges.containsKey(from) && !edges.get(from).contains(to)) {
+                edges.get(from).add(to);
+            }
+        }
+    }
+
+    /**
+     * Removes edge from the face. The edge is removed if it already exists in the face.
+     * @param node1
+     *            the first index of the edge to be removed
+     * @param node2
+     *            the second index of the edge to be removed
+     * @return <b>true</b> if the edge was removed and <b>false</b> otherwise
+     */
+    public boolean removeEdge(Integer node1, Integer node2) {
+        boolean edgeRemoved = false;
+        if (nodes.contains(node1) && nodes.contains(node2)) {
+            if (edges.containsKey(node1)) {
+                edgeRemoved |= edges.get(node1).remove(node2);
+            }
+            if (edges.containsKey(node2)) {
+                edgeRemoved |= edges.get(node2).remove(node1);
+            }
+            if (edgeRemoved) {
+                if (this.getNeighbourCount(node1) == 0) {
+                    this.removeIndexes(node1);
+                }
+                if (this.getNeighbourCount(node2) == 0) {
+                    this.removeIndexes(node2);
+                }
+            }
+        }
+        return edgeRemoved;
+    }
+
+    /**
+     * Tells if the given indexes are neighbours.
+     * @param index1
+     *            the first index
+     * @param index2
+     *            the second index
+     * @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise
+     */
+    public boolean areNeighbours(Integer index1, Integer index2) {
+        if (index1.equals(index2)) {
+            return false;
+        }
+        return edges.get(index1).contains(index2) || edges.get(index2).contains(index1);
+    }
+
+    /**
+     * The method shifts all indexes by a given value.
+     * @param shift
+     *            the value to shift all indexes
+     */
+    public void shiftIndexes(int shift) {
+        List<Integer> nodes = new ArrayList<Integer>(this.nodes.size());
+        for (Integer node : this.nodes) {
+            nodes.add(node + shift);
+        }
+
+        Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
+        for (Entry<Integer, List<Integer>> entry : this.edges.entrySet()) {
+            List<Integer> neighbours = new ArrayList<Integer>(entry.getValue().size());
+            for (Integer neighbour : entry.getValue()) {
+                neighbours.add(neighbour + shift);
+            }
+            edges.put(entry.getKey() + shift, neighbours);
+        }
+
+        this.nodes = nodes;
+        this.edges = edges;
+    }
+
+    /**
+     * Reverses the order of the indexes.
+     */
+    public void reverse() {
+        Collections.reverse(nodes);
+        edges.clear();
+        this.prepareEdges(nodes);
+    }
+
+    /**
+     * Returns the neighbour count of the given index.
+     * @param index
+     *            the index whose neighbour count will be checked
+     * @return the count of neighbours of the given index
+     */
+    public int getNeighbourCount(Integer index) {
+        int result = 0;
+        if (edges.containsKey(index)) {
+            result = edges.get(index).size();
+            for (List<Integer> neighbours : edges.values()) {
+                if (neighbours.contains(index)) {
+                    ++result;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the position of the given index in the loop.
+     * @param index
+     *            the index of the face
+     * @return the indexe's position in the loop
+     */
+    public int indexOf(Integer index) {
+        return nodes.indexOf(index);
+    }
+
+    /**
+     * Returns the index at the given position.
+     * @param indexPosition
+     *            the position of the index
+     * @return the index at a given position
+     */
+    public Integer get(int indexPosition) {
+        return nodes.get(indexPosition);
+    }
+
+    /**
+     * @return all indexes of the face
+     */
+    public List<Integer> getAll() {
+        return new ArrayList<Integer>(nodes);
+    }
+
+    /**
+     * The method removes all given indexes.
+     * @param indexes
+     *            the indexes to be removed
+     */
+    public void removeIndexes(Integer... indexes) {
+        for (Integer index : indexes) {
+            nodes.remove(index);
+            edges.remove(index);
+            for (List<Integer> neighbours : edges.values()) {
+                neighbours.remove(index);
+            }
+        }
+    }
+
+    /**
+     * The method finds the path between the given indexes.
+     * @param start
+     *            the start index
+     * @param end
+     *            the end index
+     * @return a list containing indexes on the path from start to end (inclusive)
+     * @throws IllegalStateException
+     *             an exception is thrown when the loop is not normalized (at least one
+     *             index has more than 2 neighbours)
+     */
+    public List<Integer> findPath(Integer start, Integer end) {
+        List<Integer> result = new ArrayList<Integer>();
+        Integer node = start;
+        while (!node.equals(end)) {
+            result.add(node);
+            List<Integer> nextSteps = edges.get(node);
+            if (nextSteps.size() == 0) {
+                return null;
+            } else if (nextSteps.size() == 1) {
+                node = nextSteps.get(0);
+            } else {
+                throw new IllegalStateException("Triangulation failed. Face has ambiguous indexes loop. Please triangulate your model in Blender as a workaround.");
+            }
+        }
+        result.add(end);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "IndexesLoop " + nodes.toString();
+    }
+
+    @Override
+    public int compare(Integer i1, Integer i2) {
+        return nodes.indexOf(i1) - nodes.indexOf(i2);
+    }
+
+    @Override
+    public Iterator<Integer> iterator() {
+        return nodes.iterator();
+    }
+}

+ 364 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java

@@ -0,0 +1,364 @@
+package com.jme3.scene.plugins.blender.meshes;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+
+/**
+ * A class that aggregates the mesh data to prepare proper buffers. The buffers refer only to ONE material.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class MeshBuffers {
+    private static final int              MAXIMUM_WEIGHTS_PER_VERTEX = 4;
+
+    /** The material index. */
+    private final int                     materialIndex;
+    /** The vertices. */
+    private List<Vector3f>                verts                      = new ArrayList<Vector3f>();
+    /** The normals. */
+    private List<Vector3f>                normals                    = new ArrayList<Vector3f>();
+    /** The UV coordinate sets. */
+    private Map<String, List<Vector2f>>   uvCoords                   = new HashMap<String, List<Vector2f>>();
+    /** The vertex colors. */
+    private List<byte[]>                  vertColors                 = new ArrayList<byte[]>();
+    /** The indexes. */
+    private List<Integer>                 indexes                    = new ArrayList<Integer>();
+    /** The maximum weights count assigned to a single vertex. Used during weights normalization. */
+    private int                           maximumWeightsPerVertex;
+    /** A list of mapping between weights and indexes. Each entry for the proper vertex. */
+    private List<TreeMap<Float, Integer>> boneWeightAndIndexes       = new ArrayList<TreeMap<Float, Integer>>();
+
+    /**
+     * Constructor stores only the material index value.
+     * @param materialIndex
+     *            the material index
+     */
+    public MeshBuffers(int materialIndex) {
+        this.materialIndex = materialIndex;
+    }
+
+    /**
+     * @return the material index
+     */
+    public int getMaterialIndex() {
+        return materialIndex;
+    }
+
+    /**
+     * @return indexes buffer
+     */
+    public Buffer getIndexBuffer() {
+        if (indexes.size() <= Short.MAX_VALUE) {
+            short[] indices = new short[indexes.size()];
+            for (int i = 0; i < indexes.size(); ++i) {
+                indices[i] = indexes.get(i).shortValue();
+            }
+            return BufferUtils.createShortBuffer(indices);
+        } else {
+            int[] indices = new int[indexes.size()];
+            for (int i = 0; i < indexes.size(); ++i) {
+                indices[i] = indexes.get(i).intValue();
+            }
+            return BufferUtils.createIntBuffer(indices);
+        }
+    }
+
+    /**
+     * @return positions buffer
+     */
+    public VertexBuffer getPositionsBuffer() {
+        VertexBuffer positionBuffer = new VertexBuffer(Type.Position);
+        Vector3f[] data = verts.toArray(new Vector3f[verts.size()]);
+        positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data));
+        return positionBuffer;
+    }
+
+    /**
+     * @return normals buffer
+     */
+    public VertexBuffer getNormalsBuffer() {
+        VertexBuffer positionBuffer = new VertexBuffer(Type.Normal);
+        Vector3f[] data = normals.toArray(new Vector3f[normals.size()]);
+        positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data));
+        return positionBuffer;
+    }
+
+    /**
+     * @return bone buffers
+     */
+    public BoneBuffersData getBoneBuffers() {
+        BoneBuffersData result = null;
+        if (maximumWeightsPerVertex > 0) {
+            this.normalizeBoneBuffers(MAXIMUM_WEIGHTS_PER_VERTEX);
+            maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;
+            
+            FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX);
+            ByteBuffer indicesData = BufferUtils.createByteBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX);
+            int index = 0;
+            for (Map<Float, Integer> boneBuffersData : boneWeightAndIndexes) {
+                if (boneBuffersData.size() > 0) {
+                    int count = 0;
+                    for (Entry<Float, Integer> entry : boneBuffersData.entrySet()) {
+                        weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey());
+                        indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue().byteValue());
+                        ++count;
+                    }
+                } else {
+                    // if no bone is assigned to this vertex then attach it to the 0-indexed root bone
+                    weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
+                    indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
+                }
+                ++index;
+            }
+            VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
+            verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
+
+            VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
+            verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
+
+            result = new BoneBuffersData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
+        }
+
+        return result;
+    }
+
+    /**
+     * @return UV coordinates sets
+     */
+    public Map<String, List<Vector2f>> getUvCoords() {
+        return uvCoords;
+    }
+
+    /**
+     * @return <b>true</b> if vertex colors are used and <b>false</b> otherwise
+     */
+    public boolean areVertexColorsUsed() {
+        return vertColors.size() > 0;
+    }
+
+    /**
+     * @return vertex colors buffer
+     */
+    public ByteBuffer getVertexColorsBuffer() {
+        ByteBuffer result = null;
+        if (vertColors.size() > 0) {
+            result = BufferUtils.createByteBuffer(4 * vertColors.size());
+            for (byte[] v : vertColors) {
+                if (v != null) {
+                    result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
+                } else {
+                    result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
+                }
+            }
+            result.flip();
+        }
+        return result;
+    }
+
+    /**
+     * @return <b>true</b> if indexes can be shorts' and <b>false</b> if they need to be ints'
+     */
+    public boolean isShortIndexBuffer() {
+        return indexes.size() <= Short.MAX_VALUE;
+    }
+
+    /**
+     * Appends a vertex and normal to the buffers.
+     * @param vert
+     *            vertex
+     * @param normal
+     *            normal vector
+     */
+    public void append(Vector3f vert, Vector3f normal) {
+        int index = this.indexOf(vert, normal, null);
+        if (index >= 0) {
+            indexes.add(index);
+        } else {
+            indexes.add(verts.size());
+            verts.add(vert);
+            normals.add(normal);
+        }
+    }
+
+    /**
+     * Appends the face data to the buffers.
+     * @param smooth
+     *            tells if the face is smooth or flat
+     * @param verts
+     *            the vertices
+     * @param normals
+     *            the normals
+     * @param uvCoords
+     *            the UV coordinates
+     * @param vertColors
+     *            the vertex colors
+     * @param vertexGroups
+     *            the vertex groups
+     */
+    public void append(boolean smooth, Vector3f[] verts, Vector3f[] normals, Map<String, List<Vector2f>> uvCoords, byte[][] vertColors, List<Map<Float, Integer>> vertexGroups) {
+        if (verts.length != normals.length) {
+            throw new IllegalArgumentException("The amount of verts and normals MUST be equal!");
+        }
+        if (vertColors != null && vertColors.length != verts.length) {
+            throw new IllegalArgumentException("The amount of vertex colors and vertices MUST be equal!");
+        }
+        if (vertexGroups.size() != 0 && vertexGroups.size() != verts.length) {
+            throw new IllegalArgumentException("The amount of (if given) vertex groups and vertices MUST be equal!");
+        }
+
+        if (!smooth) {
+            // make the normals perpendicular to the face
+            normals[0] = normals[1] = normals[2] = FastMath.computeNormal(verts[0], verts[1], verts[2]);
+        }
+
+        for (int i = 0; i < verts.length; ++i) {
+            int index = -1;
+            Map<String, Vector2f> uvCoordsForVertex = this.getUVsForVertex(i, uvCoords);
+            if (smooth && (index = this.indexOf(verts[i], normals[i], uvCoordsForVertex)) >= 0) {
+                indexes.add(index);
+            } else {
+                indexes.add(this.verts.size());
+                this.verts.add(verts[i]);
+                this.normals.add(normals[i]);
+                this.vertColors.add(vertColors[i]);
+
+                if (uvCoords != null && uvCoords.size() > 0) {
+                    for (Entry<String, List<Vector2f>> entry : uvCoords.entrySet()) {
+                        if (this.uvCoords.containsKey(entry.getKey())) {
+                            this.uvCoords.get(entry.getKey()).add(entry.getValue().get(i));
+                        } else {
+                            List<Vector2f> uvs = new ArrayList<Vector2f>();
+                            uvs.add(entry.getValue().get(i));
+                            this.uvCoords.put(entry.getKey(), uvs);
+                        }
+                    }
+                }
+
+                if (vertexGroups != null && vertexGroups.size() > 0) {
+                    Map<Float, Integer> group = vertexGroups.get(i);
+                    maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, group.size());
+                    boneWeightAndIndexes.add(new TreeMap<Float, Integer>(group));
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns UV coordinates assigned for the vertex with the proper index.
+     * @param vertexIndex
+     *            the index of the vertex
+     * @param uvs
+     *            all UV coordinates we search in
+     * @return a set of UV coordinates assigned to the given vertex
+     */
+    private Map<String, Vector2f> getUVsForVertex(int vertexIndex, Map<String, List<Vector2f>> uvs) {
+        if (uvs == null || uvs.size() == 0) {
+            return null;
+        }
+        Map<String, Vector2f> result = new HashMap<String, Vector2f>(uvs.size());
+        for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
+            result.put(entry.getKey(), entry.getValue().get(vertexIndex));
+        }
+        return result;
+    }
+
+    /**
+     * The method returns an index of a vertex described by the given data.
+     * The method tries to find a vertex that mathes the given data. If it does it means
+     * that such vertex is already used.
+     * @param vert
+     *            the vertex position coordinates
+     * @param normal
+     *            the vertex's normal vector
+     * @param uvCoords
+     *            the UV coords of the vertex
+     * @return index of the found vertex of -1
+     */
+    private int indexOf(Vector3f vert, Vector3f normal, Map<String, Vector2f> uvCoords) {
+        for (int i = 0; i < verts.size(); ++i) {
+            if (verts.get(i).equals(vert) && normals.get(i).equals(normal)) {
+                if (uvCoords != null && uvCoords.size() > 0) {
+                    for (Entry<String, Vector2f> entry : uvCoords.entrySet()) {
+                        List<Vector2f> uvs = this.uvCoords.get(entry.getKey());
+                        if (uvs == null) {
+                            return -1;
+                        }
+                        if (!uvs.get(i).equals(entry.getValue())) {
+                            return -1;
+                        }
+                    }
+                }
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * The method normalizes the weights and bone indexes data.
+     * First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
+     * Next it normalizes the weights so that the sum of all verts is 1.
+     * @param maximumSize
+     *            the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
+     */
+    private void normalizeBoneBuffers(int maximumSize) {
+        for (TreeMap<Float, Integer> group : boneWeightAndIndexes) {
+            if (group.size() > maximumSize) {
+                NavigableMap<Float, Integer> descendingWeights = group.descendingMap();
+                while (descendingWeights.size() > maximumSize) {
+                    descendingWeights.pollLastEntry();
+                }
+            }
+
+            // normalizing the weights so that the sum of the values is equal to '1'
+            TreeMap<Float, Integer> normalizedGroup = new TreeMap<Float, Integer>();
+            float sum = 0;
+            for (Entry<Float, Integer> entry : group.entrySet()) {
+                sum += entry.getKey();
+            }
+
+            if (sum != 0 && sum != 1) {
+                for (Entry<Float, Integer> entry : group.entrySet()) {
+                    normalizedGroup.put(entry.getKey() / sum, entry.getValue());
+                }
+                group.clear();
+                group.putAll(normalizedGroup);
+            }
+        }
+    }
+
+    /**
+     * A class that gathers the data for mesh bone buffers.
+     * Added to increase code readability.
+     * 
+     * @author Marcin Roguski (Kaelthas)
+     */
+    public static class BoneBuffersData {
+        public final int          maximumWeightsPerVertex;
+        public final VertexBuffer verticesWeights;
+        public final VertexBuffer verticesWeightsIndices;
+
+        public BoneBuffersData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
+            this.maximumWeightsPerVertex = maximumWeightsPerVertex;
+            this.verticesWeights = verticesWeights;
+            this.verticesWeightsIndices = verticesWeightsIndices;
+        }
+    }
+}

+ 0 - 185
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshContext.java

@@ -1,185 +0,0 @@
-package com.jme3.scene.plugins.blender.meshes;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.jme3.scene.Geometry;
-
-/**
- * Class that holds information about the mesh.
- * 
- * @author Marcin Roguski (Kaelthas)
- */
-public class MeshContext {
-    private static final Logger                       LOGGER       = Logger.getLogger(MeshContext.class.getName());
-
-    /** A map between material index and the geometry. */
-    private Map<Integer, List<Geometry>>              geometries   = new HashMap<Integer, List<Geometry>>();
-    /** The vertex reference map. */
-    private Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap;
-    /**
-     * A vertex group map. The key is the vertex group name and the value is the set of vertex groups.
-     * Linked hash map is used because the insertion order is important.
-     */
-    private LinkedHashMap<String, VertexGroup>        vertexGroups = new LinkedHashMap<String, VertexGroup>();
-
-    /**
-     * Adds a geometry for the specified material index.
-     * @param materialIndex
-     *            the material index
-     * @param geometry
-     *            the geometry
-     */
-    public void putGeometry(Integer materialIndex, Geometry geometry) {
-        List<Geometry> geomList = geometries.get(materialIndex);
-        if (geomList == null) {
-            geomList = new ArrayList<Geometry>();
-            geometries.put(materialIndex, geomList);
-        }
-        geomList.add(geometry);
-    }
-
-    /**
-     * @param materialIndex
-     *            the material index
-     * @return vertices amount that is used by mesh with the specified material
-     */
-    public int getVertexCount(int materialIndex) {
-        int result = 0;
-        for (Geometry geometry : geometries.get(materialIndex)) {
-            result += geometry.getVertexCount();
-        }
-        return result;
-    }
-
-    /**
-     * Returns material index for the geometry.
-     * @param geometry
-     *            the geometry
-     * @return material index
-     * @throws IllegalStateException
-     *             this exception is thrown when no material is found for the specified geometry
-     */
-    public int getMaterialIndex(Geometry geometry) {
-        for (Entry<Integer, List<Geometry>> entry : geometries.entrySet()) {
-            for (Geometry g : entry.getValue()) {
-                if (g.equals(geometry)) {
-                    return entry.getKey();
-                }
-            }
-        }
-        throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry);
-    }
-
-    /**
-     * This method returns the vertex reference map.
-     * 
-     * @return the vertex reference map
-     */
-    public Map<Integer, List<Integer>> getVertexReferenceMap(int materialIndex) {
-        return vertexReferenceMap.get(materialIndex);
-    }
-
-    /**
-     * This method sets the vertex reference map.
-     * 
-     * @param vertexReferenceMap
-     *            the vertex reference map
-     */
-    public void setVertexReferenceMap(Map<Integer, Map<Integer, List<Integer>>> vertexReferenceMap) {
-        this.vertexReferenceMap = vertexReferenceMap;
-    }
-
-    /**
-     * Adds a new empty vertex group to the mesh context.
-     * @param name
-     *            the name of the vertex group
-     */
-    public void addVertexGroup(String name) {
-        if (!vertexGroups.containsKey(name)) {
-            vertexGroups.put(name, new VertexGroup());
-        } else {
-            LOGGER.log(Level.WARNING, "Vertex group already added: {0}", name);
-        }
-    }
-
-    /**
-     * Adds a vertex to the vertex group with specified index (the index is the order of adding a group).
-     * @param vertexIndex
-     *            the vertex index
-     * @param weight
-     *            the vertex weight
-     * @param vertexGroupIndex
-     *            the index of a vertex group
-     */
-    public void addVertexToGroup(int vertexIndex, float weight, int vertexGroupIndex) {
-        if (vertexGroupIndex < 0 || vertexGroupIndex >= vertexGroups.size()) {
-            throw new IllegalArgumentException("Invalid group index: " + vertexGroupIndex);
-        }
-        int counter = 0;
-        for (Entry<String, VertexGroup> vg : vertexGroups.entrySet()) {
-            if (vertexGroupIndex == counter) {
-                vg.getValue().addVertex(vertexIndex, weight);
-                return;
-            }
-            ++counter;
-        }
-    }
-
-    /**
-     * Returns a group with given name of null if such group does not exist.
-     * @param groupName
-     *            the name of a vertex group
-     * @return vertex group with the given name or null
-     */
-    public VertexGroup getGroup(String groupName) {
-        return vertexGroups.get(groupName);
-    }
-
-    /**
-     * A vertex group class that maps vertex index to its weight in a single group.
-     * The group will need to be set a bone index in order to prepare proper buffers for the jme mesh.
-     * But that information is available after the skeleton is loaded.
-     * 
-     * @author Marcin Roguski (Kaelthas)
-     */
-    public static class VertexGroup extends HashMap<Integer, Float> {
-        private static final long serialVersionUID = 5601646768279643957L;
-
-        /** The index of the bone f the vertex group is to be used for attaching vertices to bones. */
-        private int               boneIndex;
-
-        /**
-         * Adds a mapping between vertex index and its weight.
-         * @param index
-         *            the index of the vertex (in JME mesh)
-         * @param weight
-         *            the weight of the vertex
-         */
-        public void addVertex(int index, float weight) {
-            this.put(index, weight);
-        }
-
-        /**
-         * The method sets the bone index for the current vertex group.
-         * @param boneIndex
-         *            the index of the bone
-         */
-        public void setBoneIndex(int boneIndex) {
-            this.boneIndex = boneIndex;
-        }
-
-        /**
-         * @return the index of the bone
-         */
-        public int getBoneIndex() {
-            return boneIndex;
-        }
-    }
-}

+ 197 - 131
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java

@@ -32,10 +32,10 @@
 package com.jme3.scene.plugins.blender.meshes;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -43,25 +43,17 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad;
 import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.Mesh.Mode;
-import com.jme3.scene.VertexBuffer;
-import com.jme3.scene.VertexBuffer.Format;
-import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.math.Vector3f;
 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.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.materials.MaterialContext;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
-import com.jme3.scene.plugins.blender.meshes.builders.MeshBuilder;
 import com.jme3.scene.plugins.blender.objects.Properties;
-import com.jme3.scene.plugins.blender.textures.TextureHelper;
-import com.jme3.util.BufferUtils;
 
 /**
  * A class that is used in mesh calculations.
@@ -75,6 +67,9 @@ public class MeshHelper extends AbstractBlenderHelper {
     public static final int     UV_DATA_LAYER_TYPE_FMESH = 5;
     /** A type of UV data layer in bmesh type. */
     public static final int     UV_DATA_LAYER_TYPE_BMESH = 16;
+    /** The flag mask indicating if the edge belongs to a face or not. */
+    public static final int     EDGE_NOT_IN_FACE_FLAG    = 0x80;
+
     /** A material used for single lines and points. */
     private Material            blackUnshadedMaterial;
 
@@ -92,27 +87,29 @@ public class MeshHelper extends AbstractBlenderHelper {
     }
 
     /**
-     * This method reads converts the given structure into mesh. The given structure needs to be filled with the appropriate data.
+     * Converts the mesh structure into temporal mesh.
+     * The temporal mesh is stored in blender context and here always a clone is being returned because the mesh might
+     * be modified by modifiers.
      * 
      * @param meshStructure
-     *            the structure we read the mesh from
-     * @return the mesh feature
+     *            the mesh structure
+     * @param blenderContext
+     *            the blender context
+     * @return temporal mesh read from the given structure
      * @throws BlenderFileException
+     *             an exception is thrown when problems with reading blend file occur
      */
-    @SuppressWarnings("unchecked")
-    public List<Geometry> toMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
-        List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
-        if (geometries != null) {
-            List<Geometry> copiedGeometries = new ArrayList<Geometry>(geometries.size());
-            for (Geometry geometry : geometries) {
-                copiedGeometries.add(geometry.clone());
-            }
-            return copiedGeometries;
+    public TemporalMesh toTemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading temporal mesh named: {0}.", meshStructure.getName());
+        TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH);
+        if (temporalMesh != null) {
+            LOGGER.fine("The mesh is already loaded. Returning its clone.");
+            return temporalMesh.clone();
         }
 
         String name = meshStructure.getName();
-        MeshContext meshContext = new MeshContext();
         LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
+        temporalMesh = new TemporalMesh(meshStructure, blenderContext);
 
         LOGGER.fine("Loading materials.");
         MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
@@ -120,145 +117,214 @@ public class MeshHelper extends AbstractBlenderHelper {
         if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) {
             materials = materialHelper.getMaterials(meshStructure, blenderContext);
         }
-
-        LOGGER.fine("Reading vertices.");
-        MeshBuilder meshBuilder = new MeshBuilder(meshStructure, materials, blenderContext);
-        if (meshBuilder.isEmpty()) {
-            LOGGER.fine("The geometry is empty.");
-            geometries = new ArrayList<Geometry>(0);
-            blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries);
-            blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext);
-            return geometries;
-        }
-        meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap());
+        temporalMesh.setMaterials(materials);
 
         LOGGER.fine("Reading custom properties.");
         Properties properties = this.loadProperties(meshStructure, blenderContext);
+        temporalMesh.setProperties(properties);
+
+        blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, meshStructure);
+        blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH, temporalMesh);
+        return temporalMesh.clone();
+    }
 
-        LOGGER.fine("Generating meshes.");
-        Map<Integer, List<Mesh>> meshes = meshBuilder.buildMeshes();
-        geometries = new ArrayList<Geometry>(meshes.size());
-        for (Entry<Integer, List<Mesh>> meshEntry : meshes.entrySet()) {
-            int materialIndex = meshEntry.getKey();
-            for (Mesh mesh : meshEntry.getValue()) {
-                LOGGER.fine("Preparing the result part.");
-                Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh);
-                if (properties != null && properties.getValue() != null) {
-                    this.applyProperties(geometry, properties);
+    /**
+     * Tells if the given mesh structure supports BMesh.
+     * 
+     * @param meshStructure
+     *            the mesh structure
+     * @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
+     */
+    public boolean isBMeshCompatible(Structure meshStructure) {
+        Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
+        Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
+        return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
+    }
+
+    /**
+     * This method returns the vertices.
+     * 
+     * @param meshStructure
+     *            the structure containing the mesh data
+     * @return a list of two - element arrays, the first element is the vertex and the second - its normal
+     * @throws BlenderFileException
+     *             this exception is thrown when the blend file structure is somehow invalid or corrupted
+     */
+    @SuppressWarnings("unchecked")
+    public void loadVerticesAndNormals(Structure meshStructure, List<Vector3f> vertices, List<Vector3f> normals) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading vertices and normals from mesh: {0}.", meshStructure.getName());
+        int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
+        if (count > 0) {
+            Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
+            List<Structure> mVerts = pMVert.fetchData();
+            Vector3f co = null, no = null;
+            if (fixUpAxis) {
+                for (int i = 0; i < count; ++i) {
+                    DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
+                    co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
+                    vertices.add(co);
+
+                    DynamicArray<Number> norm = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
+                    no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f, -norm.get(1).shortValue() / 32767.0f);
+                    normals.add(no);
+                }
+            } else {
+                for (int i = 0; i < count; ++i) {
+                    DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
+                    co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
+                    vertices.add(co);
+
+                    DynamicArray<Number> norm = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
+                    no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(1).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f);
+                    normals.add(no);
                 }
-                geometries.add(geometry);
-                meshContext.putGeometry(materialIndex, geometry);
             }
         }
+        LOGGER.log(Level.FINE, "Loaded {0} vertices and normals.", vertices.size());
+    }
+
+    /**
+     * This method returns the vertices colors. Each vertex is stored in byte[4] array.
+     * 
+     * @param meshStructure
+     *            the structure containing the mesh data
+     * @param blenderContext
+     *            the blender context
+     * @return a list of vertices colors, each color belongs to a single vertex or empty list of colors are not specified
+     * @throws BlenderFileException
+     *             this exception is thrown when the blend file structure is somehow invalid or corrupted
+     */
+    public List<byte[]> loadVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading vertices colors from mesh: {0}.", meshStructure.getName());
+        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+        Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
+        List<byte[]> verticesColors = new ArrayList<byte[]>();
+        // it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure)
+        // so we need to put them right
+        boolean useBGRA = blenderContext.getBlenderVersion() < 263;
+        if (pMCol.isNotNull()) {
+            List<Structure> mCol = pMCol.fetchData();
+            for (Structure color : mCol) {
+                byte r = ((Number) color.getFieldValue("r")).byteValue();
+                byte g = ((Number) color.getFieldValue("g")).byteValue();
+                byte b = ((Number) color.getFieldValue("b")).byteValue();
+                byte a = ((Number) color.getFieldValue("a")).byteValue();
+                verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a });
+            }
+        }
+        return verticesColors;
+    }
+
+    /**
+     * The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates.
+     * But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning.
+     * For bmesh they are enlisted just like they are stored in the blend file (in loops).
+     * For traditional faces every 4 UV's should be assigned for a single face.
+     * @param meshStructure
+     *            the mesh structure
+     * @return a map that sorts UV coordinates between different UV sets
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with blend file occur
+     */
+    @SuppressWarnings("unchecked")
+    public LinkedHashMap<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading UV coordinates from mesh: {0}.", meshStructure.getName());
+        LinkedHashMap<String, List<Vector2f>> result = new LinkedHashMap<String, List<Vector2f>>();
+        if (this.isBMeshCompatible(meshStructure)) {
+            // in this case the UV's are assigned to vertices (an array is the same length as the vertex array)
+            Structure loopData = (Structure) meshStructure.getFieldValue("ldata");
+            Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers");
+            List<Structure> loopDataLayers = pLoopDataLayers.fetchData();
+            for (Structure structure : loopDataLayers) {
+                Pointer p = (Pointer) structure.getFieldValue("data");
+                if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) {
+                    String uvSetName = structure.getFieldValue("name").toString();
+                    List<Structure> uvsStructures = p.fetchData();
+                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
+                    for (Structure uvStructure : uvsStructures) {
+                        DynamicArray<Number> loopUVS = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
+                        uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()));
+                    }
+                    result.put(uvSetName, uvs);
+                }
+            }
+        } else {
+            // in this case UV's are assigned to faces (the array has the same legnth as the faces count)
+            Structure facesData = (Structure) meshStructure.getFieldValue("fdata");
+            Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers");
+            if (pFacesDataLayers.isNotNull()) {
+                List<Structure> facesDataLayers = pFacesDataLayers.fetchData();
+                for (Structure structure : facesDataLayers) {
+                    Pointer p = (Pointer) structure.getFieldValue("data");
+                    if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) {
+                        String uvSetName = structure.getFieldValue("name").toString();
+                        List<Structure> uvsStructures = p.fetchData();
+                        List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
+                        for (Structure uvStructure : uvsStructures) {
+                            DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
+                            uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue()));
+                            uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue()));
+                            uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue()));
+                            uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue()));
+                        }
+                        result.put(uvSetName, uvs);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Loads all vertices groups.
+     * @param meshStructure
+     *            the mesh structure
+     * @return a list of vertex groups for every vertex in the mesh
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with blend file occur
+     */
+    public List<Map<String, Float>> loadVerticesGroups(Structure meshStructure) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading vertices groups from mesh: {0}.", meshStructure.getName());
+        List<Map<String, Float>> result = new ArrayList<Map<String, Float>>();
 
-        LOGGER.fine("Reading vertices groups.");// this MUST be done AFTER meshes are built, because otherwise we have no vertex references maps
         Structure parent = blenderContext.peekParent();
         Structure defbase = (Structure) parent.getFieldValue("defbase");
+        List<String> groupNames = new ArrayList<String>();
         List<Structure> defs = defbase.evaluateListBase();
         for (Structure def : defs) {
-            meshContext.addVertexGroup(def.getFieldValue("name").toString());
+            groupNames.add(def.getFieldValue("name").toString());
         }
 
         Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
         if (pDvert.isNotNull()) {// assigning weights and bone indices
             List<Structure> dverts = pDvert.fetchData();
-            int blenderVertexIndex = 0;
             for (Structure dvert : dverts) {
+                Map<String, Float> weightsForVertex = new HashMap<String, Float>();
                 Pointer pDW = (Pointer) dvert.getFieldValue("dw");
                 if (pDW.isNotNull()) {
                     List<Structure> dw = pDW.fetchData();
                     for (Structure deformWeight : dw) {
                         int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
                         float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
+                        String groupName = groupNames.get(groupIndex);
 
-                        // we need to use JME vertex index here and NOT blender vertex index
-                        for (Entry<Integer, Map<Integer, List<Integer>>> vertexReferenceMap : meshBuilder.getVertexReferenceMap().entrySet()) {// iterate through the meshes [key is the material index]
-                            for (Entry<Integer, List<Integer>> vertexEntry : vertexReferenceMap.getValue().entrySet()) {// iterate through the vertex references for the specified material
-                                if (vertexEntry.getKey().intValue() == blenderVertexIndex) {// if the indexes match then ...
-                                    for (Integer jmeVertexIndex : vertexEntry.getValue()) {// ... add all jme vertices to the specified group
-                                        meshContext.addVertexToGroup(jmeVertexIndex, weight, groupIndex);
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                ++blenderVertexIndex;
-            }
-        }
-
-        // store the data in blender context before applying the material
-        blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), meshStructure.getName(), meshStructure, geometries);
-        blenderContext.setMeshContext(meshStructure.getOldMemoryAddress(), meshContext);
-
-        // apply materials only when all geometries are in place
-        if (materials != null) {
-            for (Geometry geometry : geometries) {
-                int materialNumber = meshContext.getMaterialIndex(geometry);
-                if (materialNumber < 0) {
-                    geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext));
-                } else if (materials[materialNumber] != null) {
-                    LinkedHashMap<String, List<Vector2f>> uvCoordinates = meshBuilder.getUVCoordinates(materialNumber);
-                    MaterialContext materialContext = materials[materialNumber];
-                    materialContext.applyMaterial(geometry, meshStructure.getOldMemoryAddress(), uvCoordinates, blenderContext);
-                } else {
-                    geometry.setMaterial(blenderContext.getDefaultMaterial());
-                    LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces.");
-                }
-            }
-        } else {
-            // add UV coordinates if they are defined even if the material is not applied to the model
-            List<VertexBuffer> uvCoordsBuffer = null;
-            if (meshBuilder.hasUVCoordinates()) {
-                Map<String, List<Vector2f>> uvs = meshBuilder.getUVCoordinates(0);
-                if (uvs != null && uvs.size() > 0) {
-                    uvCoordsBuffer = new ArrayList<VertexBuffer>(uvs.size());
-                    int uvIndex = 0;
-                    for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
-                        VertexBuffer buffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[uvIndex++]);
-                        buffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(entry.getValue().toArray(new Vector2f[uvs.size()])));
-                        uvCoordsBuffer.add(buffer);
-                    }
-                }
-            }
-
-            for (Geometry geometry : geometries) {
-                Mode mode = geometry.getMesh().getMode();
-                if (mode != Mode.Triangles && mode != Mode.TriangleFan && mode != Mode.TriangleStrip) {
-                    geometry.setMaterial(this.getBlackUnshadedMaterial(blenderContext));
-                } else {
-                    Material defaultMaterial = blenderContext.getDefaultMaterial();
-                    if (geometry.getMesh().getBuffer(Type.Color) != null) {
-                        defaultMaterial = defaultMaterial.clone();
-                        defaultMaterial.setBoolean("VertexColor", true);
-                    }
-                    geometry.setMaterial(defaultMaterial);
-                }
-                if (uvCoordsBuffer != null) {
-                    for (VertexBuffer buffer : uvCoordsBuffer) {
-                        geometry.getMesh().setBuffer(buffer);
+                        weightsForVertex.put(groupName, weight);
                     }
                 }
+                result.add(weightsForVertex);
             }
         }
-
-        return geometries;
+        return result;
     }
 
     /**
-     * Tells if the given mesh structure supports BMesh.
-     * 
-     * @param meshStructure
-     *            the mesh structure
-     * @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
+     * Returns the black unshaded material. It is used for lines and points because that is how blender
+     * renders it.
+     * @param blenderContext
+     *            the blender context
+     * @return black unshaded material
      */
-    public boolean isBMeshCompatible(Structure meshStructure) {
-        Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
-        Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
-        return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
-    }
-
-    private synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) {
+    public synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) {
         if (blackUnshadedMaterial == null) {
             blackUnshadedMaterial = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
             blackUnshadedMaterial.setColor("Color", ColorRGBA.Black);

+ 88 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java

@@ -0,0 +1,88 @@
+package com.jme3.scene.plugins.blender.meshes;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+
+/**
+ * A class that represents a single point on the scene that is not a part of an edge.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class Point {
+    private static final Logger LOGGER = Logger.getLogger(Point.class.getName());
+
+    /** The point's index. */
+    private int                 index;
+
+    /**
+     * Constructs a point for a given index.
+     * @param index
+     *            the index of the point
+     */
+    public Point(int index) {
+        this.index = index;
+    }
+
+    @Override
+    public Point clone() {
+        return new Point(index);
+    }
+
+    /**
+     * @return the index of the point
+     */
+    public int getIndex() {
+        return index;
+    }
+
+    /**
+     * The method shifts the index by a given value.
+     * @param shift
+     *            the value to shift the index
+     */
+    public void shiftIndexes(int shift) {
+        index += shift;
+    }
+
+    /**
+     * Loads all points of the mesh that do not belong to any edge.
+     * @param meshStructure
+     *            the mesh structure
+     * @return a list of points
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with file reading occur
+     */
+    public static List<Point> loadAll(Structure meshStructure) throws BlenderFileException {
+        LOGGER.log(Level.FINE, "Loading all points that do not belong to any edge from mesh: {0}", meshStructure.getName());
+        List<Point> result = new ArrayList<Point>();
+
+        Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
+        if (pMEdge.isNotNull()) {
+            int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
+            Set<Integer> unusedVertices = new HashSet<Integer>(count);
+            for (int i = 0; i < count; ++i) {
+                unusedVertices.add(i);
+            }
+
+            List<Structure> edges = pMEdge.fetchData();
+            for (Structure edge : edges) {
+                unusedVertices.remove(((Number) edge.getFieldValue("v1")).intValue());
+                unusedVertices.remove(((Number) edge.getFieldValue("v2")).intValue());
+            }
+
+            for (Integer unusedIndex : unusedVertices) {
+                result.add(new Point(unusedIndex));
+            }
+        }
+        LOGGER.log(Level.FINE, "Loaded {0} points.", result.size());
+        return result;
+    }
+}

+ 599 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java

@@ -0,0 +1,599 @@
+package com.jme3.scene.plugins.blender.meshes;
+
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.Node;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.materials.MaterialContext;
+import com.jme3.scene.plugins.blender.meshes.MeshBuffers.BoneBuffersData;
+import com.jme3.scene.plugins.blender.modifiers.Modifier;
+import com.jme3.scene.plugins.blender.objects.Properties;
+
+/**
+ * The class extends Geometry so that it can be temporalily added to the object's node.
+ * Later each such node's child will be transformed into a list of geometries.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class TemporalMesh extends Geometry {
+    private static final Logger        LOGGER                    = Logger.getLogger(TemporalMesh.class.getName());
+
+    /** The blender context. */
+    protected final BlenderContext     blenderContext;
+
+    /** The mesh's structure. */
+    protected final Structure          meshStructure;
+
+    /** Loaded vertices. */
+    protected List<Vector3f>           vertices                  = new ArrayList<Vector3f>();
+    /** Loaded normals. */
+    protected List<Vector3f>           normals                   = new ArrayList<Vector3f>();
+    /** Loaded vertex groups. */
+    protected List<Map<String, Float>> vertexGroups              = new ArrayList<Map<String, Float>>();
+    /** Loaded vertex colors. */
+    protected List<byte[]>             verticesColors            = new ArrayList<byte[]>();
+
+    /** Materials used by the mesh. */
+    protected MaterialContext[]        materials;
+    /** The properties of the mesh. */
+    protected Properties               properties;
+    /** The bone indexes. */
+    protected Map<String, Integer>     boneIndexes               = new HashMap<String, Integer>();
+    /** The modifiers that should be applied after the mesh has been created. */
+    protected List<Modifier>           postMeshCreationModifiers = new ArrayList<Modifier>();
+
+    /** The faces of the mesh. */
+    protected List<Face>               faces                     = new ArrayList<Face>();
+    /** The edges of the mesh. */
+    protected List<Edge>               edges                     = new ArrayList<Edge>();
+    /** The points of the mesh. */
+    protected List<Point>              points                    = new ArrayList<Point>();
+
+    /** The bounding box of the temporal mesh. */
+    protected BoundingBox              boundingBox;
+
+    /**
+     * Creates a temporal mesh based on the given mesh structure.
+     * @param meshStructure
+     *            the mesh structure
+     * @param blenderContext
+     *            the blender context
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with file reading occur
+     */
+    public TemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
+        this(meshStructure, blenderContext, true);
+    }
+
+    /**
+     * Creates a temporal mesh based on the given mesh structure.
+     * @param meshStructure
+     *            the mesh structure
+     * @param blenderContext
+     *            the blender context
+     * @param loadData
+     *            tells if the data should be loaded from the mesh structure
+     * @throws BlenderFileException
+     *             an exception is thrown when problems with file reading occur
+     */
+    protected TemporalMesh(Structure meshStructure, BlenderContext blenderContext, boolean loadData) throws BlenderFileException {
+        this.blenderContext = blenderContext;
+        name = meshStructure.getName();
+        this.meshStructure = meshStructure;
+
+        if (loadData) {
+            MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+
+            meshHelper.loadVerticesAndNormals(meshStructure, vertices, normals);
+            verticesColors = meshHelper.loadVerticesColors(meshStructure, blenderContext);
+            LinkedHashMap<String, List<Vector2f>> userUVGroups = meshHelper.loadUVCoordinates(meshStructure);
+            vertexGroups = meshHelper.loadVerticesGroups(meshStructure);
+
+            faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext);
+            edges = Edge.loadAll(meshStructure);
+            points = Point.loadAll(meshStructure);
+        }
+    }
+
+    @Override
+    public TemporalMesh clone() {
+        try {
+            TemporalMesh result = new TemporalMesh(meshStructure, blenderContext, false);
+            for (Vector3f v : vertices) {
+                result.vertices.add(v.clone());
+            }
+            for (Vector3f n : normals) {
+                result.normals.add(n.clone());
+            }
+            for (Map<String, Float> group : vertexGroups) {
+                result.vertexGroups.add(new HashMap<String, Float>(group));
+            }
+            for (byte[] vertColor : verticesColors) {
+                result.verticesColors.add(vertColor.clone());
+            }
+            result.materials = materials;
+            result.properties = properties;
+            result.boneIndexes.putAll(boneIndexes);
+            result.postMeshCreationModifiers.addAll(postMeshCreationModifiers);
+            for (Face face : faces) {
+                result.faces.add(face.clone());
+            }
+            for (Edge edge : edges) {
+                result.edges.add(edge.clone());
+            }
+            for (Point point : points) {
+                result.points.add(point.clone());
+            }
+            return result;
+        } catch (BlenderFileException e) {
+            LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage());
+        }
+        return null;
+    }
+
+    /**
+     * @return the vertices of the mesh
+     */
+    protected List<Vector3f> getVertices() {
+        return vertices;
+    }
+
+    /**
+     * @return the normals of the mesh
+     */
+    protected List<Vector3f> getNormals() {
+        return normals;
+    }
+
+    @Override
+    public void updateModelBound() {
+        if (boundingBox == null) {
+            boundingBox = new BoundingBox();
+        }
+        Vector3f min = new Vector3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
+        Vector3f max = new Vector3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
+        for (Vector3f v : vertices) {
+            min.set(Math.min(min.x, v.x), Math.min(min.y, v.y), Math.min(min.z, v.z));
+            max.set(Math.max(max.x, v.x), Math.max(max.y, v.y), Math.max(max.z, v.z));
+        }
+        boundingBox.setMinMax(min, max);
+    }
+
+    @Override
+    public BoundingVolume getModelBound() {
+        this.updateModelBound();
+        return boundingBox;
+    }
+
+    @Override
+    public BoundingVolume getWorldBound() {
+        this.updateModelBound();
+        Node parent = this.getParent();
+        if (parent != null) {
+            BoundingVolume bv = boundingBox.clone();
+            bv.setCenter(parent.getWorldTranslation());
+            return bv;
+        } else {
+            return boundingBox;
+        }
+    }
+
+    /**
+     * Triangulates the mesh.
+     */
+    public void triangulate() {
+        LOGGER.fine("Triangulating temporal mesh.");
+        List<Face> triangulatedFaces = new ArrayList<Face>();
+        for (Face face : faces) {
+            triangulatedFaces.addAll(face.triangulate(vertices, normals));
+        }
+        faces = triangulatedFaces;
+    }
+
+    /**
+     * The method appends the given mesh to the current one. New faces and vertices and indexes are added.
+     * @param mesh
+     *            the mesh to be appended
+     */
+    public void append(TemporalMesh mesh) {
+        // we need to shift the indexes in faces, lines and points
+        int shift = vertices.size();
+        if (shift > 0) {
+            for (Face face : mesh.faces) {
+                face.shiftIndexes(shift);
+                face.setTemporalMesh(this);
+            }
+            for (Edge edge : mesh.edges) {
+                edge.shiftIndexes(shift);
+            }
+            for (Point point : mesh.points) {
+                point.shiftIndexes(shift);
+            }
+        }
+
+        faces.addAll(mesh.faces);
+        edges.addAll(mesh.edges);
+        points.addAll(mesh.points);
+
+        vertices.addAll(mesh.vertices);
+        normals.addAll(mesh.normals);
+        vertexGroups.addAll(mesh.vertexGroups);
+        verticesColors.addAll(mesh.verticesColors);
+        boneIndexes.putAll(mesh.boneIndexes);
+    }
+
+    /**
+     * Translate all vertices by the given vector.
+     * @param translation
+     *            the translation vector
+     * @return this mesh after translation (NO new instance is created)
+     */
+    public TemporalMesh translate(Vector3f translation) {
+        for (Vector3f v : vertices) {
+            v.addLocal(translation);
+        }
+        return this;
+    }
+
+    /**
+     * Sets the properties of the mesh.
+     * @param properties
+     *            the properties of the mesh
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * Sets the materials of the mesh.
+     * @param materials
+     *            the materials of the mesh
+     */
+    public void setMaterials(MaterialContext[] materials) {
+        this.materials = materials;
+    }
+
+    /**
+     * Adds bone index to the mesh.
+     * @param boneName
+     *            the name of the bone
+     * @param boneIndex
+     *            the index of the bone
+     */
+    public void addBoneIndex(String boneName, Integer boneIndex) {
+        boneIndexes.put(boneName, boneIndex);
+    }
+
+    /**
+     * The modifier to be applied after the geometries are created.
+     * @param modifier
+     *            the modifier to be applied
+     */
+    public void applyAfterMeshCreate(Modifier modifier) {
+        postMeshCreationModifiers.add(modifier);
+    }
+
+    @Override
+    public int getVertexCount() {
+        return vertices.size();
+    }
+
+    /**
+     * Returns the vertex at the given position.
+     * @param i
+     *            the vertex position
+     * @return the vertex at the given position
+     */
+    public Vector3f getVertex(int i) {
+        return vertices.get(i);
+    }
+
+    /**
+     * Returns the normal at the given position.
+     * @param i
+     *            the normal position
+     * @return the normal at the given position
+     */
+    public Vector3f getNormal(int i) {
+        return normals.get(i);
+    }
+
+    /**
+     * Flips the order of the mesh's indexes.
+     */
+    public void flipIndexes() {
+        for (Face face : faces) {
+            face.flipIndexes();
+        }
+        for (Edge edge : edges) {
+            edge.flipIndexes();
+        }
+        Collections.reverse(points);
+    }
+
+    /**
+     * Flips UV coordinates.
+     * @param u
+     *            indicates if U coords should be flipped
+     * @param v
+     *            indicates if V coords should be flipped
+     */
+    public void flipUV(boolean u, boolean v) {
+        for (Face face : faces) {
+            face.flipUV(u, v);
+        }
+    }
+
+    /**
+     * The mesh builds geometries from the mesh. The result is stored in the blender context
+     * under the mesh's OMA.
+     */
+    public void toGeometries() {
+        LOGGER.log(Level.FINE, "Converting temporal mesh {0} to jme geometries.", name);
+        List<Geometry> result = new ArrayList<Geometry>();
+        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+        Node parent = this.getParent();
+        parent.detachChild(this);
+
+        this.prepareFacesGeometry(result, meshHelper);
+        this.prepareLinesGeometry(result, meshHelper);
+        this.preparePointsGeometry(result, meshHelper);
+
+        blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
+
+        for (Geometry geometry : result) {
+            parent.attachChild(geometry);
+        }
+
+        for (Modifier modifier : postMeshCreationModifiers) {
+            modifier.postMeshCreationApply(parent, blenderContext);
+        }
+    }
+
+    /**
+     * The method creates geometries from faces.
+     * @param result
+     *            the list where new geometries will be appended
+     * @param meshHelper
+     *            the mesh helper
+     */
+    protected void prepareFacesGeometry(List<Geometry> result, MeshHelper meshHelper) {
+        LOGGER.fine("Preparing faces geometries.");
+        this.triangulate();
+
+        Vector3f[] tempVerts = new Vector3f[3];
+        Vector3f[] tempNormals = new Vector3f[3];
+        byte[][] tempVertColors = new byte[3][];
+        List<Map<Float, Integer>> boneBuffers = new ArrayList<Map<Float, Integer>>(3);
+
+        LOGGER.log(Level.FINE, "Appending {0} faces to mesh buffers.", faces.size());
+        Map<Integer, MeshBuffers> faceMeshes = new HashMap<Integer, MeshBuffers>();
+        for (Face face : faces) {
+            MeshBuffers meshBuffers = faceMeshes.get(face.getMaterialNumber());
+            if (meshBuffers == null) {
+                meshBuffers = new MeshBuffers(face.getMaterialNumber());
+                faceMeshes.put(face.getMaterialNumber(), meshBuffers);
+            }
+
+            List<Integer> indexes = face.getIndexes();
+            List<byte[]> vertexColors = face.getVertexColors();
+            boneBuffers.clear();
+            assert indexes.size() == 3 : "The mesh has not been properly triangulated!";
+            for (int i = 0; i < 3; ++i) {
+                int vertIndex = indexes.get(i);
+                tempVerts[i] = vertices.get(vertIndex);
+                tempNormals[i] = normals.get(vertIndex);
+                tempVertColors[i] = vertexColors != null ? vertexColors.get(i) : null;
+
+                if (boneIndexes.size() > 0) {
+                    Map<Float, Integer> boneBuffersForVertex = new HashMap<Float, Integer>();
+                    Map<String, Float> vertexGroupsForVertex = vertexGroups.get(vertIndex);
+                    for (Entry<String, Integer> entry : boneIndexes.entrySet()) {
+                        if (vertexGroupsForVertex.containsKey(entry.getKey())) {
+                            boneBuffersForVertex.put(vertexGroupsForVertex.get(entry.getKey()), entry.getValue());
+                        }
+                    }
+                    boneBuffers.add(boneBuffersForVertex);
+                }
+            }
+
+            meshBuffers.append(face.isSmooth(), tempVerts, tempNormals, face.getUvSets(), tempVertColors, boneBuffers);
+        }
+
+        LOGGER.fine("Converting mesh buffers to geometries.");
+        Map<Geometry, MeshBuffers> geometryToBuffersMap = new HashMap<Geometry, MeshBuffers>();
+        for (Entry<Integer, MeshBuffers> entry : faceMeshes.entrySet()) {
+            MeshBuffers meshBuffers = entry.getValue();
+
+            Mesh mesh = new Mesh();
+
+            if (meshBuffers.isShortIndexBuffer()) {
+                mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer());
+            } else {
+                mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer());
+            }
+            mesh.setBuffer(meshBuffers.getPositionsBuffer());
+            mesh.setBuffer(meshBuffers.getNormalsBuffer());
+            if (meshBuffers.areVertexColorsUsed()) {
+                mesh.setBuffer(Type.Color, 4, meshBuffers.getVertexColorsBuffer());
+                mesh.getBuffer(Type.Color).setNormalized(true);
+            }
+
+            BoneBuffersData boneBuffersData = meshBuffers.getBoneBuffers();
+            if (boneBuffersData != null) {
+                mesh.setMaxNumWeights(boneBuffersData.maximumWeightsPerVertex);
+                mesh.setBuffer(boneBuffersData.verticesWeights);
+                mesh.setBuffer(boneBuffersData.verticesWeightsIndices);
+
+                LOGGER.fine("Generating bind pose and normal buffers.");
+                mesh.generateBindPose(true);
+
+                // change the usage type of vertex and normal buffers from Static to Stream
+                mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
+                mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
+
+                // creating empty buffers for HW skinning; the buffers will be setup if ever used
+                VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
+                VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
+                mesh.setBuffer(verticesWeightsHW);
+                mesh.setBuffer(verticesWeightsIndicesHW);
+            }
+
+            Geometry geometry = new Geometry(name + (result.size() + 1), mesh);
+            if (properties != null && properties.getValue() != null) {
+                meshHelper.applyProperties(geometry, properties);
+            }
+            result.add(geometry);
+
+            geometryToBuffersMap.put(geometry, meshBuffers);
+        }
+
+        LOGGER.fine("Applying materials to geometries.");
+        for (Entry<Geometry, MeshBuffers> entry : geometryToBuffersMap.entrySet()) {
+            int materialIndex = entry.getValue().getMaterialIndex();
+            Geometry geometry = entry.getKey();
+            if (materialIndex >= 0 && materials != null && materials.length > materialIndex && materials[materialIndex] != null) {
+                materials[materialIndex].applyMaterial(geometry, meshStructure.getOldMemoryAddress(), entry.getValue().getUvCoords(), blenderContext);
+            } else {
+                geometry.setMaterial(blenderContext.getDefaultMaterial());
+            }
+        }
+    }
+
+    /**
+     * The method creates geometries from lines.
+     * @param result
+     *            the list where new geometries will be appended
+     * @param meshHelper
+     *            the mesh helper
+     */
+    protected void prepareLinesGeometry(List<Geometry> result, MeshHelper meshHelper) {
+        if (edges.size() > 0) {
+            LOGGER.fine("Preparing lines geometries.");
+            
+            List<List<Integer>> separateEdges = new ArrayList<List<Integer>>();
+            List<Edge> edges = new ArrayList<Edge>(this.edges);
+            while (edges.size() > 0) {
+                boolean edgeAppended = false;
+                int edgeIndex = 0;
+                for (List<Integer> list : separateEdges) {
+                    for (edgeIndex = 0; edgeIndex < edges.size() && !edgeAppended; ++edgeIndex) {
+                        Edge edge = edges.get(edgeIndex);
+                        if (list.get(0).equals(edge.getFirstIndex())) {
+                            list.add(0, edge.getSecondIndex());
+                            --edgeIndex;
+                            edgeAppended = true;
+                        } else if (list.get(0).equals(edge.getSecondIndex())) {
+                            list.add(0, edge.getFirstIndex());
+                            --edgeIndex;
+                            edgeAppended = true;
+                        } else if (list.get(list.size() - 1).equals(edge.getFirstIndex())) {
+                            list.add(edge.getSecondIndex());
+                            --edgeIndex;
+                            edgeAppended = true;
+                        } else if (list.get(list.size() - 1).equals(edge.getSecondIndex())) {
+                            list.add(edge.getFirstIndex());
+                            --edgeIndex;
+                            edgeAppended = true;
+                        }
+                    }
+                    if (edgeAppended) {
+                        break;
+                    }
+                }
+                Edge edge = edges.remove(edgeAppended ? edgeIndex : 0);
+                if (!edgeAppended) {
+                    separateEdges.add(new ArrayList<Integer>(Arrays.asList(edge.getFirstIndex(), edge.getSecondIndex())));
+                }
+            }
+
+            for (List<Integer> list : separateEdges) {
+                MeshBuffers meshBuffers = new MeshBuffers(0);
+                for (int index : list) {
+                    meshBuffers.append(vertices.get(index), normals.get(index));
+                }
+                Mesh mesh = new Mesh();
+                mesh.setPointSize(2);
+                mesh.setMode(Mode.LineStrip);
+                if (meshBuffers.isShortIndexBuffer()) {
+                    mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer());
+                } else {
+                    mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer());
+                }
+                mesh.setBuffer(meshBuffers.getPositionsBuffer());
+                mesh.setBuffer(meshBuffers.getNormalsBuffer());
+
+                Geometry geometry = new Geometry(meshStructure.getName() + (result.size() + 1), mesh);
+                geometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext));
+                if (properties != null && properties.getValue() != null) {
+                    meshHelper.applyProperties(geometry, properties);
+                }
+                result.add(geometry);
+            }
+        }
+    }
+
+    /**
+     * The method creates geometries from points.
+     * @param result
+     *            the list where new geometries will be appended
+     * @param meshHelper
+     *            the mesh helper
+     */
+    protected void preparePointsGeometry(List<Geometry> result, MeshHelper meshHelper) {
+        if (points.size() > 0) {
+            LOGGER.fine("Preparing point geometries.");
+            
+            MeshBuffers pointBuffers = new MeshBuffers(0);
+            for (Point point : points) {
+                pointBuffers.append(vertices.get(point.getIndex()), normals.get(point.getIndex()));
+            }
+            Mesh pointsMesh = new Mesh();
+            pointsMesh.setMode(Mode.Points);
+            pointsMesh.setPointSize(blenderContext.getBlenderKey().getPointsSize());
+            if (pointBuffers.isShortIndexBuffer()) {
+                pointsMesh.setBuffer(Type.Index, 1, (ShortBuffer) pointBuffers.getIndexBuffer());
+            } else {
+                pointsMesh.setBuffer(Type.Index, 1, (IntBuffer) pointBuffers.getIndexBuffer());
+            }
+            pointsMesh.setBuffer(pointBuffers.getPositionsBuffer());
+            pointsMesh.setBuffer(pointBuffers.getNormalsBuffer());
+
+            Geometry pointsGeometry = new Geometry(meshStructure.getName() + (result.size() + 1), pointsMesh);
+            pointsGeometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext));
+            if (properties != null && properties.getValue() != null) {
+                meshHelper.applyProperties(pointsGeometry, properties);
+            }
+            result.add(pointsGeometry);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]";
+    }
+}

+ 0 - 586
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/FaceMeshBuilder.java

@@ -1,586 +0,0 @@
-package com.jme3.scene.plugins.blender.meshes.builders;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Logger;
-
-import com.jme3.math.FastMath;
-import com.jme3.math.Vector2f;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.VertexBuffer;
-import com.jme3.scene.VertexBuffer.Format;
-import com.jme3.scene.VertexBuffer.Type;
-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.meshes.MeshHelper;
-import com.jme3.scene.plugins.blender.textures.UserUVCollection;
-import com.jme3.util.BufferUtils;
-
-/**
- * A builder class for meshes made of triangles (faces).
- * 
- * @author Marcin Roguski (Kaelthas)
- */
-/* package */class FaceMeshBuilder {
-    private static final Logger                       LOGGER           = Logger.getLogger(FaceMeshBuilder.class.getName());
-
-    /** An array of reference vertices. */
-    private Vector3f[][]                              verticesAndNormals;
-    /** An list of vertices colors. */
-    private List<byte[]>                              verticesColors;
-    /** A variable that indicates if the model uses generated textures. */
-    private boolean                                   usesGeneratedTextures;
-    /**
-     * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
-     * positions (it simply tells which vertex is referenced where in the result list).
-     */
-    private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap;
-    /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<Vector3f>>              normalMap        = new HashMap<Integer, List<Vector3f>>();
-    /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<Vector3f>>              vertexMap        = new HashMap<Integer, List<Vector3f>>();
-    /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<byte[]>>                vertexColorsMap  = new HashMap<Integer, List<byte[]>>();
-    /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */
-    private Map<Integer, List<Integer>>               indexMap         = new HashMap<Integer, List<Integer>>();
-    /** A collection of user defined UV coordinates (one mesh can have more than one such mappings). */
-    private UserUVCollection                          userUVCollection = new UserUVCollection();
-
-    /**
-     * Constructor. Stores the given array (not copying it).
-     * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
-     * The amount of vertices is always faceCount * 3.
-     * @param verticesAndNormals
-     *            the reference vertices and normals array
-     * @param usesGeneratedTextures
-     *            a variable that indicates if the model uses generated textures or not
-     */
-    public FaceMeshBuilder(Vector3f[][] verticesAndNormals, boolean usesGeneratedTextures) {
-        this.verticesAndNormals = verticesAndNormals;
-        this.usesGeneratedTextures = usesGeneratedTextures;
-        globalVertexReferenceMap = new HashMap<Integer, Map<Integer, List<Integer>>>(verticesAndNormals.length);
-    }
-
-    public void readMesh(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
-        verticesColors = this.getVerticesColors(structure, blenderContext);
-        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
-
-        if (meshHelper.isBMeshCompatible(structure)) {
-            this.readBMesh(structure);
-        } else {
-            this.readTraditionalFaces(structure);
-        }
-    }
-
-    /**
-     * Builds the meshes.
-     * @return a map between material index and the mesh
-     */
-    public Map<Integer, Mesh> buildMeshes() {
-        Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(indexMap.size());
-
-        for (Entry<Integer, List<Integer>> meshEntry : indexMap.entrySet()) {
-            int materialIndex = meshEntry.getKey();
-            // key is the material index
-            // value is a list of vertex indices
-            Mesh mesh = new Mesh();
-
-            // creating vertices indices for this mesh
-            List<Integer> indexList = meshEntry.getValue();
-            if (this.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) {
-                short[] indices = new short[indexList.size()];
-                for (int i = 0; i < indexList.size(); ++i) {
-                    indices[i] = indexList.get(i).shortValue();
-                }
-                mesh.setBuffer(Type.Index, 1, indices);
-            } else {
-                int[] indices = new int[indexList.size()];
-                for (int i = 0; i < indexList.size(); ++i) {
-                    indices[i] = indexList.get(i).intValue();
-                }
-                mesh.setBuffer(Type.Index, 1, indices);
-            }
-
-            LOGGER.fine("Creating vertices buffer.");
-            VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
-            verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getVertices(materialIndex)));
-            mesh.setBuffer(verticesBuffer);
-
-            LOGGER.fine("Creating normals buffer.");
-            VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
-            normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(this.getNormals(materialIndex)));
-            mesh.setBuffer(normalsBuffer);
-
-            if (verticesColors != null) {
-                LOGGER.fine("Setting vertices colors.");
-                mesh.setBuffer(Type.Color, 4, this.getVertexColorsBuffer(materialIndex));
-                mesh.getBuffer(Type.Color).setNormalized(true);
-            }
-
-            result.put(materialIndex, mesh);
-        }
-
-        return result;
-    }
-
-    /**
-     * This method reads the mesh from the new BMesh system.
-     * 
-     * @param meshStructure
-     *            the mesh structure
-     * @throws BlenderFileException
-     *             an exception is thrown when there are problems with the
-     *             blender file
-     */
-    private void readBMesh(Structure meshStructure) throws BlenderFileException {
-        LOGGER.fine("Reading BMesh.");
-        Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
-        Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
-        Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
-
-        if (pMPoly.isNotNull() && pMLoop.isNotNull()) {
-            Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, true);
-            List<Structure> polys = pMPoly.fetchData();
-            List<Structure> loops = pMLoop.fetchData();
-            int[] vertexColorIndex = verticesColors == null ? null : new int[3];
-            for (Structure poly : polys) {
-                int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
-                int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
-                int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
-                boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
-                int[] vertexIndexes = new int[totLoop];
-
-                for (int i = loopStart; i < loopStart + totLoop; ++i) {
-                    vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
-                }
-
-                int i = 0;
-                while (i < totLoop - 2) {
-                    int v1 = vertexIndexes[0];
-                    int v2 = vertexIndexes[i + 1];
-                    int v3 = vertexIndexes[i + 2];
-                    if (vertexColorIndex != null) {
-                        vertexColorIndex[0] = loopStart;
-                        vertexColorIndex[1] = loopStart + i + 1;
-                        vertexColorIndex[2] = loopStart + i + 2;
-                    }
-
-                    if (uvs != null) {
-                        // uvs always must be added wheater we have texture or not
-                        for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
-                            Vector2f[] uvCoordsForASingleFace = new Vector2f[3];
-                            uvCoordsForASingleFace[0] = entry.getValue().get(loopStart);
-                            uvCoordsForASingleFace[1] = entry.getValue().get(loopStart + i + 1);
-                            uvCoordsForASingleFace[2] = entry.getValue().get(loopStart + i + 2);
-                            uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace);
-                        }
-                    }
-
-                    this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex);
-                    uvCoordinatesForFace.clear();
-                    ++i;
-                }
-            }
-        }
-    }
-
-    /**
-     * This method reads the mesh from traditional triangle/quad storing
-     * structures.
-     * 
-     * @param meshStructure
-     *            the mesh structure
-     * @throws BlenderFileException
-     *             an exception is thrown when there are problems with the
-     *             blender file
-     */
-    private void readTraditionalFaces(Structure meshStructure) throws BlenderFileException {
-        LOGGER.fine("Reading traditional faces.");
-        Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
-        List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null;
-        if (mFaces != null && mFaces.size() > 0) {
-            // indicates if the material with the specified number should have a texture attached
-            Map<String, List<Vector2f>> uvs = this.loadUVCoordinates(meshStructure, false);
-            Map<String, Vector2f[]> uvCoordinatesForFace = new HashMap<String, Vector2f[]>();
-            int[] vertexColorIndex = verticesColors == null ? null : new int[3];
-            for (int i = 0; i < mFaces.size(); ++i) {
-                Structure mFace = mFaces.get(i);
-                int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
-                boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
-                if (uvs != null) {
-                    // uvs always must be added wheater we have texture or not
-                    for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
-                        Vector2f[] uvCoordsForASingleFace = new Vector2f[3];
-                        uvCoordsForASingleFace[0] = entry.getValue().get(i * 4);
-                        uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 1);
-                        uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 2);
-                        uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace);
-                    }
-                }
-
-                int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
-                int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
-                int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
-                int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
-                if (vertexColorIndex != null) {
-                    vertexColorIndex[0] = i * 4;
-                    vertexColorIndex[1] = i * 4 + 1;
-                    vertexColorIndex[2] = i * 4 + 2;
-                }
-
-                this.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex);
-                uvCoordinatesForFace.clear();
-                if (v4 > 0) {
-                    if (uvs != null) {
-                        // uvs always must be added wheater we have texture or not
-                        for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
-                            Vector2f[] uvCoordsForASingleFace = new Vector2f[3];
-                            uvCoordsForASingleFace[0] = entry.getValue().get(i * 4);
-                            uvCoordsForASingleFace[1] = entry.getValue().get(i * 4 + 2);
-                            uvCoordsForASingleFace[2] = entry.getValue().get(i * 4 + 3);
-                            uvCoordinatesForFace.put(entry.getKey(), uvCoordsForASingleFace);
-                        }
-                    }
-                    if (vertexColorIndex != null) {
-                        vertexColorIndex[0] = i * 4;
-                        vertexColorIndex[1] = i * 4 + 2;
-                        vertexColorIndex[2] = i * 4 + 3;
-                    }
-                    this.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, vertexColorIndex);
-                    uvCoordinatesForFace.clear();
-                }
-            }
-        }
-    }
-
-    /**
-     * This method adds a face to the mesh.
-     * @param v1
-     *            index of the 1'st vertex from the reference vertex table
-     * @param v2
-     *            index of the 2'nd vertex from the reference vertex table
-     * @param v3
-     *            index of the 3'rd vertex from the reference vertex table
-     * @param smooth
-     *            indicates if this face should have smooth shading or flat shading
-     * @param materialNumber
-     *            the material number for this face
-     * @param uvsForFace
-     *            a 3-element array of vertices UV coordinates mapped to the UV's set name
-     * @param vertexColorIndex
-     *            a table of 3 elements that indicates the verts' colors indexes
-     */
-    private void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Map<String, Vector2f[]> uvsForFace, int[] vertexColorIndex) {
-        if (uvsForFace != null && uvsForFace.size() > 0) {
-            for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
-                if (entry.getValue().length != 3) {
-                    throw new IllegalArgumentException("UV coordinates must be a 3-element array!" + (entry.getKey() != null ? " (UV set name: " + entry.getKey() + ')' : ""));
-                }
-            }
-        }
-
-        // getting the required lists
-        List<Integer> indexList = indexMap.get(materialNumber);
-        if (indexList == null) {
-            indexList = new ArrayList<Integer>();
-            indexMap.put(materialNumber, indexList);
-        }
-        List<Vector3f> vertexList = vertexMap.get(materialNumber);
-        if (vertexList == null) {
-            vertexList = new ArrayList<Vector3f>();
-            vertexMap.put(materialNumber, vertexList);
-        }
-        List<byte[]> vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null;
-        if (vertexColorsList == null && vertexColorsMap != null) {
-            vertexColorsList = new ArrayList<byte[]>();
-            vertexColorsMap.put(materialNumber, vertexColorsList);
-        }
-        List<Vector3f> normalList = normalMap.get(materialNumber);
-        if (normalList == null) {
-            normalList = new ArrayList<Vector3f>();
-            normalMap.put(materialNumber, normalList);
-        }
-        Map<Integer, List<Integer>> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber);
-        if (vertexReferenceMap == null) {
-            vertexReferenceMap = new HashMap<Integer, List<Integer>>();
-            globalVertexReferenceMap.put(materialNumber, vertexReferenceMap);
-        }
-
-        // creating faces
-        Integer[] index = new Integer[] { v1, v2, v3 };
-        if (smooth && !usesGeneratedTextures) {
-            for (int i = 0; i < 3; ++i) {
-                if (!vertexReferenceMap.containsKey(index[i])) {
-                    // if this index is not yet used then create another face
-                    this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
-                    if (uvsForFace != null) {
-                        for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
-                            userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size());
-                        }
-                    }
-
-                    vertexList.add(verticesAndNormals[index[i]][0]);
-                    if (verticesColors != null) {
-                        vertexColorsList.add(verticesColors.get(vertexColorIndex[i]));
-                    }
-                    normalList.add(verticesAndNormals[index[i]][1]);
-
-                    index[i] = vertexList.size() - 1;
-                } else if (uvsForFace != null) {
-                    // if the index is used then check if the vertexe's UV coordinates match, if yes then the vertex doesn't have separate UV's
-                    // in different faces so we can use it here as well, if UV's are different in separate faces the we need to add this vert
-                    // because in jme one vertex can have only on UV coordinate
-                    boolean vertexAlreadyUsed = false;
-                    for (Integer vertexIndex : vertexReferenceMap.get(index[i])) {
-                        int vertexUseCounter = 0;
-                        for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
-                            if (entry.getValue()[i].equals(userUVCollection.getUVForVertex(entry.getKey(), vertexIndex))) {
-                                ++vertexUseCounter;
-                            }
-                        }
-                        if (vertexUseCounter == uvsForFace.size()) {
-                            vertexAlreadyUsed = true;
-                            index[i] = vertexIndex;
-                            break;
-                        }
-                    }
-
-                    if (!vertexAlreadyUsed) {
-                        // treat this face as a new one because its vertices have separate UV's
-                        this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
-                        for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
-                            userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size());
-                        }
-                        vertexList.add(verticesAndNormals[index[i]][0]);
-                        if (verticesColors != null) {
-                            vertexColorsList.add(verticesColors.get(vertexColorIndex[i]));
-                        }
-                        normalList.add(verticesAndNormals[index[i]][1]);
-                        index[i] = vertexList.size() - 1;
-                    }
-                } else {
-                    // use this index again
-                    index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]);
-                }
-                indexList.add(index[i]);
-            }
-        } else {
-            Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]);
-            for (int i = 0; i < 3; ++i) {
-                indexList.add(vertexList.size());
-                this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap);
-                if (uvsForFace != null) {
-                    for (Entry<String, Vector2f[]> entry : uvsForFace.entrySet()) {
-                        userUVCollection.addUV(materialNumber, entry.getKey(), entry.getValue()[i], vertexList.size());
-                    }
-                }
-                vertexList.add(verticesAndNormals[index[i]][0]);
-                if (verticesColors != null) {
-                    vertexColorsList.add(verticesColors.get(vertexColorIndex[i]));
-                }
-                normalList.add(smooth ? verticesAndNormals[index[i]][1] : n);
-            }
-        }
-    }
-
-    /**
-     * The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates.
-     * But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning.
-     * For bmesh they are enlisted just like they are stored in the blend file (in loops).
-     * For traditional faces every 4 UV's should be assigned for a single face.
-     * @param meshStructure
-     *            the mesh structure
-     * @param useBMesh
-     *            tells if we should load the coordinates from loops of from faces
-     * @return a map that sorts UV coordinates between different UV sets
-     * @throws BlenderFileException
-     *             an exception is thrown when problems with blend file occur
-     */
-    @SuppressWarnings("unchecked")
-    private Map<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure, boolean useBMesh) throws BlenderFileException {
-        Map<String, List<Vector2f>> result = new HashMap<String, List<Vector2f>>();
-        if (useBMesh) {
-            // in this case the UV's are assigned to vertices (an array is the same length as the vertex array)
-            Structure loopData = (Structure) meshStructure.getFieldValue("ldata");
-            Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers");
-            List<Structure> loopDataLayers = pLoopDataLayers.fetchData();
-            for (Structure structure : loopDataLayers) {
-                Pointer p = (Pointer) structure.getFieldValue("data");
-                if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) {
-                    String uvSetName = structure.getFieldValue("name").toString();
-                    List<Structure> uvsStructures = p.fetchData();
-                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
-                    for (Structure uvStructure : uvsStructures) {
-                        DynamicArray<Number> loopUVS = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
-                        uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()));
-                    }
-                    result.put(uvSetName, uvs);
-                }
-            }
-        } else {
-            // in this case UV's are assigned to faces (the array has the same legnth as the faces count)
-            Structure facesData = (Structure) meshStructure.getFieldValue("fdata");
-            Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers");
-            List<Structure> facesDataLayers = pFacesDataLayers.fetchData();
-            for (Structure structure : facesDataLayers) {
-                Pointer p = (Pointer) structure.getFieldValue("data");
-                if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) {
-                    String uvSetName = structure.getFieldValue("name").toString();
-                    List<Structure> uvsStructures = p.fetchData();
-                    List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
-                    for (Structure uvStructure : uvsStructures) {
-                        DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
-                        uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue()));
-                        uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue()));
-                        uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue()));
-                        uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue()));
-                    }
-                    result.put(uvSetName, uvs);
-                }
-            }
-        }
-        return result;
-    }
-
-    /**
-     * This method returns the vertices colors. Each vertex is stored in byte[4] array.
-     * 
-     * @param meshStructure
-     *            the structure containing the mesh data
-     * @param blenderContext
-     *            the blender context
-     * @return a list of vertices colors, each color belongs to a single vertex
-     * @throws BlenderFileException
-     *             this exception is thrown when the blend file structure is somehow invalid or corrupted
-     */
-    private List<byte[]> getVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
-        MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
-        Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
-        List<byte[]> verticesColors = null;
-        // it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure)
-        // so we need to put them right
-        boolean useBGRA = blenderContext.getBlenderVersion() < 263;
-        if (pMCol.isNotNull()) {
-            List<Structure> mCol = pMCol.fetchData();
-            verticesColors = new ArrayList<byte[]>(mCol.size());
-            for (Structure color : mCol) {
-                byte r = ((Number) color.getFieldValue("r")).byteValue();
-                byte g = ((Number) color.getFieldValue("g")).byteValue();
-                byte b = ((Number) color.getFieldValue("b")).byteValue();
-                byte a = ((Number) color.getFieldValue("a")).byteValue();
-                verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a });
-            }
-        }
-        return verticesColors;
-    }
-
-    /**
-     * @return a map that maps vertex index from reference array to its indices in the result list
-     */
-    public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() {
-        return globalVertexReferenceMap;
-    }
-
-    /**
-     * @param materialNumber
-     *            the material index
-     * @return result vertices array
-     */
-    private Vector3f[] getVertices(int materialNumber) {
-        return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]);
-    }
-
-    /**
-     * @param materialNumber
-     *            the material index
-     * @return the amount of result vertices
-     */
-    private int getVerticesAmount(int materialNumber) {
-        return vertexMap.get(materialNumber).size();
-    }
-
-    /**
-     * @param materialNumber
-     *            the material index
-     * @return normals result array
-     */
-    private Vector3f[] getNormals(int materialNumber) {
-        return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]);
-    }
-
-    /**
-     * @param materialNumber
-     *            the material index
-     * @return the vertices colors buffer or null if no vertex colors is set
-     */
-    private ByteBuffer getVertexColorsBuffer(int materialNumber) {
-        ByteBuffer result = null;
-        if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) {
-            List<byte[]> data = vertexColorsMap.get(materialNumber);
-            result = BufferUtils.createByteBuffer(4 * data.size());
-            for (byte[] v : data) {
-                if (v != null) {
-                    result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
-                } else {
-                    result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
-                }
-            }
-            result.flip();
-        }
-        return result;
-    }
-
-    /**
-     * @param materialNumber
-     *            the material number that is appied to the mesh
-     * @return UV coordinates of vertices that belong to the required mesh part
-     */
-    public LinkedHashMap<String, List<Vector2f>> getUVCoordinates(int materialNumber) {
-        return userUVCollection.getUVCoordinates(materialNumber);
-    }
-
-    /**
-     * @return indicates if the mesh has UV coordinates
-     */
-    public boolean hasUVCoordinates() {
-        return userUVCollection.hasUVCoordinates();
-    }
-
-    /**
-     * @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
-     */
-    public boolean isEmpty() {
-        return vertexMap.size() == 0;
-    }
-
-    /**
-     * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
-     * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key
-     * - the reference indices list.
-     * 
-     * @param basicVertexIndex
-     *            the index of the vertex from its basic table
-     * @param resultIndex
-     *            the index of the vertex in its result vertex list
-     * @param vertexReferenceMap
-     *            the reference map
-     */
-    private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {
-        List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
-        if (referenceList == null) {
-            referenceList = new ArrayList<Integer>();
-            vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
-        }
-        referenceList.add(Integer.valueOf(resultIndex));
-    }
-}

+ 0 - 160
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/LineMeshBuilder.java

@@ -1,160 +0,0 @@
-package com.jme3.scene.plugins.blender.meshes.builders;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Logger;
-
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.Mesh.Mode;
-import com.jme3.scene.VertexBuffer;
-import com.jme3.scene.VertexBuffer.Format;
-import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.VertexBuffer.Usage;
-import com.jme3.scene.plugins.blender.file.BlenderFileException;
-import com.jme3.scene.plugins.blender.file.Pointer;
-import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.util.BufferUtils;
-
-/**
- * A builder that creates a lines mesh. The result is made of lines that do not belong to any face.
- * 
- * @author Marcin Roguski (Kaelthas)
- */
-/* package */class LineMeshBuilder {
-    private static final Logger LOGGER                = Logger.getLogger(LineMeshBuilder.class.getName());
-
-    private static final int    EDGE_NOT_IN_FACE_FLAG = 0x80;
-
-    /** An array of reference vertices. */
-    private Vector3f[][]        verticesAndNormals;
-    /** The vertices of the mesh. */
-    private List<Vector3f>      vertices              = new ArrayList<Vector3f>();
-    /** The normals of the mesh. */
-    private List<Vector3f>      normals              = new ArrayList<Vector3f>();
-    
-    /**
-     * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
-     * positions (it simply tells which vertex is referenced where in the result list).
-     */
-    private Map<Integer, List<Integer>> globalVertexReferenceMap;
-    
-    /**
-     * Constructor. Stores the given array (not copying it).
-     * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
-     * The amount of vertices is always faceCount * 3.
-     * @param verticesAndNormals
-     *            the reference vertices and normals array
-     */
-    public LineMeshBuilder(Vector3f[][] verticesAndNormals) {
-        this.verticesAndNormals = verticesAndNormals;
-        globalVertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAndNormals.length);
-    }
-
-    /**
-     * The method reads the mesh. It loads only edges that are marked as not belonging to any face in their flag field.
-     * @param meshStructure
-     *            the mesh structure
-     * @throws BlenderFileException
-     *             an exception thrown when reading from the blend file fails
-     */
-    public void readMesh(Structure meshStructure) throws BlenderFileException {
-        LOGGER.fine("Reading line mesh.");
-        Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
-
-        if (pMEdge.isNotNull()) {
-            List<Structure> edges = pMEdge.fetchData();
-            int vertexIndex = 0;//vertex index in the result mesh
-            for (Structure edge : edges) {
-                int flag = ((Number) edge.getFieldValue("flag")).intValue();
-                if ((flag & EDGE_NOT_IN_FACE_FLAG) != 0) {
-                    int v1 = ((Number) edge.getFieldValue("v1")).intValue();
-                    int v2 = ((Number) edge.getFieldValue("v2")).intValue();
-                    
-                    vertices.add(verticesAndNormals[v1][0]);
-                    normals.add(verticesAndNormals[v1][1]);
-                    this.appendVertexReference(v1, vertexIndex++, globalVertexReferenceMap);
-                    
-                    vertices.add(verticesAndNormals[v2][0]);
-                    normals.add(verticesAndNormals[v2][1]);
-                    this.appendVertexReference(v2, vertexIndex++, globalVertexReferenceMap);
-                }
-            }
-        }
-    }
-
-    /**
-     * Builds the meshes.
-     * @return a map between material index and the mesh
-     */
-    public Map<Integer, Mesh> buildMeshes() {
-        LOGGER.fine("Building line mesh.");
-        Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(1);
-        if (vertices.size() > 0) {
-            Mesh mesh = new Mesh();
-            mesh.setMode(Mode.Lines);
-
-            LOGGER.fine("Creating indices buffer.");
-            if (vertices.size() <= Short.MAX_VALUE) {
-                short[] indices = new short[vertices.size()];
-                for (int i = 0; i < vertices.size(); ++i) {
-                    indices[i] = (short) i;
-                }
-                mesh.setBuffer(Type.Index, 1, indices);
-            } else {
-                int[] indices = new int[vertices.size()];
-                for (int i = 0; i < vertices.size(); ++i) {
-                    indices[i] = i;
-                }
-                mesh.setBuffer(Type.Index, 1, indices);
-            }
-
-            LOGGER.fine("Creating vertices buffer.");
-            VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
-            verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()])));
-            mesh.setBuffer(verticesBuffer);
-
-            LOGGER.fine("Creating normals buffer (in case of lines it is required if skeleton is applied).");
-            VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
-            normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()])));
-            mesh.setBuffer(normalsBuffer);
-            
-            result.put(-1, mesh);
-        }
-        return result;
-    }
-
-    /**
-     * @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
-     */
-    public boolean isEmpty() {
-        return vertices == null;
-    }
-    
-    public Map<Integer, List<Integer>> getGlobalVertexReferenceMap() {
-        return globalVertexReferenceMap;
-    }
-    
-    /**
-     * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
-     * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key
-     * - the reference indices list.
-     * 
-     * @param basicVertexIndex
-     *            the index of the vertex from its basic table
-     * @param resultIndex
-     *            the index of the vertex in its result vertex list
-     * @param vertexReferenceMap
-     *            the reference map
-     */
-    private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {
-        List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
-        if (referenceList == null) {
-            referenceList = new ArrayList<Integer>();
-            vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
-        }
-        referenceList.add(Integer.valueOf(resultIndex));
-    }
-}

+ 0 - 161
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/MeshBuilder.java

@@ -1,161 +0,0 @@
-package com.jme3.scene.plugins.blender.meshes.builders;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import com.jme3.math.Vector2f;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Mesh;
-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.MaterialContext;
-
-public class MeshBuilder {
-    private boolean          fixUpAxis;
-    private PointMeshBuilder pointMeshBuilder;
-    private LineMeshBuilder  lineMeshBuilder;
-    private FaceMeshBuilder  faceMeshBuilder;
-    /**
-     * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
-     * positions (it simply tells which vertex is referenced where in the result list).
-     */
-    private Map<Integer, Map<Integer, List<Integer>>> globalVertexReferenceMap = new HashMap<Integer, Map<Integer,List<Integer>>>();
-    
-    public MeshBuilder(Structure meshStructure, MaterialContext[] materials, BlenderContext blenderContext) throws BlenderFileException {
-        fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
-        Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(meshStructure);
-
-        faceMeshBuilder = new FaceMeshBuilder(verticesAndNormals, this.areGeneratedTexturesPresent(materials));
-        faceMeshBuilder.readMesh(meshStructure, blenderContext);
-        lineMeshBuilder = new LineMeshBuilder(verticesAndNormals);
-        lineMeshBuilder.readMesh(meshStructure);
-        pointMeshBuilder = new PointMeshBuilder(verticesAndNormals);
-        pointMeshBuilder.readMesh(meshStructure);
-    }
-
-    public Map<Integer, List<Mesh>> buildMeshes() {
-        Map<Integer, List<Mesh>> result = new HashMap<Integer, List<Mesh>>();
-
-        Map<Integer, Mesh> meshes = faceMeshBuilder.buildMeshes();
-        for (Entry<Integer, Mesh> entry : meshes.entrySet()) {
-            List<Mesh> meshList = new ArrayList<Mesh>();
-            meshList.add(entry.getValue());
-            result.put(entry.getKey(), meshList);
-        }
-
-        meshes = lineMeshBuilder.buildMeshes();
-        for (Entry<Integer, Mesh> entry : meshes.entrySet()) {
-            List<Mesh> meshList = result.get(entry.getKey());
-            if (meshList == null) {
-                meshList = new ArrayList<Mesh>();
-                result.put(entry.getKey(), meshList);
-            }
-            meshList.add(entry.getValue());
-        }
-
-        meshes = pointMeshBuilder.buildMeshes();
-        for (Entry<Integer, Mesh> entry : meshes.entrySet()) {
-            List<Mesh> meshList = result.get(entry.getKey());
-            if (meshList == null) {
-                meshList = new ArrayList<Mesh>();
-                result.put(entry.getKey(), meshList);
-            }
-            meshList.add(entry.getValue());
-        }
-
-        globalVertexReferenceMap.putAll(faceMeshBuilder.getVertexReferenceMap());
-        globalVertexReferenceMap.put(-1, lineMeshBuilder.getGlobalVertexReferenceMap());
-        globalVertexReferenceMap.get(-1).putAll(pointMeshBuilder.getGlobalVertexReferenceMap());
-        return result;
-    }
-
-    /**
-     * @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
-     */
-    public boolean isEmpty() {
-        return faceMeshBuilder.isEmpty() && lineMeshBuilder.isEmpty() && pointMeshBuilder.isEmpty();
-    }
-
-    /**
-     * @return a map that maps vertex index from reference array to its indices in the result list
-     */
-    public Map<Integer, Map<Integer, List<Integer>>> getVertexReferenceMap() {
-        return globalVertexReferenceMap;
-    }
-
-    /**
-     * @param materialNumber
-     *            the material number that is appied to the mesh
-     * @return UV coordinates of vertices that belong to the required mesh part
-     */
-    public LinkedHashMap<String, List<Vector2f>> getUVCoordinates(int materialNumber) {
-        return faceMeshBuilder.getUVCoordinates(materialNumber);
-    }
-
-    /**
-     * @return indicates if the mesh has UV coordinates
-     */
-    public boolean hasUVCoordinates() {
-        return faceMeshBuilder.hasUVCoordinates();
-    }
-
-    /**
-     * This method returns the vertices.
-     * 
-     * @param meshStructure
-     *            the structure containing the mesh data
-     * @return a list of two - element arrays, the first element is the vertex and the second - its normal
-     * @throws BlenderFileException
-     *             this exception is thrown when the blend file structure is somehow invalid or corrupted
-     */
-    @SuppressWarnings("unchecked")
-    private Vector3f[][] getVerticesAndNormals(Structure meshStructure) throws BlenderFileException {
-        int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
-        Vector3f[][] result = new Vector3f[count][2];
-        if (count == 0) {
-            return result;
-        }
-
-        Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
-        List<Structure> mVerts = pMVert.fetchData();
-        if (fixUpAxis) {
-            for (int i = 0; i < count; ++i) {
-                DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
-                result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
-
-                DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
-                result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f);
-            }
-        } else {
-            for (int i = 0; i < count; ++i) {
-                DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
-                result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
-
-                DynamicArray<Number> normals = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
-                result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * @return <b>true</b> if the material has at least one generated component and <b>false</b> otherwise
-     */
-    private boolean areGeneratedTexturesPresent(MaterialContext[] materials) {
-        if (materials != null) {
-            for (MaterialContext material : materials) {
-                if (material != null && material.hasGeneratedTextures()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-}

+ 0 - 171
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/builders/PointMeshBuilder.java

@@ -1,171 +0,0 @@
-package com.jme3.scene.plugins.blender.meshes.builders;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Logger;
-
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.Mesh.Mode;
-import com.jme3.scene.VertexBuffer;
-import com.jme3.scene.VertexBuffer.Format;
-import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.VertexBuffer.Usage;
-import com.jme3.scene.plugins.blender.file.BlenderFileException;
-import com.jme3.scene.plugins.blender.file.Pointer;
-import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.util.BufferUtils;
-
-/**
- * A builder that creates a points mesh. The result is made of points that do not belong to any edge and face.
- * 
- * @author Marcin Roguski (Kaelthas)
- */
-/* package */class PointMeshBuilder {
-    private static final Logger LOGGER   = Logger.getLogger(PointMeshBuilder.class.getName());
-
-    /** An array of reference vertices. */
-    private Vector3f[][]        verticesAndNormals;
-    /** The vertices of the mesh. */
-    private List<Vector3f>      vertices = new ArrayList<Vector3f>();
-    /** The normals of the mesh. */
-    private List<Vector3f>      normals              = new ArrayList<Vector3f>();
-    
-    /**
-     * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList'
-     * positions (it simply tells which vertex is referenced where in the result list).
-     */
-    private Map<Integer, List<Integer>> globalVertexReferenceMap;
-    
-    /**
-     * Constructor. Stores the given array (not copying it).
-     * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied.
-     * The amount of vertices is always faceCount * 3.
-     * @param verticesAndNormals
-     *            the reference vertices and normals array
-     */
-    public PointMeshBuilder(Vector3f[][] verticesAndNormals) {
-        this.verticesAndNormals = verticesAndNormals;
-        globalVertexReferenceMap = new HashMap<Integer, List<Integer>>(verticesAndNormals.length);
-    }
-
-    /**
-     * The method reads the mesh. Since blender does not store the information in the vertex itself whether it belongs
-     * anywhere or not, we need to check all vertices and use here only those that are not used.
-     * @param meshStructure
-     *            the mesh structure
-     * @throws BlenderFileException
-     *             an exception thrown when reading from the blend file fails
-     */
-    public void readMesh(Structure meshStructure) throws BlenderFileException {
-        LOGGER.fine("Reading points mesh.");
-        Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
-
-        if (pMEdge.isNotNull()) {
-            int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
-            Set<Vector3f> usedVertices = new HashSet<Vector3f>(count);
-            List<Structure> edges = pMEdge.fetchData();
-
-            for (Structure edge : edges) {
-                int v1 = ((Number) edge.getFieldValue("v1")).intValue();
-                int v2 = ((Number) edge.getFieldValue("v2")).intValue();
-                usedVertices.add(verticesAndNormals[v1][0]);
-                usedVertices.add(verticesAndNormals[v2][0]);
-            }
-
-            if (usedVertices.size() < count) {
-                vertices = new ArrayList<Vector3f>(count - usedVertices.size());
-                int vertexIndex = 0, blenderVertexIndex = 0;
-                for (Vector3f[] vertAndNormal : verticesAndNormals) {
-                    if (!usedVertices.contains(vertAndNormal[0])) {
-                        vertices.add(vertAndNormal[0]);
-                        normals.add(vertAndNormal[1]);
-                        this.appendVertexReference(blenderVertexIndex, vertexIndex++, globalVertexReferenceMap);
-                    }
-                    ++blenderVertexIndex;
-                }
-            }
-        }
-    }
-
-    /**
-     * Builds the meshes.
-     * @return a map between material index and the mesh
-     */
-    public Map<Integer, Mesh> buildMeshes() {
-        LOGGER.fine("Building point mesh.");
-        Map<Integer, Mesh> result = new HashMap<Integer, Mesh>(1);
-
-        if (vertices.size() > 0) {
-            Mesh mesh = new Mesh();
-            mesh.setMode(Mode.Points);
-            mesh.setPointSize(3);
-
-            // the point mesh does not need index buffer, but some modifiers applied by importer need it
-            // the 'alone point' situation should be quite rare so not too many resources are wasted here
-            LOGGER.fine("Creating indices buffer.");
-            if (vertices.size() <= Short.MAX_VALUE) {
-                short[] indices = new short[vertices.size()];
-                for (int i = 0; i < vertices.size(); ++i) {
-                    indices[i] = (short) i;
-                }
-                mesh.setBuffer(Type.Index, 1, indices);
-            } else {
-                int[] indices = new int[vertices.size()];
-                for (int i = 0; i < vertices.size(); ++i) {
-                    indices[i] = i;
-                }
-                mesh.setBuffer(Type.Index, 1, indices);
-            }
-
-            LOGGER.fine("Creating vertices buffer.");
-            VertexBuffer verticesBuffer = new VertexBuffer(Type.Position);
-            verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(vertices.toArray(new Vector3f[vertices.size()])));
-            mesh.setBuffer(verticesBuffer);
-            
-            LOGGER.fine("Creating normals buffer (in case of points it is required if skeleton is applied).");
-            VertexBuffer normalsBuffer = new VertexBuffer(Type.Normal);
-            normalsBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(normals.toArray(new Vector3f[normals.size()])));
-            mesh.setBuffer(normalsBuffer);
-
-            result.put(-1, mesh);
-        }
-        return result;
-    }
-
-    /**
-     * @return <b>true</b> if the mesh has no vertices and <b>false</b> otherwise
-     */
-    public boolean isEmpty() {
-        return vertices == null;
-    }
-    
-    public Map<Integer, List<Integer>> getGlobalVertexReferenceMap() {
-        return globalVertexReferenceMap;
-    }
-    
-    /**
-     * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created
-     * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key
-     * - the reference indices list.
-     * 
-     * @param basicVertexIndex
-     *            the index of the vertex from its basic table
-     * @param resultIndex
-     *            the index of the vertex in its result vertex list
-     * @param vertexReferenceMap
-     *            the reference map
-     */
-    private void appendVertexReference(int basicVertexIndex, int resultIndex, Map<Integer, List<Integer>> vertexReferenceMap) {
-        List<Integer> referenceList = vertexReferenceMap.get(Integer.valueOf(basicVertexIndex));
-        if (referenceList == null) {
-            referenceList = new ArrayList<Integer>();
-            vertexReferenceMap.put(Integer.valueOf(basicVertexIndex), referenceList);
-        }
-        referenceList.add(Integer.valueOf(resultIndex));
-    }
-}

+ 27 - 313
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java

@@ -1,42 +1,20 @@
 package com.jme3.scene.plugins.blender.modifiers;
 
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
 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 com.jme3.animation.Bone;
 import com.jme3.animation.Skeleton;
-import com.jme3.math.Matrix4f;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.VertexBuffer;
-import com.jme3.scene.VertexBuffer.Format;
-import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.VertexBuffer.Usage;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
 import com.jme3.scene.plugins.blender.animations.AnimationHelper;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
-import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
-import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.blender.meshes.MeshContext;
-import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
-import com.jme3.util.BufferUtils;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 
 /**
  * This modifier allows to add bone animation to the object.
@@ -44,23 +22,12 @@ import com.jme3.util.BufferUtils;
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ArmatureModifier extends Modifier {
-    private static final Logger LOGGER                     = Logger.getLogger(ArmatureModifier.class.getName());
-    private static final int    MAXIMUM_WEIGHTS_PER_VERTEX = 4;                                                 // JME
+    private static final Logger LOGGER              = Logger.getLogger(ArmatureModifier.class.getName());
 
-    private static final int    FLAG_VERTEX_GROUPS         = 0x01;
-    private static final int    FLAG_BONE_ENVELOPES        = 0x02;
+    private static final int    FLAG_VERTEX_GROUPS  = 0x01;
+    private static final int    FLAG_BONE_ENVELOPES = 0x02;
 
-    private Structure           armatureObject;
     private Skeleton            skeleton;
-    private Structure           meshStructure;
-    /** The wold transform matrix of the armature object. */
-    private Matrix4f            objectWorldMatrix;
-    /** Old memory address of the mesh that will have the skeleton applied. */
-    private Long                meshOMA;
-    /** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
-    private boolean             useVertexGroups;
-    /** The variable tells if the bones' envelopes should be used to assign verts to bones. */
-    private boolean             useBoneEnvelopes;
 
     /**
      * This constructor reads animation data from the object structore. The
@@ -77,17 +44,16 @@ import com.jme3.util.BufferUtils;
      *             corrupted
      */
     public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
-        Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0);
         if (this.validate(modifierStructure, blenderContext)) {
             Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
             if (pArmatureObject.isNotNull()) {
                 int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
-                useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
-                useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
+                boolean useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
+                boolean useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
                 modifying = useBoneEnvelopes || useVertexGroups;
                 if (modifying) {// if neither option is used the modifier will not modify anything anyway
-                    armatureObject = pArmatureObject.fetchData().get(0);
-
+                    Structure armatureObject = pArmatureObject.fetchData().get(0);
+                    
                     // load skeleton
                     Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
                     List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
@@ -99,16 +65,6 @@ import com.jme3.util.BufferUtils;
                     Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
                     skeleton = new Skeleton(bones);
                     blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
-                    this.meshStructure = meshStructure;
-
-                    // read mesh indexes
-                    meshOMA = meshStructure.getOldMemoryAddress();
-
-                    if (useBoneEnvelopes) {
-                        ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-                        Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
-                        objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
-                    }
                 }
             } else {
                 modifying = false;
@@ -116,280 +72,38 @@ import com.jme3.util.BufferUtils;
         }
     }
 
-    /**
-     * This method builds the object's bones structure.
-     * 
-     * @param armatureObjectOMA
-     *            the OMa of the armature node
-     * @param boneStructure
-     *            the structure containing the bones' data
-     * @param parent
-     *            the parent bone
-     * @param result
-     *            the list where the newly created bone will be added
-     * @param spatialOMA
-     *            the OMA of the spatial that will own the skeleton
-     * @param blenderContext
-     *            the blender context
-     * @throws BlenderFileException
-     *             an exception is thrown when there is problem with the blender
-     *             file
-     */
     private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
         BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
         bc.buildBone(result, spatialOMA, blenderContext);
     }
+    
+    @Override
+    public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
+        LOGGER.fine("Applying armature modifier after mesh has been created.");
+        AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
+        animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
+        node.updateModelBound();
+    }
 
     @Override
-    @SuppressWarnings("unchecked")
     public void apply(Node node, BlenderContext blenderContext) {
         if (invalid) {
             LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
-        }// if invalid, animData will be null
-        if (skeleton != null) {
-            // setting weights for bones
-            List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
-            MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
-            for (Geometry geom : geomList) {
-                int materialIndex = meshContext.getMaterialIndex(geom);
-                Mesh mesh = geom.getMesh();
-
-                MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
-                if (buffers != null) {
-                    mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
-                    mesh.setBuffer(buffers.verticesWeights);
-                    mesh.setBuffer(buffers.verticesWeightsIndices);
-
-                    LOGGER.fine("Generating bind pose and normal buffers.");
-                    mesh.generateBindPose(true);
-
-                    // change the usage type of vertex and normal buffers from
-                    // Static to Stream
-                    mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
-                    mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
-
-                    // creating empty buffers for HW skinning
-                    // the buffers will be setup if ever used.
-                    VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
-                    VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
-                    mesh.setBuffer(verticesWeightsHW);
-                    mesh.setBuffer(verticesWeightsIndicesHW);
-                }
-            }
-
-            AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
-            animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
-            node.updateModelBound();
         }
-    }
-
-    /**
-     * Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
-     * 
-     * @param meshContext
-     *            the mesh context
-     * @param skeleton
-     *            the current skeleton
-     * @param materialIndex
-     *            the material index
-     * @param mesh
-     *            the mesh we create the buffers for
-     * @param blenderContext
-     *            the blender context
-     * @return an instance that aggregates all needed data for the mesh
-     */
-    private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
-        int vertexListSize = meshContext.getVertexCount(materialIndex);
-        Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
-
-        Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
-        Buffer indexes = mesh.getBuffer(Type.Index).getData();
-        FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
 
-        int maximumWeightsPerVertex = 0;
-        if (useVertexGroups) {
-            LOGGER.fine("Attaching verts to bones using vertex groups.");
-            for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
-                Bone bone = skeleton.getBone(boneIndex);
-                VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
-                if (vertexGroup != null) {
-                    vertexGroup.setBoneIndex(boneIndex);
-                    vertexGroups.put(bone.getName(), vertexGroup);
-                }
-            }
-        }
-
-        if (useBoneEnvelopes) {
-            LOGGER.fine("Attaching verts to bones using bone envelopes.");
-            Vector3f pos = new Vector3f();
-
-            for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
-                Bone bone = skeleton.getBone(boneIndex);
-                BoneContext boneContext = blenderContext.getBoneContext(bone);
-                BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
-                if (boneEnvelope != null) {
-                    VertexGroup vertexGroup = vertexGroups.get(bone.getName());
-                    if (vertexGroup == null) {
-                        vertexGroup = new VertexGroup();
-                        vertexGroups.put(bone.getName(), vertexGroup);
-                    }
-                    vertexGroup.setBoneIndex(boneIndex);
-
-                    for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
-                        List<Integer> vertexIndices = entry.getValue();
-                        for (int j = 0; j < indexes.limit(); ++j) {
-                            int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
-                            if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
-                                int ii = index * 3;
-                                pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
-                                // move the vertex to the global space position
-                                objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
-                                if (boneEnvelope.isInEnvelope(pos)) {
-                                    vertexGroup.addVertex(index, boneEnvelope.getWeight());
-                                } else if (boneIndex == 5) {
-                                    System.out.println("Si nie zaapa: " + pos);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
-        if (vertexGroups.size() > 0) {
-            LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
-            for (VertexGroup vertexGroup : vertexGroups.values()) {
-                for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
-                    WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
-                    if (vertexWeights == null) {
-                        vertexWeights = new WeightsAndBoneIndexes();
-                        weights.put(entry.getKey(), vertexWeights);
-                    }
-                    vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
-                }
-            }
-
-            LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
-            for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
-                maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
-                entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
-            }
-
-            if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
-                LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
-                maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
-            }
-        }
-
-        if(maximumWeightsPerVertex == 0) {
-            LOGGER.fine("No vertex group data nor bone envelopes found to attach vertices to bones!");
-            return null;
-        }
-        
-        LOGGER.fine("Preparing buffers for the mesh.");
-        FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
-        ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
-        for (int i = 0; i < indexes.limit(); ++i) {
-            int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
-            WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
-            if (weightsAndBoneIndexes != null) {
-                int count = 0;
-                for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
-                    weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
-                    indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
-                    ++count;
+        if (modifying) {
+            TemporalMesh temporalMesh = this.getTemporalMesh(node);
+            if (temporalMesh != null) {
+                LOGGER.log(Level.FINE, "Applying armature modifier to: {0}", temporalMesh);
+                
+                LOGGER.fine("Creating map between bone name and its index.");
+                for (int i = 0; i < skeleton.getBoneCount(); ++i) {
+                    Bone bone = skeleton.getBone(i);
+                    temporalMesh.addBoneIndex(bone.getName(), i);
                 }
+                temporalMesh.applyAfterMeshCreate(this);
             } else {
-                // if no bone is assigned to this vertex then attach it to the 0-indexed root bone
-                weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
-                indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
-            }
-        }
-        VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
-        verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
-
-        VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
-        verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
-
-        return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
-    }
-
-    /**
-     * A class that gathers the data for mesh bone buffers.
-     * Added to increase code readability.
-     * 
-     * @author Marcin Roguski (Kaelthas)
-     */
-    private static class MeshWeightsData {
-        public final int          maximumWeightsPerVertex;
-        public final VertexBuffer verticesWeights;
-        public final VertexBuffer verticesWeightsIndices;
-
-        public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
-            this.maximumWeightsPerVertex = maximumWeightsPerVertex;
-            this.verticesWeights = verticesWeights;
-            this.verticesWeightsIndices = verticesWeightsIndices;
-        }
-    }
-
-    /**
-     * A map between the bone index and the bone's weight.
-     * 
-     * @author Marcin Roguski (Kaelthas)
-     */
-    private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
-        private static final long serialVersionUID = 2754299007299077459L;
-
-        /**
-         * The method normalizes the weights and bone indexes data.
-         * First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
-         * Next it normalizes the weights so that the sum of all verts is 1.
-         * @param maximumSize
-         *            the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
-         */
-        public void normalize(int maximumSize) {
-            if (this.size() > maximumSize) {// select only the most significant weights
-                float lowestWeight = Float.MAX_VALUE;
-                int lowestWeightIndex = -1;
-                HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
-                for (Entry<Integer, Float> entry : this.entrySet()) {
-                    if (msw.size() < maximumSize) {
-                        msw.put(entry.getKey(), entry.getValue());
-                        if (entry.getValue() < lowestWeight) {
-                            lowestWeight = entry.getValue();
-                            lowestWeightIndex = entry.getKey();
-                        }
-                    } else if (entry.getValue() > lowestWeight) {
-                        msw.remove(lowestWeightIndex);
-                        msw.put(lowestWeightIndex, lowestWeight);
-
-                        // search again for the lowest weight
-                        lowestWeight = Float.MAX_VALUE;
-                        for (Entry<Integer, Float> e : msw.entrySet()) {
-                            if (e.getValue() < lowestWeight) {
-                                lowestWeight = e.getValue();
-                                lowestWeightIndex = e.getKey();
-                            }
-                        }
-                    }
-                }
-
-                // replace current weights with the given ones
-                this.clear();
-                this.putAll(msw);
-            }
-
-            // normalizing the weights so that the sum of the values is equal to '1'
-            float sum = 0;
-            for (Entry<Integer, Float> entry : this.entrySet()) {
-                sum += entry.getValue();
-            }
-
-            if (sum != 0 && sum != 1) {
-                for (Entry<Integer, Float> entry : this.entrySet()) {
-                    entry.setValue(entry.getValue() / sum);
-                }
+                LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
             }
         }
     }

+ 109 - 128
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java

@@ -1,5 +1,11 @@
 package com.jme3.scene.plugins.blender.modifiers;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import com.jme3.bounding.BoundingBox;
 import com.jme3.bounding.BoundingSphere;
 import com.jme3.bounding.BoundingVolume;
@@ -9,32 +15,32 @@ import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 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.meshes.MeshHelper;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
 import com.jme3.scene.shape.Curve;
 
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 /**
  * This modifier allows to array modifier to the object.
  * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ArrayModifier extends Modifier {
-    private static final Logger LOGGER       = Logger.getLogger(ArrayModifier.class.getName());
+    private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());
 
-    /** Parameters of the modifier. */
-    private Map<String, Object> modifierData = new HashMap<String, Object>();
+    private int                 fittype;
+    private int                 count;
+    private float               length;
+    private float[]             offset;
+    private float[]             scale;
+    private Pointer             pOffsetObject;
+    private Pointer             pStartCap;
+    private Pointer             pEndCap;
 
     /**
      * This constructor reads array data from the modifier structure. The
@@ -54,18 +60,16 @@ import java.util.logging.Logger;
     @SuppressWarnings("unchecked")
     public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
         if (this.validate(modifierStructure, blenderContext)) {
-            Number fittype = (Number) modifierStructure.getFieldValue("fit_type");
-            modifierData.put("fittype", fittype);
-            switch (fittype.intValue()) {
+            fittype = ((Number) modifierStructure.getFieldValue("fit_type")).intValue();
+            switch (fittype) {
                 case 0:// FIXED COUNT
-                    modifierData.put("count", modifierStructure.getFieldValue("count"));
+                    count = ((Number) modifierStructure.getFieldValue("count")).intValue();
                     break;
                 case 1:// FIXED LENGTH
-                    modifierData.put("length", modifierStructure.getFieldValue("length"));
+                    length = ((Number) modifierStructure.getFieldValue("length")).floatValue();
                     break;
                 case 2:// FITCURVE
                     Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob");
-                    float length = 0;
                     if (pCurveOb.isNotNull()) {
                         Structure curveStructure = pCurveOb.fetchData().get(0);
                         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
@@ -88,8 +92,7 @@ import java.util.logging.Logger;
                             }
                         }
                     }
-                    modifierData.put("length", Float.valueOf(length));
-                    modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH
+                    fittype = 1;// treat it like FIXED LENGTH
                     break;
                 default:
                     assert false : "Unknown array modifier fit type: " + fittype;
@@ -99,30 +102,19 @@ import java.util.logging.Logger;
             int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue();
             if ((offsettype & 0x01) != 0) {// Constant offset
                 DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset");
-                float[] offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
-                modifierData.put("offset", offset);
+                offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
             }
             if ((offsettype & 0x02) != 0) {// Relative offset
                 DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale");
-                float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
-                modifierData.put("scale", scale);
+                scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
             }
             if ((offsettype & 0x04) != 0) {// Object offset
-                Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
-                if (pOffsetObject.isNotNull()) {
-                    modifierData.put("offsetob", pOffsetObject);
-                }
+                pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
             }
 
             // start cap and end cap
-            Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
-            if (pStartCap.isNotNull()) {
-                modifierData.put("startcap", pStartCap);
-            }
-            Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
-            if (pEndCap.isNotNull()) {
-                modifierData.put("endcap", pEndCap);
-            }
+            pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
+            pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
         }
     }
 
@@ -131,116 +123,105 @@ import java.util.logging.Logger;
         if (invalid) {
             LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName());
         } else {
-            int fittype = ((Number) modifierData.get("fittype")).intValue();
-            float[] offset = (float[]) modifierData.get("offset");
-            if (offset == null) {// the node will be repeated several times in the same place
-                offset = new float[] { 0.0f, 0.0f, 0.0f };
-            }
-            float[] scale = (float[]) modifierData.get("scale");
-            if (scale == null) {// the node will be repeated several times in the same place
-                scale = new float[] { 0.0f, 0.0f, 0.0f };
-            } else {
-                // getting bounding box
-                node.updateModelBound();
-                BoundingVolume boundingVolume = node.getWorldBound();
-                if (boundingVolume instanceof BoundingBox) {
-                    scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
-                    scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
-                    scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
-                } else if (boundingVolume instanceof BoundingSphere) {
-                    float radius = ((BoundingSphere) boundingVolume).getRadius();
-                    scale[0] *= radius * 2.0f;
-                    scale[1] *= radius * 2.0f;
-                    scale[2] *= radius * 2.0f;
+            TemporalMesh temporalMesh = this.getTemporalMesh(node);
+            if (temporalMesh != null) {
+                LOGGER.log(Level.FINE, "Applying array modifier to: {0}", temporalMesh);
+                if (offset == null) {// the node will be repeated several times in the same place
+                    offset = new float[] { 0.0f, 0.0f, 0.0f };
+                }
+                if (scale == null) {// the node will be repeated several times in the same place
+                    scale = new float[] { 0.0f, 0.0f, 0.0f };
                 } else {
-                    throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
+                    // getting bounding box
+                    temporalMesh.updateModelBound();
+                    BoundingVolume boundingVolume = temporalMesh.getWorldBound();
+                    if (boundingVolume instanceof BoundingBox) {
+                        scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
+                        scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
+                        scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
+                    } else if (boundingVolume instanceof BoundingSphere) {
+                        float radius = ((BoundingSphere) boundingVolume).getRadius();
+                        scale[0] *= radius * 2.0f;
+                        scale[1] *= radius * 2.0f;
+                        scale[2] *= radius * 2.0f;
+                    } else {
+                        throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
+                    }
                 }
-            }
-    
-            // adding object's offset
-            float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
-            Pointer pOffsetObject = (Pointer) modifierData.get("offsetob");
-            if (pOffsetObject != null) {
-                FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
-                ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                try {// we take the structure in case the object was not yet loaded
-                    Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);
-                    Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();
-                    objectOffset[0] = translation.x;
-                    objectOffset[1] = translation.y;
-                    objectOffset[2] = translation.z;
-                } catch (BlenderFileException e) {
-                    LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
+
+                // adding object's offset
+                float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
+                if (pOffsetObject != null && pOffsetObject.isNotNull()) {
+                    FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
+                    ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
+                    try {// we take the structure in case the object was not yet loaded
+                        Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);
+                        Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();
+                        objectOffset[0] = translation.x;
+                        objectOffset[1] = translation.y;
+                        objectOffset[2] = translation.z;
+                    } catch (BlenderFileException e) {
+                        LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
+                    }
                 }
-            }
-    
-            // getting start and end caps
-            Node[] caps = new Node[] { null, null };
-            Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") };
-            for (int i = 0; i < pCaps.length; ++i) {
-                if (pCaps[i] != null) {
-                    caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
-                    if (caps[i] != null) {
-                        caps[i] = (Node) caps[i].clone();
-                    } else {
-                        FileBlockHeader capBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
+
+                // getting start and end caps
+                MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
+                TemporalMesh[] caps = new TemporalMesh[] { null, null };
+                Pointer[] pCaps = new Pointer[] { pStartCap, pEndCap };
+                for (int i = 0; i < pCaps.length; ++i) {
+                    if (pCaps[i].isNotNull()) {
+                        FileBlockHeader capBlock = blenderContext.getFileBlock(pCaps[i].getOldMemoryAddress());
                         try {// we take the structure in case the object was not yet loaded
                             Structure capStructure = capBlock.getStructure(blenderContext);
-                            ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                            caps[i] = (Node) objectHelper.toObject(capStructure, blenderContext);
-                            if (caps[i] == null) {
-                                LOGGER.log(Level.WARNING, "Cap object ''{0}'' couldn''t be loaded!", capStructure.getName());
-                            }
+                            Pointer pMesh = (Pointer) capStructure.getFieldValue("data");
+                            List<Structure> meshesArray = pMesh.fetchData();
+                            caps[i] = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
                         } catch (BlenderFileException e) {
                             LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage());
                         }
                     }
                 }
-            }
-    
-            Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
-            if(blenderContext.getBlenderKey().isFixUpAxis()) {
-                float y = translationVector.y;
-                translationVector.y = translationVector.z;
-                translationVector.z = y == 0 ? 0 : -y;
-            }
-            
-            // getting/calculating repeats amount
-            int count = 0;
-            if (fittype == 0) {// Fixed count
-                count = ((Number) modifierData.get("count")).intValue() - 1;
-            } else if (fittype == 1) {// Fixed length
-                float length = ((Number) modifierData.get("length")).floatValue();
-                if (translationVector.length() > 0.0f) {
-                    count = (int) (length / translationVector.length()) - 1;
+
+                Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
+                if (blenderContext.getBlenderKey().isFixUpAxis()) {
+                    float y = translationVector.y;
+                    translationVector.y = translationVector.z;
+                    translationVector.z = y == 0 ? 0 : -y;
                 }
-            } else if (fittype == 2) {// Fit curve
-                throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
-            } else {
-                throw new IllegalStateException("Unknown fit type: " + fittype);
-            }
-    
-            // adding translated nodes and caps
-            if (count > 0) {
-                Node[] arrayNodes = new Node[count];
-                Vector3f newTranslation = new Vector3f();
-                for (int i = 0; i < count; ++i) {
-                    newTranslation.addLocal(translationVector);
-                    Node nodeClone = (Node) node.clone();
-                    nodeClone.setLocalTranslation(newTranslation);
-                    arrayNodes[i] = nodeClone;
+
+                // getting/calculating repeats amount
+                int count = 0;
+                if (fittype == 0) {// Fixed count
+                    count = this.count - 1;
+                } else if (fittype == 1) {// Fixed length
+                    float length = this.length;
+                    if (translationVector.length() > 0.0f) {
+                        count = (int) (length / translationVector.length()) - 1;
+                    }
+                } else if (fittype == 2) {// Fit curve
+                    throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
+                } else {
+                    throw new IllegalStateException("Unknown fit type: " + fittype);
                 }
-                for (Node nodeClone : arrayNodes) {
-                    node.attachChild(nodeClone);
+
+                // adding translated nodes and caps
+                Vector3f totalTranslation = new Vector3f(translationVector);
+                if (count > 0) {
+                    TemporalMesh originalMesh = temporalMesh.clone();
+                    for (int i = 0; i < count; ++i) {
+                        temporalMesh.append(originalMesh.clone().translate(totalTranslation));
+                        totalTranslation.addLocal(translationVector);
+                    }
                 }
                 if (caps[0] != null) {
-                    caps[0].getLocalTranslation().set(node.getLocalTranslation()).subtractLocal(translationVector);
-                    node.attachChild(caps[0]);
+                    temporalMesh.append(caps[0].clone().translate(translationVector.multLocal(-1)));
                 }
                 if (caps[1] != null) {
-                    caps[1].getLocalTranslation().set(newTranslation).addLocal(translationVector);
-                    node.attachChild(caps[1]);
+                    temporalMesh.append(caps[1].clone().translate(totalTranslation));
                 }
+            } else {
+                LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
             }
         }
     }

+ 71 - 195
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java

@@ -1,27 +1,16 @@
 package com.jme3.scene.plugins.blender.modifiers;
 
-import java.nio.Buffer;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Vector3f;
-import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
-import com.jme3.scene.Spatial;
-import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.objects.ObjectHelper;
 
 /**
@@ -30,21 +19,22 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class MirrorModifier extends Modifier {
-    private static final Logger LOGGER            = Logger.getLogger(MirrorModifier.class.getName());
+    private static final Logger LOGGER                   = Logger.getLogger(MirrorModifier.class.getName());
 
-    private static final int    FLAG_MIRROR_X     = 0x08;
-    private static final int    FLAG_MIRROR_Y     = 0x10;
-    private static final int    FLAG_MIRROR_Z     = 0x20;
-    private static final int    FLAG_MIRROR_U     = 0x02;
-    private static final int    FLAG_MIRROR_V     = 0x04;
-    // private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40;
-    private static final int    FLAG_MIRROR_MERGE = 0x80;
+    private static final int    FLAG_MIRROR_X            = 0x08;
+    private static final int    FLAG_MIRROR_Y            = 0x10;
+    private static final int    FLAG_MIRROR_Z            = 0x20;
+    private static final int    FLAG_MIRROR_U            = 0x02;
+    private static final int    FLAG_MIRROR_V            = 0x04;
+    private static final int    FLAG_MIRROR_VERTEX_GROUP = 0x40;
+    private static final int    FLAG_MIRROR_MERGE        = 0x80;
 
     private boolean[]           isMirrored;
     private boolean             mirrorU, mirrorV;
     private boolean             merge;
     private float               tolerance;
     private Pointer             pMirrorObject;
+    private boolean             mirrorVGroup;
 
     /**
      * This constructor reads mirror data from the modifier structure. The
@@ -74,11 +64,15 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
             }
             mirrorU = (flag & FLAG_MIRROR_U) != 0;
             mirrorV = (flag & FLAG_MIRROR_V) != 0;
-            // boolean mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0;
+            mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0;
             merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake)
 
             tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue();
             pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob");
+            
+            if(mirrorVGroup) {
+                LOGGER.warning("Mirroring vertex groups is currently not supported.");
+            }
         }
     }
 
@@ -87,156 +81,75 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
         if (invalid) {
             LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());
         } else {
-            Vector3f mirrorPlaneCenter = new Vector3f();
-            if (pMirrorObject.isNotNull()) {
-                Structure objectStructure;
-                try {
-                    objectStructure = pMirrorObject.fetchData().get(0);
-                    ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-                    Node object = (Node) objectHelper.toObject(objectStructure, blenderContext);
-                    if (object != null) {
-                        // compute the mirror object coordinates in node's local space
-                        mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation());
+            TemporalMesh temporalMesh = this.getTemporalMesh(node);
+            if (temporalMesh != null) {
+                LOGGER.log(Level.FINE, "Applying mirror modifier to: {0}", temporalMesh);
+                Vector3f mirrorPlaneCenter = new Vector3f();
+                if (pMirrorObject.isNotNull()) {
+                    Structure objectStructure;
+                    try {
+                        objectStructure = pMirrorObject.fetchData().get(0);
+                        ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
+                        Node object = (Node) objectHelper.toObject(objectStructure, blenderContext);
+                        if (object != null) {
+                            // compute the mirror object coordinates in node's local space
+                            mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation());
+                        }
+                    } catch (BlenderFileException e) {
+                        LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());
+                        LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName());
+                        return;
                     }
-                } catch (BlenderFileException e) {
-                    LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());
-                    LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName());
-                    return;
                 }
-            }
-
-            LOGGER.finest("Allocating temporal variables.");
-            float d;
-            Vector3f mirrorPlaneNormal = new Vector3f();
-            Vector3f shiftVector = new Vector3f();
-            Vector3f point = new Vector3f();
-            Vector3f normal = new Vector3f();
-            Set<Integer> modifiedIndexes = new HashSet<Integer>();
-            List<Geometry> geometriesToAdd = new ArrayList<Geometry>();
-            final char[] mirrorNames = new char[] { 'X', 'Y', 'Z' };
-
-            LOGGER.fine("Mirroring mesh.");
-            for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
-                if (isMirrored[mirrorIndex]) {
-                    boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0;
-                    if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space
-                        mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex)));
-                    }
-
-                    for (Spatial spatial : node.getChildren()) {
-                        if (spatial instanceof Geometry) {
-                            Mesh mesh = ((Geometry) spatial).getMesh();
-                            Mesh clone = mesh.deepClone();
 
-                            LOGGER.log(Level.FINEST, "Fetching buffers of cloned spatial: {0}", spatial.getName());
-                            FloatBuffer position = mesh.getFloatBuffer(Type.Position);
-                            FloatBuffer bindPosePosition = mesh.getFloatBuffer(Type.BindPosePosition);
-
-                            FloatBuffer clonePosition = clone.getFloatBuffer(Type.Position);
-                            FloatBuffer cloneBindPosePosition = clone.getFloatBuffer(Type.BindPosePosition);
-                            FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal);
-                            FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal);
-                            Buffer cloneIndexes = clone.getBuffer(Type.Index).getData();
-
-                            for (int i = 0; i < cloneIndexes.limit(); ++i) {
-                                int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i);
-                                if (!modifiedIndexes.contains(index)) {
-                                    modifiedIndexes.add(index);
-
-                                    this.get(clonePosition, index, point);
-                                    if (mirrorAtPoint0) {
-                                        d = Math.abs(point.get(mirrorIndex));
-                                        shiftVector.set(0, 0, 0).set(mirrorIndex, -point.get(mirrorIndex));
-                                    } else {
-                                        d = this.computeDistanceFromPlane(point, mirrorPlaneCenter, mirrorPlaneNormal);
-                                        mirrorPlaneNormal.mult(d, shiftVector);
-                                    }
-
-                                    if (merge && d <= tolerance) {
-                                        point.addLocal(shiftVector);
-
-                                        this.set(index, point, clonePosition, cloneBindPosePosition, position, bindPosePosition);
-                                        if (cloneNormals != null) {
-                                            this.get(cloneNormals, index, normal);
-                                            normal.set(mirrorIndex, 0);
-                                            this.set(index, normal, cloneNormals, cloneBindPoseNormals);
-                                        }
-                                    } else {
-                                        point.addLocal(shiftVector.multLocal(2));
+                LOGGER.finest("Allocating temporal variables.");
+                float d;
+                Vector3f mirrorPlaneNormal = new Vector3f();
+                Vector3f shiftVector = new Vector3f();
+
+                LOGGER.fine("Mirroring mesh.");
+                for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
+                    if (isMirrored[mirrorIndex]) {
+                        boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0;
+                        if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space
+                            mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex)));
+                        }
 
-                                        this.set(index, point, clonePosition, cloneBindPosePosition);
-                                        if (cloneNormals != null) {
-                                            this.get(cloneNormals, index, normal);
-                                            normal.set(mirrorIndex, -normal.get(mirrorIndex));
-                                            this.set(index, normal, cloneNormals, cloneBindPoseNormals);
-                                        }
-                                    }
-                                }
+                        TemporalMesh mirror = temporalMesh.clone();
+                        for (int i = 0; i < mirror.getVertexCount(); ++i) {
+                            Vector3f vertex = mirror.getVertex(i);
+                            Vector3f normal = mirror.getNormal(i);
+
+                            if (mirrorAtPoint0) {
+                                d = Math.abs(vertex.get(mirrorIndex));
+                                shiftVector.set(0, 0, 0).set(mirrorIndex, -vertex.get(mirrorIndex));
+                            } else {
+                                d = this.computeDistanceFromPlane(vertex, mirrorPlaneCenter, mirrorPlaneNormal);
+                                mirrorPlaneNormal.mult(d, shiftVector);
                             }
-                            modifiedIndexes.clear();
 
-                            LOGGER.finer("Flipping index order.");
-                            switch (mesh.getMode()) {
-                                case Points:
-                                    cloneIndexes.flip();
-                                    break;
-                                case Lines:
-                                    for (int i = 0; i < cloneIndexes.limit(); i += 2) {
-                                        if (cloneIndexes instanceof ShortBuffer) {
-                                            short index = ((ShortBuffer) cloneIndexes).get(i + 1);
-                                            ((ShortBuffer) cloneIndexes).put(i + 1, ((ShortBuffer) cloneIndexes).get(i));
-                                            ((ShortBuffer) cloneIndexes).put(i, index);
-                                        } else {
-                                            int index = ((IntBuffer) cloneIndexes).get(i + 1);
-                                            ((IntBuffer) cloneIndexes).put(i + 1, ((IntBuffer) cloneIndexes).get(i));
-                                            ((IntBuffer) cloneIndexes).put(i, index);
-                                        }
-                                    }
-                                    break;
-                                case Triangles:
-                                    for (int i = 0; i < cloneIndexes.limit(); i += 3) {
-                                        if (cloneIndexes instanceof ShortBuffer) {
-                                            short index = ((ShortBuffer) cloneIndexes).get(i + 2);
-                                            ((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1));
-                                            ((ShortBuffer) cloneIndexes).put(i + 1, index);
-                                        } else {
-                                            int index = ((IntBuffer) cloneIndexes).get(i + 2);
-                                            ((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1));
-                                            ((IntBuffer) cloneIndexes).put(i + 1, index);
-                                        }
-                                    }
-                                    break;
-                                default:
-                                    throw new IllegalStateException("Invalid mesh mode: " + mesh.getMode());
+                            if (merge && d <= tolerance) {
+                                vertex.addLocal(shiftVector);
+                                normal.set(mirrorIndex, 0);
+                                temporalMesh.getVertex(i).addLocal(shiftVector);
+                                temporalMesh.getNormal(i).set(mirrorIndex, 0);
+                            } else {
+                                vertex.addLocal(shiftVector.multLocal(2));
+                                normal.set(mirrorIndex, -normal.get(mirrorIndex));
                             }
+                        }
 
-                            if (mirrorU && clone.getBuffer(Type.TexCoord) != null) {
-                                LOGGER.finer("Mirroring U coordinates.");
-                                FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
-                                for (int i = 0; i < cloneUVs.limit(); i += 2) {
-                                    cloneUVs.put(i, 1.0f - cloneUVs.get(i));
-                                }
-                            }
-                            if (mirrorV && clone.getBuffer(Type.TexCoord) != null) {
-                                LOGGER.finer("Mirroring V coordinates.");
-                                FloatBuffer cloneUVs = (FloatBuffer) clone.getBuffer(Type.TexCoord).getData();
-                                for (int i = 1; i < cloneUVs.limit(); i += 2) {
-                                    cloneUVs.put(i, 1.0f - cloneUVs.get(i));
-                                }
-                            }
+                        mirror.flipIndexes();
 
-                            Geometry geometry = new Geometry(spatial.getName() + " - mirror " + mirrorNames[mirrorIndex], clone);
-                            geometry.setMaterial(((Geometry) spatial).getMaterial());
-                            geometriesToAdd.add(geometry);
+                        if (mirrorU || mirrorV) {
+                            mirror.flipUV(mirrorU, mirrorV);
                         }
-                    }
 
-                    LOGGER.log(Level.FINE, "Adding {0} geometries to current node.", geometriesToAdd.size());
-                    for (Geometry geometry : geometriesToAdd) {
-                        node.attachChild(geometry);
+                        temporalMesh.append(mirror);
                     }
-                    geometriesToAdd.clear();
                 }
+            } else {
+                LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
             }
         }
     }
@@ -268,41 +181,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
     private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) {
         return Math.abs(n.dot(p) - c.dot(n));
     }
-
-    /**
-     * Sets the given value (v) into every of the buffers at the given index.
-     * The index is cosidered to be an index of a vertex of the mesh.
-     * @param index
-     *            the index of vertex of the mesh
-     * @param value
-     *            the value to be set
-     * @param buffers
-     *            the buffers where the value will be set
-     */
-    private void set(int index, Vector3f value, FloatBuffer... buffers) {
-        index *= 3;
-        for (FloatBuffer buffer : buffers) {
-            if (buffer != null) {
-                buffer.put(index, value.x);
-                buffer.put(index + 1, value.y);
-                buffer.put(index + 2, value.z);
-            }
-        }
-    }
-
-    /**
-     * Fetches the vector's value from the given buffer at specified index.
-     * @param buffer
-     *            the buffer we get the data from
-     * @param index
-     *            the index of vertex of the mesh
-     * @param store
-     *            the vector where the result will be set
-     */
-    private void get(FloatBuffer buffer, int index, Vector3f store) {
-        index *= 3;
-        store.x = buffer.get(index);
-        store.y = buffer.get(index + 1);
-        store.z = buffer.get(index + 2);
-    }
 }

+ 22 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java

@@ -1,9 +1,13 @@
 package com.jme3.scene.plugins.blender.modifiers;
 
+import java.util.List;
+
 import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 
 /**
  * This class represents an object's modifier. The modifier object can be varied
@@ -40,6 +44,16 @@ public abstract class Modifier {
      */
     public abstract void apply(Node node, BlenderContext blenderContext);
 
+    /**
+     * The method that is called when geometries are already created.
+     * @param node
+     *            the node that will have the modifier applied
+     * @param blenderContext
+     *            the blender context
+     */
+    public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
+    }
+
     /**
      * Determines if the modifier can be applied multiple times over one mesh.
      * At this moment only armature and object animation modifiers cannot be
@@ -67,4 +81,12 @@ public abstract class Modifier {
     public boolean isModifying() {
         return modifying;
     }
+
+    protected TemporalMesh getTemporalMesh(Node node) {
+        List<Spatial> children = node.getChildren();
+        if (children != null && children.size() == 1 && children.get(0) instanceof TemporalMesh) {
+            return (TemporalMesh) children.get(0);
+        }
+        return null;
+    }
 }

+ 47 - 34
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java

@@ -1,5 +1,10 @@
 package com.jme3.scene.plugins.blender.modifiers;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 import com.jme3.effect.ParticleEmitter;
 import com.jme3.effect.shapes.EmitterMeshVertexShape;
 import com.jme3.effect.shapes.EmitterShape;
@@ -13,13 +18,9 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.materials.MaterialHelper;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 /**
  * This modifier allows to add particles to the object.
  * 
@@ -53,42 +54,54 @@ import java.util.logging.Logger;
             }
         }
     }
+    
+    @Override
+    public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
+        LOGGER.log(Level.FINE, "Applying particles modifier to: {0}", node);
+        
+        MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
+        ParticleEmitter emitter = particleEmitter.clone();
+
+        // veryfying the alpha function for particles' texture
+        Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
+        char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
+        if (nameSuffix == 'B' || nameSuffix == 'N') {
+            alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
+        }
+        // removing the type suffix from the name
+        emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
+
+        // applying emitter shape
+        EmitterShape emitterShape = emitter.getShape();
+        List<Mesh> meshes = new ArrayList<Mesh>();
+        for (Spatial spatial : node.getChildren()) {
+            if (spatial instanceof Geometry) {
+                Mesh mesh = ((Geometry) spatial).getMesh();
+                if (mesh != null) {
+                    meshes.add(mesh);
+                    Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);
+                    emitter.setMaterial(material);// TODO: divide into several pieces
+                }
+            }
+        }
+        if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
+            ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
+        }
+
+        node.attachChild(emitter);
+    }
 
     @Override
     public void apply(Node node, BlenderContext blenderContext) {
         if (invalid) {
             LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName());
         } else {
-            MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
-            ParticleEmitter emitter = particleEmitter.clone();
-
-            // veryfying the alpha function for particles' texture
-            Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE;
-            char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1);
-            if (nameSuffix == 'B' || nameSuffix == 'N') {
-                alphaFunction = MaterialHelper.ALPHA_MASK_NONE;
-            }
-            // removing the type suffix from the name
-            emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1));
-
-            // applying emitter shape
-            EmitterShape emitterShape = emitter.getShape();
-            List<Mesh> meshes = new ArrayList<Mesh>();
-            for (Spatial spatial : node.getChildren()) {
-                if (spatial instanceof Geometry) {
-                    Mesh mesh = ((Geometry) spatial).getMesh();
-                    if (mesh != null) {
-                        meshes.add(mesh);
-                        Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext);
-                        emitter.setMaterial(material);// TODO: divide into several pieces
-                    }
-                }
-            }
-            if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) {
-                ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes);
+            TemporalMesh temporalMesh = this.getTemporalMesh(node);
+            if(temporalMesh != null) {
+                temporalMesh.applyAfterMeshCreate(this);
+            } else {
+                LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
             }
-
-            node.attachChild(emitter);
         }
     }
 }

+ 54 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java

@@ -0,0 +1,54 @@
+package com.jme3.scene.plugins.blender.modifiers;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.scene.Node;
+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.meshes.TemporalMesh;
+
+/**
+ * The triangulation modifier. It does not take any settings into account so if the result is different than
+ * in blender then please apply the modifier before importing.
+ * 
+ * @author Marcin Roguski
+ */
+public class TriangulateModifier extends Modifier {
+    private static final Logger LOGGER = Logger.getLogger(TriangulateModifier.class.getName());
+
+    /**
+     * This constructor reads animation data from the object structore. The
+     * stored data is the AnimData and additional data is armature's OMA.
+     * 
+     * @param objectStructure
+     *            the structure of the object
+     * @param modifierStructure
+     *            the structure of the modifier
+     * @param blenderContext
+     *            the blender context
+     * @throws BlenderFileException
+     *             this exception is thrown when the blender file is somehow
+     *             corrupted
+     */
+    public TriangulateModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
+        if (this.validate(modifierStructure, blenderContext)) {
+            LOGGER.warning("Triangulation modifier does not take modifier options into account. If triangulation result is different" + " than the model in blender please apply the modifier before importing!");
+        }
+    }
+
+    @Override
+    public void apply(Node node, BlenderContext blenderContext) {
+        if (invalid) {
+            LOGGER.log(Level.WARNING, "Triangulate modifier is invalid! Cannot be applied to: {0}", node.getName());
+        }
+        TemporalMesh temporalMesh = this.getTemporalMesh(node);
+        if (temporalMesh != null) {
+            LOGGER.log(Level.FINE, "Applying triangulation modifier to: {0}", temporalMesh);
+            temporalMesh.triangulate();
+        } else {
+            LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
+        }
+    }
+}

+ 68 - 55
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@@ -53,7 +53,7 @@ import com.jme3.scene.Spatial.CullHint;
 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.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.animations.AnimationHelper;
 import com.jme3.scene.plugins.blender.cameras.CameraHelper;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
@@ -64,6 +64,7 @@ import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.lights.LightHelper;
 import com.jme3.scene.plugins.blender.meshes.MeshHelper;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.modifiers.Modifier;
 import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
 import com.jme3.util.TempVars;
@@ -129,7 +130,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
         }
 
         LOGGER.fine("Checking if the object has not been already loaded.");
-        Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+        Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (loadedResult != null) {
             return loadedResult;
         }
@@ -137,12 +138,12 @@ public class ObjectHelper extends AbstractBlenderHelper {
         blenderContext.pushParent(objectStructure);
         String name = objectStructure.getName();
         LOGGER.log(Level.FINE, "Loading obejct: {0}", name);
-
+        
         int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue();
         boolean visible = (restrictflag & 0x01) != 0;
 
         Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
-        Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+        Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (parent == null && pParent.isNotNull()) {
             Structure parentStructure = pParent.fetchData().get(0);
             parent = this.toObject(parentStructure, blenderContext);
@@ -153,6 +154,11 @@ public class ObjectHelper extends AbstractBlenderHelper {
         Node result = null;
         try {
             switch (objectType) {
+                case LATTICE:
+                case METABALL:
+                case TEXT:
+                case WAVE:
+                    LOGGER.log(Level.WARNING, "{0} type is not supported but the node will be returned in order to keep parent - child relationship.", objectType);
                 case EMPTY:
                 case ARMATURE:
                     // need to use an empty node to properly create
@@ -164,11 +170,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
                     Pointer pMesh = (Pointer) objectStructure.getFieldValue("data");
                     List<Structure> meshesArray = pMesh.fetchData();
-                    List<Geometry> geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext);
-                    if (geometries != null) {
-                        for (Geometry geometry : geometries) {
-                            result.attachChild(geometry);
-                        }
+                    TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
+                    if(temporalMesh != null) {
+                        result.attachChild(temporalMesh);
                     }
                     break;
                 case SURF:
@@ -207,59 +211,68 @@ public class ObjectHelper extends AbstractBlenderHelper {
                 default:
                     LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type);
             }
-        } finally {
-            blenderContext.popParent();
-        }
-
-        if (result != null) {
-            LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released).");
-            blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
-            blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
-            if (objectType == ObjectType.ARMATURE) {
-                blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
-            }
+            
+            if (result != null) {
+                LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released).");
+                Long oma = objectStructure.getOldMemoryAddress();
+                blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure);
+                blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
+                
+                blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
+                if (objectType == ObjectType.ARMATURE) {
+                    blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
+                }
 
-            result.setLocalTransform(t);
-            result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
-            if (parent instanceof Node) {
-                ((Node) parent).attachChild(result);
-            }
+                result.setLocalTransform(t);
+                result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
+                if (parent instanceof Node) {
+                    ((Node) parent).attachChild(result);
+                }
 
-            if (result.getChildren() != null) {
-                for (Spatial child : result.getChildren()) {
-                    if (child instanceof Geometry) {
-                        this.flipMeshIfRequired((Geometry) child, child.getWorldScale());
+                LOGGER.fine("Reading and applying object's modifiers.");
+                ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
+                Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
+                for (Modifier modifier : modifiers) {
+                    modifier.apply(result, blenderContext);
+                }
+                
+                if (result.getChildren() != null && result.getChildren().size() > 0) {
+                    if(result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) {
+                        LOGGER.fine("Converting temporal mesh into jme geometries.");
+                        ((TemporalMesh)result.getChild(0)).toGeometries();
+                    }
+                    
+                    LOGGER.fine("Applying proper scale to the geometries.");
+                    for (Spatial child : result.getChildren()) {
+                        if (child instanceof Geometry) {
+                            this.flipMeshIfRequired((Geometry) child, child.getWorldScale());
+                        }
                     }
                 }
-            }
 
-            LOGGER.fine("Reading and applying object's modifiers.");
-            ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
-            Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
-            for (Modifier modifier : modifiers) {
-                modifier.apply(result, blenderContext);
-            }
+                // I prefer do compute bounding box here than read it from the file
+                result.updateModelBound();
 
-            // I prefer do compute bounding box here than read it from the file
-            result.updateModelBound();
+                LOGGER.fine("Applying animations to the object if such are defined.");
+                AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
+                animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
 
-            LOGGER.fine("Applying animations to the object if such are defined.");
-            AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
-            animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
+                LOGGER.fine("Loading constraints connected with this object.");
+                ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
+                constraintHelper.loadConstraints(objectStructure, blenderContext);
 
-            LOGGER.fine("Loading constraints connected with this object.");
-            ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-            constraintHelper.loadConstraints(objectStructure, blenderContext);
-
-            LOGGER.fine("Loading custom properties.");
-            if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
-                Properties properties = this.loadProperties(objectStructure, blenderContext);
-                // the loaded property is a group property, so we need to get
-                // each value and set it to Spatial
-                if (properties != null && properties.getValue() != null) {
-                    this.applyProperties(result, properties);
+                LOGGER.fine("Loading custom properties.");
+                if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
+                    Properties properties = this.loadProperties(objectStructure, blenderContext);
+                    // the loaded property is a group property, so we need to get
+                    // each value and set it to Spatial
+                    if (properties != null && properties.getValue() != null) {
+                        this.applyProperties(result, properties);
+                    }
                 }
             }
+        } finally {
+            blenderContext.popParent();
         }
         return result;
     }
@@ -323,8 +336,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
      * @return <b>true</b> if the first given OMA points to a parent of the second one and <b>false</b> otherwise
      */
     public boolean isParent(Long supposedParentOMA, Long spatialOMA) {
-        Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedFeatureDataType.LOADED_FEATURE);
-        Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE);
+        Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedDataType.FEATURE);
+        Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedDataType.FEATURE);
 
         Spatial parent = spatial.getParent();
         while (parent != null) {
@@ -350,7 +363,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
         Matrix4f parentInv = tempVars.tempMat4;
         Pointer pParent = (Pointer) objectStructure.getFieldValue("parent");
         if (pParent.isNotNull()) {
-            Structure parentObjectStructure = (Structure) blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_STRUCTURE);
+            Structure parentObjectStructure = (Structure) blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedDataType.STRUCTURE);
             this.getMatrix(parentObjectStructure, "obmat", fixUpAxis, parentInv).invertLocal();
         } else {
             parentInv.loadIdentity();

+ 9 - 8
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java

@@ -5,8 +5,8 @@ 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.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -17,9 +17,10 @@ import com.jme3.math.Vector2f;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.Structure;
 import com.jme3.scene.plugins.blender.materials.MaterialContext;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
 import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
 import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType;
@@ -34,6 +35,7 @@ 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.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
 
 /**
@@ -132,8 +134,7 @@ public class CombinedTexture {
      * @param blenderContext
      *            the blender context
      */
-    @SuppressWarnings("unchecked")
-    public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
+    public void flatten(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
         Mesh mesh = geometry.getMesh();
         Texture previousTexture = null;
         UVCoordinatesType masterUVCoordinatesType = null;
@@ -159,8 +160,8 @@ public class CombinedTexture {
                         }
                         masterUserUVSetName = textureData.uvCoordinatesName;
                     } else {
-                        List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
-                        resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries);
+                        TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH);
+                        resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, temporalMesh);
                     }
                 }
                 this.blend(resultTexture, textureData.textureBlender, blenderContext);
@@ -198,7 +199,7 @@ public class CombinedTexture {
                                 textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
                             }
                         } else {
-                            List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
+                            TemporalMesh geometries = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH);
                             textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries);
                         }
                         TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext);
@@ -297,7 +298,7 @@ public class CombinedTexture {
                 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));
+                texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data, ColorSpace.Linear));
             }
 
             if (result == null) {

+ 4 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java

@@ -9,11 +9,11 @@ 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;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
 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;
@@ -56,7 +56,7 @@ import com.jme3.util.TempVars;
     private final Structure             mTex;
     /** Texture generateo for the specified texture type. */
     private final TextureGenerator      textureGenerator;
-    /** 
+    /**
      * The generated texture cast functions. They are used to cas a given point on a plane to a specified shape in 3D space.
      * The functions should be ordered as the ordinal of a BlenderKey.CastFunction enums.
      */
@@ -152,9 +152,8 @@ import com.jme3.util.TempVars;
      *            the blender context
      * @return triangulated texture
      */
-    @SuppressWarnings("unchecked")
     public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) {
-        List<Geometry> geometries = (List<Geometry>) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE);
+        TemporalMesh geometries = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH);
 
         int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() };
         List<Vector3f> uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries);

+ 7 - 5
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java

@@ -51,7 +51,7 @@ import com.jme3.math.Vector2f;
 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.BlenderContext.LoadedDataType;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.FileBlockHeader;
@@ -69,6 +69,7 @@ import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.MinFilter;
 import com.jme3.texture.Texture.WrapMode;
 import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
 
 /**
@@ -130,7 +131,7 @@ public class TextureHelper extends AbstractBlenderHelper {
      *             somehow invalid or corrupted
      */
     public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException {
-        Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+        Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (result != null) {
             return result;
         }
@@ -200,7 +201,8 @@ public class TextureHelper extends AbstractBlenderHelper {
             if (LOGGER.isLoggable(Level.FINE)) {
                 LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() });
             }
-            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), tex.getName(), tex, result);
+            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.STRUCTURE, tex);
+            blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
         }
         return result;
     }
@@ -223,7 +225,7 @@ public class TextureHelper extends AbstractBlenderHelper {
     protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException {
         LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress());
         Texture result = null;
-        Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+        Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE);
         if (im == null) {
             String texturePath = imageStructure.getFieldValue("name").toString();
             Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile");
@@ -340,7 +342,7 @@ public class TextureHelper extends AbstractBlenderHelper {
         int height = maxY - minY;
         ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3));
 
-        Image result = new Image(image.getFormat(), width, height, data);
+        Image result = new Image(image.getFormat(), width, height, data, ColorSpace.sRGB);
         PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
         TexturePixel pixel = new TexturePixel();
 

+ 37 - 97
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java

@@ -31,6 +31,10 @@
  */
 package com.jme3.scene.plugins.blender.textures;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
 import com.jme3.bounding.BoundingBox;
 import com.jme3.bounding.BoundingSphere;
 import com.jme3.bounding.BoundingVolume;
@@ -41,10 +45,6 @@ import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer;
 import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType;
 import com.jme3.util.BufferUtils;
-import java.nio.FloatBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Logger;
 
 /**
  * This class is used for UV coordinates generation.
@@ -55,7 +55,7 @@ public class UVCoordinatesGenerator {
     private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName());
 
     public static enum UVCoordinatesType {
-        TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128), 
+        TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128),
         TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096),
         TEXCO_PARTICLE_OR_STRAND(8192), //TEXCO_PARTICLE (since blender 2.6x) has also the value of: 8192 but is used for halo materials instead of normal materials
         TEXCO_STRESS(16384), TEXCO_SPEED(32768);
@@ -90,7 +90,7 @@ public class UVCoordinatesGenerator {
      *            bounding box)
      * @return UV coordinates for the given mesh
      */
-    public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List<Geometry> geometries) {
+    public static List<Vector2f> generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, Geometry geometries) {
         List<Vector2f> result = new ArrayList<Vector2f>();
         BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
         float[] inputData = null;// positions, normals, reflection vectors, etc.
@@ -166,7 +166,7 @@ public class UVCoordinatesGenerator {
      *            bounding box)
      * @return UV coordinates for the given mesh
      */
-    public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List<Geometry> geometries) {
+    public static List<Vector3f> generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, Geometry... geometries) {
         List<Vector3f> result = new ArrayList<Vector3f>();
         BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries);
         float[] inputData = null;// positions, normals, reflection vectors, etc.
@@ -264,40 +264,24 @@ public class UVCoordinatesGenerator {
      *            the list of geometries
      * @return bounding box of the given geometries
      */
-    public static BoundingBox getBoundingBox(List<Geometry> geometries) {
+    public static BoundingBox getBoundingBox(Geometry... geometries) {
         BoundingBox result = null;
         for (Geometry geometry : geometries) {
-            BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh());
-            if (result == null) {
-                result = bb;
+            geometry.updateModelBound();
+            BoundingVolume bv = geometry.getModelBound();
+            if (bv instanceof BoundingBox) {
+                return (BoundingBox) bv;
+            } else if (bv instanceof BoundingSphere) {
+                BoundingSphere bs = (BoundingSphere) bv;
+                float r = bs.getRadius();
+                return new BoundingBox(bs.getCenter(), r, r, r);
             } else {
-                result.merge(bb);
+                throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
             }
         }
         return result;
     }
 
-    /**
-     * This method returns the bounding box of the given mesh.
-     * 
-     * @param mesh
-     *            the mesh
-     * @return bounding box of the given mesh
-     */
-    /* package */static BoundingBox getBoundingBox(Mesh mesh) {
-        mesh.updateBound();
-        BoundingVolume bv = mesh.getBound();
-        if (bv instanceof BoundingBox) {
-            return (BoundingBox) bv;
-        } else if (bv instanceof BoundingSphere) {
-            BoundingSphere bs = (BoundingSphere) bv;
-            float r = bs.getRadius();
-            return new BoundingBox(bs.getCenter(), r, r, r);
-        } else {
-            throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
-        }
-    }
-
     /**
      * This method returns the bounding sphere of the given geometries.
      * 
@@ -305,74 +289,25 @@ public class UVCoordinatesGenerator {
      *            the list of geometries
      * @return bounding sphere of the given geometries
      */
-    /* package */static BoundingSphere getBoundingSphere(List<Geometry> geometries) {
+    /* package */static BoundingSphere getBoundingSphere(Geometry... geometries) {
         BoundingSphere result = null;
         for (Geometry geometry : geometries) {
-            BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh());
-            if (result == null) {
-                result = bs;
+            geometry.updateModelBound();
+            BoundingVolume bv = geometry.getModelBound();
+            if (bv instanceof BoundingBox) {
+                BoundingBox bb = (BoundingBox) bv;
+                float r = Math.max(bb.getXExtent(), bb.getYExtent());
+                r = Math.max(r, bb.getZExtent());
+                return new BoundingSphere(r, bb.getCenter());
+            } else if (bv instanceof BoundingSphere) {
+                return (BoundingSphere) bv;
             } else {
-                result.merge(bs);
+                throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
             }
         }
         return result;
     }
 
-    /**
-     * This method returns the bounding sphere of the given mesh.
-     * 
-     * @param mesh
-     *            the mesh
-     * @return bounding sphere of the given mesh
-     */
-    /* package */static BoundingSphere getBoundingSphere(Mesh mesh) {
-        mesh.updateBound();
-        BoundingVolume bv = mesh.getBound();
-        if (bv instanceof BoundingBox) {
-            BoundingBox bb = (BoundingBox) bv;
-            float r = Math.max(bb.getXExtent(), bb.getYExtent());
-            r = Math.max(r, bb.getZExtent());
-            return new BoundingSphere(r, bb.getCenter());
-        } else if (bv instanceof BoundingSphere) {
-            return (BoundingSphere) bv;
-        } else {
-            throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName());
-        }
-    }
-
-    /**
-     * This method returns the bounding tube of the given mesh.
-     * 
-     * @param mesh
-     *            the mesh
-     * @return bounding tube of the given mesh
-     */
-    /* package */static BoundingTube getBoundingTube(Mesh mesh) {
-        Vector3f center = new Vector3f();
-        float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE;
-        float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE;
-        float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE;
-
-        FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position);
-        int limit = positions.limit();
-        for (int i = 0; i < limit; i += 3) {
-            float x = positions.get(i);
-            float y = positions.get(i + 1);
-            float z = positions.get(i + 2);
-            center.addLocal(x, y, z);
-            maxx = x > maxx ? x : maxx;
-            minx = x < minx ? x : minx;
-            maxy = y > maxy ? y : maxy;
-            miny = y < miny ? y : miny;
-            maxz = z > maxz ? z : maxz;
-            minz = z < minz ? z : minz;
-        }
-        center.divideLocal(limit / 3);
-
-        float radius = Math.max(maxx - minx, maxy - miny) * 0.5f;
-        return new BoundingTube(radius, maxz - minz, center);
-    }
-
     /**
      * This method returns the bounding tube of the given geometries.
      * 
@@ -380,10 +315,15 @@ public class UVCoordinatesGenerator {
      *            the list of geometries
      * @return bounding tube of the given geometries
      */
-    /* package */static BoundingTube getBoundingTube(List<Geometry> geometries) {
+    /* package */static BoundingTube getBoundingTube(Geometry... geometries) {
         BoundingTube result = null;
         for (Geometry geometry : geometries) {
-            BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh());
+            BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry);
+            Vector3f max = bb.getMax(null);
+            Vector3f min = bb.getMin(null);
+            float radius = Math.max(max.x - min.x, max.y - min.y) * 0.5f;
+            
+            BoundingTube bt = new BoundingTube(radius, max.z - min.z, bb.getCenter());
             if (result == null) {
                 result = bt;
             } else {
@@ -394,7 +334,7 @@ public class UVCoordinatesGenerator {
     }
 
     /**
-     * A very simple bounding tube. Id holds only the basic data bout the
+     * A very simple bounding tube. It holds only the basic data bout the
      * bounding tube and does not provide full functionality of a
      * BoundingVolume. Should be replaced with a bounding tube that extends the
      * BoundingVolume if it is ever created.
@@ -432,7 +372,7 @@ public class UVCoordinatesGenerator {
         public BoundingTube merge(BoundingTube boundingTube) {
             // get tubes (tube1.radius >= tube2.radius)
             BoundingTube tube1, tube2;
-            if (this.radius >= boundingTube.radius) {
+            if (radius >= boundingTube.radius) {
                 tube1 = this;
                 tube2 = boundingTube;
             } else {